more fixes

* use bold style in status (mission_item_linear.xml)
* fix download attemps not begin updated
* dont stop the queue if a download fails
* implement partial wake-lock & wifi-lock
* show notifications for failed downloads
* ¿proper bitmap dispose? (DownloadManagerService.java)
* improve buffer filling (CircularFile.java)
* [Mp4Dash] increment reserved space from 2MiB to 15MiB. This is expensive but useful for devices with low ram
* [WebM] use 2MiB of reserved space
* fix debug warning if one thread is used
* fix wrong download speed when the activity is suspended
* Fix "Queue" menu item that appears in post-processing errors
* fix mission length dont being updated (missing commit)
This commit is contained in:
kapodamy 2018-11-20 19:10:50 -03:00
parent fef9d541ed
commit d647555e3a
19 changed files with 400 additions and 150 deletions

View file

@ -11,6 +11,7 @@ import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -37,6 +38,7 @@ import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilenameUtils; import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.SecondaryStreamHelper;
import org.schabi.newpipe.util.StreamItemAdapter; import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -55,17 +57,24 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private static final String TAG = "DialogFragment"; private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG; private static final boolean DEBUG = MainActivity.DEBUG;
@State protected StreamInfo currentInfo; @State
@State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty(); protected StreamInfo currentInfo;
@State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty(); @State
@State protected StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty(); protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
@State protected int selectedVideoIndex = 0; @State
@State protected int selectedAudioIndex = 0; protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
@State protected int selectedSubtitleIndex = 0; @State
protected StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams = StreamSizeWrapper.empty();
@State
protected int selectedVideoIndex = 0;
@State
protected int selectedAudioIndex = 0;
@State
protected int selectedSubtitleIndex = 0;
private StreamItemAdapter<AudioStream> audioStreamsAdapter; private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream> videoStreamsAdapter; private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
private StreamItemAdapter<SubtitlesStream> subtitleStreamsAdapter; private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
private final CompositeDisposable disposables = new CompositeDisposable(); private final CompositeDisposable disposables = new CompositeDisposable();
@ -144,7 +153,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); if (DEBUG)
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
getDialog().dismiss(); getDialog().dismiss();
return; return;
@ -153,14 +163,29 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext())); setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
Icepick.restoreInstanceState(this, savedInstanceState); Icepick.restoreInstanceState(this, savedInstanceState);
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, true); SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4);
List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) continue;
AudioStream audioStream = SecondaryStreamHelper.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));
if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams, audioStream));
} else if (DEBUG) {
Log.w(TAG, "No audio stream candidates for video format " + videoStreams.get(i).getFormat().name());
}
}
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams); this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedSubtitleStreams); this.subtitleStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedSubtitleStreams);
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]"); if (DEBUG)
Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.download_dialog, container); return inflater.inflate(R.layout.download_dialog, container);
} }
@ -293,7 +318,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
@Override @Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]"); if (DEBUG)
Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
boolean flag = true; boolean flag = true;
switch (checkedId) { switch (checkedId) {
@ -318,7 +344,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (DEBUG) Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); if (DEBUG)
Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) { switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button: case R.id.audio_button:
selectedAudioIndex = position; selectedAudioIndex = position;
@ -458,57 +485,41 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
String[] urls; String[] urls;
String psName = null; String psName = null;
String[] psArgs = null; String[] psArgs = null;
String secondaryStream = null; String secondaryStreamUrl = null;
long nearLength = 0;
if (selectedStream instanceof VideoStream) { if (selectedStream instanceof VideoStream) {
VideoStream videoStream = (VideoStream) selectedStream; SecondaryStreamHelper<AudioStream> secondaryStream = videoStreamsAdapter
if (videoStream.isVideoOnly() && videoStream.getFormat() != MediaFormat.v3GPP) { .getAllSecondary()
boolean m4v = videoStream.getFormat() == MediaFormat.MPEG_4; .get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
for (AudioStream audio : audioStreamsAdapter.getAll()) { if (secondaryStream != null) {
if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) { secondaryStreamUrl = secondaryStream.getStream().getUrl();
secondaryStream = audio.getUrl(); psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
break;
}
}
if (secondaryStream == null) {
// retry, but this time in reverse order
List<AudioStream> audioStreams = audioStreamsAdapter.getAll();
for (int i = audioStreams.size() - 1; i >= 0; i--) {
AudioStream audio = audioStreams.get(i);
if (audio.getFormat() == (m4v ? MediaFormat.MP3 : MediaFormat.OPUS)) {
secondaryStream = audio.getUrl();
break;
}
}
}
if (secondaryStream == null) {
Log.w(TAG, "No audio stream candidates for video format " + videoStream.getFormat().name());
psName = null;
psArgs = null;
} else {
psName = m4v ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
psArgs = null; psArgs = null;
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
// set nearLength, only, if both sizes are fetched or known. this probably does not work on weak internet connections
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
nearLength = secondaryStream.getSizeInBytes() + videoSize;
} }
} }
} else if ((selectedStream instanceof SubtitlesStream) && selectedStream.getFormat() == MediaFormat.TTML) { } else if ((selectedStream instanceof SubtitlesStream) && selectedStream.getFormat() == MediaFormat.TTML) {
psName = Postprocessing.ALGORITHM_TTML_CONVERTER; psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
psArgs = new String[]{ psArgs = new String[]{
selectedStream.getFormat().getSuffix(), selectedStream.getFormat().getSuffix(),
"false",//ignore empty frames "false",// ignore empty frames
"false",// detect youtube duplicateLines "false",// detect youtube duplicate lines
}; };
} }
if (secondaryStream == null) { if (secondaryStreamUrl == null) {
urls = new String[]{selectedStream.getUrl()}; urls = new String[]{selectedStream.getUrl()};
} else { } else {
urls = new String[]{selectedStream.getUrl(), secondaryStream}; urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl};
} }
DownloadManagerService.startMission(context, urls, location, fileName, kind, threads, currentInfo.getUrl(), psName, psArgs); DownloadManagerService.startMission(context, urls, location, fileName, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength);
getDialog().dismiss(); getDialog().dismiss();
} }

View file

@ -746,7 +746,7 @@ public class VideoDetailFragment
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false); sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams); selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams);
final StreamItemAdapter<VideoStream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled); final StreamItemAdapter<VideoStream, Stream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled);
spinnerToolbar.setAdapter(streamsAdapter); spinnerToolbar.setAdapter(streamsAdapter);
spinnerToolbar.setSelection(selectedVideoStreamIndex); spinnerToolbar.setSelection(selectedVideoStreamIndex);
spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

View file

@ -257,7 +257,7 @@ public class Mp4DashReader {
private String boxName(int type) { private String boxName(int type) {
try { try {
return new String(ByteBuffer.allocate(4).putInt(type).array(), "US-ASCII"); return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
return "0x" + Integer.toHexString(type); return "0x" + Integer.toHexString(type);
} }

View file

@ -0,0 +1,66 @@
package org.schabi.newpipe.util;
import android.support.annotation.NonNull;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import java.util.List;
public class SecondaryStreamHelper<T extends Stream> {
private final int position;
private final StreamSizeWrapper<T> streams;
public SecondaryStreamHelper(StreamSizeWrapper<T> streams, T selectedStream) {
this.streams = streams;
this.position = streams.getStreamsList().indexOf(selectedStream);
if (this.position < 0) throw new RuntimeException("selected stream not found");
}
public T getStream() {
return streams.getStreamsList().get(position);
}
public long getSizeInBytes() {
return streams.getSizeInBytes(position);
}
/**
* find the correct audio stream for the desired video stream
*
* @param audioStreams list of audio streams
* @param videoStream desired video ONLY stream
* @return selected audio stream or null if a candidate was not found
*/
public static AudioStream getAudioStreamFor(@NonNull List<AudioStream> audioStreams, @NonNull VideoStream videoStream) {
// TODO: check if m4v and m4a selected streams are DASH compliant
switch (videoStream.getFormat()) {
case WEBM:
case MPEG_4:
break;
default:
return null;
}
boolean m4v = videoStream.getFormat() == MediaFormat.MPEG_4;
for (AudioStream audio : audioStreams) {
if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) {
return audio;
}
}
// retry, but this time in reverse order
for (int i = audioStreams.size() - 1; i >= 0; i--) {
AudioStream audio = audioStreams.get(i);
if (audio.getFormat() == (m4v ? MediaFormat.MP3 : MediaFormat.OPUS)) {
return audio;
}
}
return null;
}
}

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import android.content.Context; import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -29,26 +30,34 @@ import us.shandian.giga.util.Utility;
/** /**
* A list adapter for a list of {@link Stream streams}, currently supporting {@link VideoStream} and {@link AudioStream}. * A list adapter for a list of {@link Stream streams}, currently supporting {@link VideoStream} and {@link AudioStream}.
*/ */
public class StreamItemAdapter<T extends Stream> extends BaseAdapter { public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseAdapter {
private final Context context; private final Context context;
private final StreamSizeWrapper<T> streamsWrapper; private final StreamSizeWrapper<T> streamsWrapper;
private final boolean showIconNoAudio; private final SparseArray<SecondaryStreamHelper<U>> secondaryStreams;
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, boolean showIconNoAudio) { public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, SparseArray<SecondaryStreamHelper<U>> secondaryStreams) {
this.context = context; this.context = context;
this.streamsWrapper = streamsWrapper; this.streamsWrapper = streamsWrapper;
this.showIconNoAudio = showIconNoAudio; this.secondaryStreams = secondaryStreams;
}
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, boolean showIconNoAudio) {
this(context, streamsWrapper, showIconNoAudio ? new SparseArray<>() : null);
} }
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper) { public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper) {
this(context, streamsWrapper, false); this(context, streamsWrapper, null);
} }
public List<T> getAll() { public List<T> getAll() {
return streamsWrapper.getStreamsList(); return streamsWrapper.getStreamsList();
} }
public SparseArray<SecondaryStreamHelper<U>> getAllSecondary() {
return secondaryStreams;
}
@Override @Override
public int getCount() { public int getCount() {
return streamsWrapper.getStreamsList().size(); return streamsWrapper.getStreamsList().size();
@ -90,23 +99,16 @@ public class StreamItemAdapter<T extends Stream> extends BaseAdapter {
String qualityString; String qualityString;
if (stream instanceof VideoStream) { if (stream instanceof VideoStream) {
qualityString = ((VideoStream) stream).getResolution(); VideoStream videoStream = ((VideoStream) stream);
qualityString = videoStream.getResolution();
if (!showIconNoAudio) { if (secondaryStreams != null) {
woSoundIconVisibility = View.GONE; if (videoStream.isVideoOnly()) {
} else if (((VideoStream) stream).isVideoOnly()) { woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE : View.INVISIBLE;
switch (stream.getFormat()) {
case WEBM:// fully supported
case MPEG_4:// ¿is DASH MPEG-4 format?
woSoundIconVisibility = View.INVISIBLE;
break;
default:
woSoundIconVisibility = View.VISIBLE;
break;
}
} else if (isDropdownItem) { } else if (isDropdownItem) {
woSoundIconVisibility = View.INVISIBLE; woSoundIconVisibility = View.INVISIBLE;
} }
}
} else if (stream instanceof AudioStream) { } else if (stream instanceof AudioStream) {
qualityString = ((AudioStream) stream).getAverageBitrate() + "kbps"; qualityString = ((AudioStream) stream).getAverageBitrate() + "kbps";
} else if (stream instanceof SubtitlesStream) { } else if (stream instanceof SubtitlesStream) {
@ -119,7 +121,13 @@ public class StreamItemAdapter<T extends Stream> extends BaseAdapter {
} }
if (streamsWrapper.getSizeInBytes(position) > 0) { if (streamsWrapper.getSizeInBytes(position) > 0) {
SecondaryStreamHelper secondary = secondaryStreams == null ? null : secondaryStreams.get(position);
if (secondary != null) {
long size = secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position);
sizeView.setText(Utility.formatBytes(size));
} else {
sizeView.setText(streamsWrapper.getFormattedSize(position)); sizeView.setText(streamsWrapper.getFormattedSize(position));
}
sizeView.setVisibility(View.VISIBLE); sizeView.setVisibility(View.VISIBLE);
} else { } else {
sizeView.setVisibility(View.GONE); sizeView.setVisibility(View.GONE);

View file

@ -74,7 +74,7 @@ public class DownloadInitializer implements Runnable {
} }
} else { } else {
// if one thread is solicited don't calculate blocks, is useless // if one thread is solicited don't calculate blocks, is useless
mMission.blocks = 0; mMission.blocks = 1;
mMission.fallback = true; mMission.fallback = true;
mMission.unknownLength = false; mMission.unknownLength = false;
} }

View file

@ -103,6 +103,11 @@ public class DownloadMission extends Mission {
*/ */
public int maxRetry; public int maxRetry;
/**
* Approximated final length, this represent the sum of all resources sizes
*/
public long nearLength;
public int threadCount = 3; public int threadCount = 3;
boolean fallback; boolean fallback;
private int finishCount; private int finishCount;
@ -432,7 +437,7 @@ public class DownloadMission extends Mission {
return; return;
} }
if (DEBUG && blocks < 1) { if (DEBUG && blocks == 0) {
Log.w(TAG, "pausing a download that can not be resumed."); Log.w(TAG, "pausing a download that can not be resumed.");
} }
@ -507,6 +512,13 @@ public class DownloadMission extends Mission {
return current >= urls.length && postprocessingName == null; return current >= urls.length && postprocessingName == null;
} }
public long getLength() {
long near = offsets[current < offsets.length ? current : (offsets.length - 1)] + length;
near -= offsets[0];// don't count reserved space
return near > nearLength ? near : nearLength;
}
private boolean doPostprocessing() { private boolean doPostprocessing() {
if (postprocessingName == null) return true; if (postprocessingName == null) return true;

View file

@ -14,7 +14,7 @@ class Mp4DashMuxer extends Postprocessing {
Mp4DashMuxer(DownloadMission mission) { Mp4DashMuxer(DownloadMission mission) {
super(mission); super(mission);
recommendedReserve = 2048 * 1024;// 2 MiB recommendedReserve = 15360 * 1024;// 15 MiB
worksOnSameFile = true; worksOnSameFile = true;
} }

View file

@ -91,6 +91,8 @@ public abstract class Postprocessing {
out = new CircularFile(file, 0, this::progressReport, checker); out = new CircularFile(file, 0, this::progressReport, checker);
mission.done = 0; mission.done = 0;
mission.length = mission.getLength();
int result = process(out, sources); int result = process(out, sources);
if (result == OK_RESULT) { if (result == OK_RESULT) {

View file

@ -16,7 +16,7 @@ class WebMMuxer extends Postprocessing {
WebMMuxer(DownloadMission mission) { WebMMuxer(DownloadMission mission) {
super(mission); super(mission);
recommendedReserve = (1024 + 512) * 1024;// 1.50 MiB recommendedReserve = 2048 * 1024;// 2 MiB
worksOnSameFile = true; worksOnSameFile = true;
} }

View file

@ -10,7 +10,7 @@ import java.util.ArrayList;
public class CircularFile extends SharpStream { public class CircularFile extends SharpStream {
private final static int AUX_BUFFER_SIZE = 1024 * 1024;// 1 MiB private final static int AUX_BUFFER_SIZE = 1024 * 1024;// 1 MiB
private final static int AUX2_BUFFER_SIZE = 256 * 1024;// 256 KiB private final static int NOTIFY_BYTES_INTERVAL = 256 * 1024;// 256 KiB
private final static int QUEUE_BUFFER_SIZE = 8 * 1024;// 8 KiB private final static int QUEUE_BUFFER_SIZE = 8 * 1024;// 8 KiB
private RandomAccessFile out; private RandomAccessFile out;
@ -108,32 +108,56 @@ public class CircularFile extends SharpStream {
} }
long end = callback.check(); long end = callback.check();
int available; long available;
if (end == -1) { if (end == -1) {
available = Integer.MAX_VALUE; available = Long.MAX_VALUE;
} else { } else {
if (end < startOffset) { if (end < startOffset) {
throw new IOException("The reported offset is invalid. reported offset is " + String.valueOf(end)); throw new IOException("The reported offset is invalid. reported offset is " + String.valueOf(end));
} }
available = (int) (end - position); available = end - position;
} }
while (available > 0 && auxiliaryBuffers.size() > 0) { while (available > 0 && auxiliaryBuffers.size() > 0) {
ManagedBuffer aux = auxiliaryBuffers.get(0); ManagedBuffer aux = auxiliaryBuffers.get(0);
if ((queue.size + aux.size) > available) { // check if there is enough space to dump the auxiliar buffer
available = 0;// wait for next check if (available >= (aux.size + queue.size)) {
available -= aux.size;
writeQueue(aux.buffer, 0, aux.size);
aux.dereference();
auxiliaryBuffers.remove(0);
continue;
}
// try flush contents to avoid allocate another auxiliar buffer
if (aux.available() < len && available > queue.size) {
int size = Math.min(len, aux.available());
aux.write(b, off, size);
off += size;
len -= size;
size = Math.min(aux.size, (int) available - queue.size);
if (size < 1) {
break; break;
} }
writeQueue(aux.buffer, 0, aux.size); writeQueue(aux.buffer, 0, size);
available -= aux.size; aux.dereference(size);
aux.dereference();
auxiliaryBuffers.remove(0); available -= size;
} }
if (available > (len + queue.size)) { break;
}
if (len < 1) {
return;
}
if (auxiliaryBuffers.size() < 1 && available > (len + queue.size)) {
writeQueue(b, off, len); writeQueue(b, off, len);
} else { } else {
int i = auxiliaryBuffers.size() - 1; int i = auxiliaryBuffers.size() - 1;
@ -150,14 +174,14 @@ public class CircularFile extends SharpStream {
if (available < 1) { if (available < 1) {
// secondary auxiliary buffer // secondary auxiliary buffer
available = len; available = len;
aux = new ManagedBuffer(Math.max(len, AUX2_BUFFER_SIZE)); aux = new ManagedBuffer(Math.max(len, AUX_BUFFER_SIZE));
auxiliaryBuffers.add(aux); auxiliaryBuffers.add(aux);
i++; i++;
} else { } else {
available = Math.min(len, available); available = Math.min(len, available);
} }
aux.write(b, off, available); aux.write(b, off, (int) available);
len -= available; len -= available;
if (len < 1) { if (len < 1) {
@ -173,7 +197,7 @@ public class CircularFile extends SharpStream {
position += length; position += length;
if (onProgress != null && position > reportPosition) { if (onProgress != null && position > reportPosition) {
reportPosition = position + AUX2_BUFFER_SIZE;// notify every 256 KiB (approx) reportPosition = position + NOTIFY_BYTES_INTERVAL;
onProgress.report(position); onProgress.report(position);
} }
} }
@ -195,6 +219,10 @@ public class CircularFile extends SharpStream {
offset += size; offset += size;
length -= size; length -= size;
} }
if (queue.size >= queue.buffer.length) {
flushQueue();
}
} }
private void flushQueue() throws IOException { private void flushQueue() throws IOException {
@ -238,7 +266,9 @@ public class CircularFile extends SharpStream {
flush(); flush();
out.seek(startOffset); out.seek(startOffset);
if (onProgress != null) onProgress.report(-position); if (onProgress != null) {
onProgress.report(-position);
}
position = startOffset; position = startOffset;
reportPosition = startOffset; reportPosition = startOffset;
@ -327,6 +357,18 @@ public class CircularFile extends SharpStream {
size = 0; size = 0;
} }
void dereference(int amount) {
if (amount > size) {
throw new IndexOutOfBoundsException("Invalid dereference amount (" + amount + ">=" + size + ")");
}
size -= amount;
for (int i = 0; i < size; i++) {
buffer[i] = buffer[amount + i];
}
}
protected int available() { protected int available() {
return buffer.length - size; return buffer.length - size;
} }

View file

@ -116,7 +116,6 @@ public class DownloadManager {
return result; return result;
} }
@SuppressWarnings("ResultOfMethodCallIgnored")
private void loadPendingMissions() { private void loadPendingMissions() {
File[] subs = mPendingMissionsDir.listFiles(); File[] subs = mPendingMissionsDir.listFiles();
@ -136,9 +135,11 @@ public class DownloadManager {
DownloadMission mis = Utility.readFromFile(sub); DownloadMission mis = Utility.readFromFile(sub);
if (mis == null) { if (mis == null) {
//noinspection ResultOfMethodCallIgnored
sub.delete(); sub.delete();
} else { } else {
if (mis.isFinished()) { if (mis.isFinished()) {
//noinspection ResultOfMethodCallIgnored
sub.delete(); sub.delete();
continue; continue;
} }
@ -173,6 +174,7 @@ public class DownloadManager {
m.threadCount = mis.threadCount; m.threadCount = mis.threadCount;
m.source = mis.source; m.source = mis.source;
m.maxRetry = mis.maxRetry; m.maxRetry = mis.maxRetry;
m.nearLength = mis.nearLength;
mis = m; mis = m;
} }
@ -204,7 +206,7 @@ public class DownloadManager {
* @param postProcessingArgs the arguments for the post-processing algorithm. * @param postProcessingArgs the arguments for the post-processing algorithm.
*/ */
void startMission(String[] urls, String location, String name, char kind, int threads, String source, void startMission(String[] urls, String location, String name, char kind, int threads, String source,
String postprocessingName, String[] postProcessingArgs) { String postprocessingName, String[] postProcessingArgs, long nearLength) {
synchronized (this) { synchronized (this) {
// check for existing pending download // check for existing pending download
DownloadMission pendingMission = getPendingMission(location, name); DownloadMission pendingMission = getPendingMission(location, name);
@ -229,6 +231,7 @@ public class DownloadManager {
mission.source = source; mission.source = source;
mission.mHandler = mHandler; mission.mHandler = mHandler;
mission.maxRetry = mPrefs.getInt(mPrefMaxRetry, 3); mission.maxRetry = mPrefs.getInt(mPrefMaxRetry, 3);
mission.nearLength = nearLength;
while (true) { while (true) {
mission.metadata = new File(mPendingMissionsDir, String.valueOf(mission.timestamp)); mission.metadata = new File(mPendingMissionsDir, String.valueOf(mission.timestamp));
@ -406,26 +409,30 @@ public class DownloadManager {
* Set a pending download as finished * Set a pending download as finished
* *
* @param mission the desired mission * @param mission the desired mission
* @return true if exits pending missions running, otherwise, false
*/ */
boolean setFinished(DownloadMission mission) { void setFinished(DownloadMission mission) {
synchronized (this) { synchronized (this) {
int i = mMissionsPending.indexOf(mission); mMissionsPending.remove(mission);
mMissionsPending.remove(i);
mMissionsFinished.add(0, new FinishedMission(mission)); mMissionsFinished.add(0, new FinishedMission(mission));
mDownloadDataSource.addMission(mission); mDownloadDataSource.addMission(mission);
}
}
/**
* runs another mission in queue if possible
* @return true if exits pending missions running or a mission was started, otherwise, false
*/
boolean runAnotherMission() {
synchronized (this) {
if (mMissionsPending.size() < 1) return false; if (mMissionsPending.size() < 1) return false;
i = getRunningMissionsCount(); int i = getRunningMissionsCount();
if (i > 0) return true; if (i > 0) return true;
// before returning, check the queue
if (!canDownloadInCurrentNetwork()) return false; if (!canDownloadInCurrentNetwork()) return false;
for (DownloadMission mission1 : mMissionsPending) { for (DownloadMission mission : mMissionsPending) {
if (!mission1.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && mission1.enqueued) { if (!mission.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && mission.enqueued) {
resumeMission(mMissionsPending.get(i)); resumeMission(mMissionsPending.get(i));
return true; return true;
} }
@ -481,6 +488,12 @@ public class DownloadManager {
if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED); if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
} }
void updateMaximumAttempts(int maxRetry) {
synchronized (this) {
for (DownloadMission mission : mMissionsPending) mission.maxRetry = maxRetry;
}
}
/** /**
* Fast check for pending downloads. If exists, the user will be notified * Fast check for pending downloads. If exists, the user will be notified
* TODO: call this method in somewhere * TODO: call this method in somewhere

View file

@ -11,6 +11,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
@ -22,6 +23,8 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.content.PermissionChecker; import android.support.v4.content.PermissionChecker;
import android.util.Log; import android.util.Log;
@ -29,6 +32,7 @@ import android.widget.Toast;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.player.helper.LockManager;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -61,6 +65,7 @@ public class DownloadManagerService extends Service {
private static final String EXTRA_POSTPROCESSING_NAME = "DownloadManagerService.extra.postprocessingName"; private static final String EXTRA_POSTPROCESSING_NAME = "DownloadManagerService.extra.postprocessingName";
private static final String EXTRA_POSTPROCESSING_ARGS = "DownloadManagerService.extra.postprocessingArgs"; private static final String EXTRA_POSTPROCESSING_ARGS = "DownloadManagerService.extra.postprocessingArgs";
private static final String EXTRA_SOURCE = "DownloadManagerService.extra.source"; private static final String EXTRA_SOURCE = "DownloadManagerService.extra.source";
private static final String EXTRA_NEAR_LENGTH = "DownloadManagerService.extra.nearLength";
private static final String ACTION_RESET_DOWNLOAD_COUNT = APPLICATION_ID + ".reset_download_count"; private static final String ACTION_RESET_DOWNLOAD_COUNT = APPLICATION_ID + ".reset_download_count";
@ -78,6 +83,17 @@ public class DownloadManagerService extends Service {
private BroadcastReceiver mNetworkStateListener; private BroadcastReceiver mNetworkStateListener;
private SharedPreferences mPrefs = null;
private final SharedPreferences.OnSharedPreferenceChangeListener mPrefChangeListener = this::handlePreferenceChange;
private boolean wakeLockAcquired = false;
private LockManager wakeLock = null;
private int downloadFailedNotificationID = DOWNLOADS_NOTIFICATION_ID + 1;
private Bitmap icLauncher;
private Bitmap icDownloadDone;
/** /**
* notify media scanner on downloaded media file ... * notify media scanner on downloaded media file ...
* *
@ -112,12 +128,12 @@ public class DownloadManagerService extends Service {
openDownloadListIntent, openDownloadListIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
Bitmap iconBitmap = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher); icLauncher = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher);
Builder builder = new Builder(this, getString(R.string.notification_channel_id)) Builder builder = new Builder(this, getString(R.string.notification_channel_id))
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.stat_sys_download) .setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(iconBitmap) .setLargeIcon(icLauncher)
.setContentTitle(getString(R.string.msg_running)) .setContentTitle(getString(R.string.msg_running))
.setContentText(getString(R.string.msg_running_detail)); .setContentText(getString(R.string.msg_running_detail));
@ -135,6 +151,11 @@ public class DownloadManagerService extends Service {
} }
}; };
registerReceiver(mNetworkStateListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); registerReceiver(mNetworkStateListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs.registerOnSharedPreferenceChangeListener(mPrefChangeListener);
wakeLock = new LockManager(this);
} }
@Override @Override
@ -158,8 +179,9 @@ public class DownloadManagerService extends Service {
String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME); String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME);
String[] psArgs = intent.getStringArrayExtra(EXTRA_POSTPROCESSING_ARGS); String[] psArgs = intent.getStringArrayExtra(EXTRA_POSTPROCESSING_ARGS);
String source = intent.getStringExtra(EXTRA_SOURCE); String source = intent.getStringExtra(EXTRA_SOURCE);
long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0);
mHandler.post(() -> mManager.startMission(urls, location, name, kind, threads, source, psName, psArgs)); mHandler.post(() -> mManager.startMission(urls, location, name, kind, threads, source, psName, psArgs, nearLength));
} else if (downloadDoneNotification != null && action.equals(ACTION_RESET_DOWNLOAD_COUNT)) { } else if (downloadDoneNotification != null && action.equals(ACTION_RESET_DOWNLOAD_COUNT)) {
downloadDoneCount = 0; downloadDoneCount = 0;
@ -184,10 +206,15 @@ public class DownloadManagerService extends Service {
notificationManager.notify(DOWNLOADS_NOTIFICATION_ID, downloadDoneNotification.build()); notificationManager.notify(DOWNLOADS_NOTIFICATION_ID, downloadDoneNotification.build());
} }
unregisterReceiver(mNetworkStateListener);
mManager.pauseAllMissions(); mManager.pauseAllMissions();
if (wakeLockAcquired) wakeLock.releaseWifiAndCpu();
unregisterReceiver(mNetworkStateListener);
mPrefs.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener);
icDownloadDone.recycle();
icLauncher.recycle();
} }
@Override @Override
@ -209,19 +236,24 @@ public class DownloadManagerService extends Service {
} }
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
DownloadMission mission = (DownloadMission) msg.obj;
switch (msg.what) { switch (msg.what) {
case MESSAGE_FINISHED: case MESSAGE_FINISHED:
DownloadMission mission = (DownloadMission) msg.obj;
notifyMediaScanner(mission.getDownloadedFile()); notifyMediaScanner(mission.getDownloadedFile());
notifyFinishedDownload(mission.name); notifyFinishedDownload(mission.name);
updateForegroundState(mManager.setFinished(mission)); mManager.setFinished(mission);
updateForegroundState(mManager.runAnotherMission());
break; break;
case MESSAGE_RUNNING: case MESSAGE_RUNNING:
case MESSAGE_PROGRESS: case MESSAGE_PROGRESS:
updateForegroundState(true); updateForegroundState(true);
break; break;
case MESSAGE_PAUSED:
case MESSAGE_ERROR: case MESSAGE_ERROR:
notifyFailedDownload(mission.name);
updateForegroundState(mManager.runAnotherMission());
break;
case MESSAGE_PAUSED:
updateForegroundState(mManager.getRunningMissionsCount() > 0); updateForegroundState(mManager.getRunningMissionsCount() > 0);
break; break;
} }
@ -272,21 +304,28 @@ public class DownloadManagerService extends Service {
mManager.handleConnectivityChange(status); mManager.handleConnectivityChange(status);
} }
private void handlePreferenceChange(SharedPreferences prefs, String key) {
if (key.equals(getString(R.string.downloads_max_retry))) {
mManager.updateMaximumAttempts(prefs.getInt(key, 3));
}
}
public void updateForegroundState(boolean state) { public void updateForegroundState(boolean state) {
if (state == mForeground) return; if (state == mForeground) return;
if (state) { if (state) {
startForeground(FOREGROUND_NOTIFICATION_ID, mNotification); startForeground(FOREGROUND_NOTIFICATION_ID, mNotification);
if (!wakeLockAcquired) wakeLock.acquireWifiAndCpu();
} else { } else {
stopForeground(true); stopForeground(true);
if (wakeLockAcquired) wakeLock.releaseWifiAndCpu();
} }
mForeground = state; mForeground = state;
} }
public static void startMission(Context context, String urls[], String location, String name, public static void startMission(Context context, String urls[], String location, String name, char kind,
char kind, int threads, String source, String postprocessingName, int threads, String source, String psName, String[] psArgs, long nearLength) {
String[] postprocessingArgs) {
Intent intent = new Intent(context, DownloadManagerService.class); Intent intent = new Intent(context, DownloadManagerService.class);
intent.setAction(Intent.ACTION_RUN); intent.setAction(Intent.ACTION_RUN);
intent.putExtra(EXTRA_URLS, urls); intent.putExtra(EXTRA_URLS, urls);
@ -295,8 +334,9 @@ public class DownloadManagerService extends Service {
intent.putExtra(EXTRA_KIND, kind); intent.putExtra(EXTRA_KIND, kind);
intent.putExtra(EXTRA_THREADS, threads); intent.putExtra(EXTRA_THREADS, threads);
intent.putExtra(EXTRA_SOURCE, source); intent.putExtra(EXTRA_SOURCE, source);
intent.putExtra(EXTRA_POSTPROCESSING_NAME, postprocessingName); intent.putExtra(EXTRA_POSTPROCESSING_NAME, psName);
intent.putExtra(EXTRA_POSTPROCESSING_ARGS, postprocessingArgs); intent.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs);
intent.putExtra(EXTRA_NEAR_LENGTH, nearLength);
context.startService(intent); context.startService(intent);
} }
@ -330,16 +370,19 @@ public class DownloadManagerService extends Service {
if (downloadDoneNotification == null) { if (downloadDoneNotification == null) {
downloadDoneList = new StringBuilder(name.length()); downloadDoneList = new StringBuilder(name.length());
Bitmap icon = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done); icDownloadDone = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done);
downloadDoneNotification = new Builder(this, getString(R.string.notification_channel_id)) downloadDoneNotification = new Builder(this, getString(R.string.notification_channel_id))
.setAutoCancel(true) .setAutoCancel(true)
.setLargeIcon(icon) .setLargeIcon(icDownloadDone)
.setSmallIcon(android.R.drawable.stat_sys_download_done) .setSmallIcon(android.R.drawable.stat_sys_download_done)
.setDeleteIntent(PendingIntent.getService(this, (int) System.currentTimeMillis(), .setDeleteIntent(PendingIntent.getService(this, (int) System.currentTimeMillis(),
new Intent(this, DownloadManagerService.class) new Intent(this, DownloadManagerService.class)
.setAction(ACTION_RESET_DOWNLOAD_COUNT) .setAction(ACTION_RESET_DOWNLOAD_COUNT)
, PendingIntent.FLAG_UPDATE_CURRENT)) , PendingIntent.FLAG_UPDATE_CURRENT))
.setContentIntent(mNotification.contentIntent); .setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis() + 1,
new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN),
PendingIntent.FLAG_UPDATE_CURRENT));
} }
if (downloadDoneCount < 1) { if (downloadDoneCount < 1) {
@ -347,23 +390,57 @@ public class DownloadManagerService extends Service {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
downloadDoneNotification.setContentTitle(getString(R.string.app_name)); downloadDoneNotification.setContentTitle(getString(R.string.app_name));
downloadDoneNotification.setContentText(getString(R.string.download_finished, name));
} else { } else {
downloadDoneNotification.setContentTitle(getString(R.string.download_finished, name)); downloadDoneNotification.setContentTitle(null);
downloadDoneNotification.setContentText(null);
} }
downloadDoneNotification.setContentText(getString(R.string.download_finished));
downloadDoneNotification.setStyle(new NotificationCompat.BigTextStyle()
.setBigContentTitle(getString(R.string.download_finished))
.bigText(name)
);
} else { } else {
downloadDoneList.append(", "); downloadDoneList.append('\n');
downloadDoneList.append(name); downloadDoneList.append(name);
downloadDoneNotification.setStyle(new NotificationCompat.BigTextStyle().bigText(downloadDoneList));
downloadDoneNotification.setContentTitle(getString(R.string.download_finished_more, String.valueOf(downloadDoneCount + 1))); downloadDoneNotification.setContentTitle(getString(R.string.download_finished_more, String.valueOf(downloadDoneCount + 1)));
downloadDoneNotification.setContentText(downloadDoneList.toString()); downloadDoneNotification.setContentText(downloadDoneList);
} }
notificationManager.notify(DOWNLOADS_NOTIFICATION_ID, downloadDoneNotification.build()); notificationManager.notify(DOWNLOADS_NOTIFICATION_ID, downloadDoneNotification.build());
downloadDoneCount++; downloadDoneCount++;
} }
public void notifyFailedDownload(String name) {
if (icDownloadDone == null) {
// TODO: use a proper icon for failed downloads
icDownloadDone = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done);
}
Builder notification = new Builder(this, getString(R.string.notification_channel_id))
.setAutoCancel(true)
.setLargeIcon(icDownloadDone)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis() + 1,
new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN),
PendingIntent.FLAG_UPDATE_CURRENT));
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
notification.setContentTitle(getString(R.string.app_name));
notification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(getString(R.string.download_failed).concat(": ").concat(name)));
} else {
notification.setContentTitle(getString(R.string.download_failed));
notification.setContentText(name);
notification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(name));
}
notificationManager.notify(downloadFailedNotificationID++, notification.build());
}
private void manageObservers(Handler handler, boolean add) { private void manageObservers(Handler handler, boolean add) {
synchronized (mEchoObservers) { synchronized (mEchoObservers) {
if (add) { if (add) {

View file

@ -142,7 +142,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
str = R.string.missions_header_pending; str = R.string.missions_header_pending;
} else { } else {
str = R.string.missions_header_finished; str = R.string.missions_header_finished;
mClear.setVisible(true); setClearButtonVisibility(true);
} }
((ViewHolderHeader) view).header.setText(str); ((ViewHolderHeader) view).header.setText(str);
@ -233,8 +233,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
} }
} }
long length = mission.offsets[mission.current < mission.offsets.length ? mission.current : (mission.offsets.length - 1)]; long length = mission.getLength();
length += mission.length;
int state = 0; int state = 0;
if (!mission.isFinished()) { if (!mission.isFinished()) {
@ -390,7 +389,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
str.append(mContext.getString(R.string.error_connect_host)); str.append(mContext.getString(R.string.error_connect_host));
break; break;
case DownloadMission.ERROR_POSTPROCESSING_FAILED: case DownloadMission.ERROR_POSTPROCESSING_FAILED:
str.append(R.string.error_postprocessing_failed); str.append(mContext.getString(R.string.error_postprocessing_failed));
case DownloadMission.ERROR_UNKNOWN_EXCEPTION: case DownloadMission.ERROR_UNKNOWN_EXCEPTION:
break; break;
default: default:
@ -418,7 +417,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
public void clearFinishedDownloads() { public void clearFinishedDownloads() {
mDownloadManager.forgetFinishedDownloads(); mDownloadManager.forgetFinishedDownloads();
applyChanges(); applyChanges();
mClear.setVisible(false); setClearButtonVisibility(false);
} }
private boolean handlePopupItem(@NonNull ViewHolderItem h, @NonNull MenuItem option) { private boolean handlePopupItem(@NonNull ViewHolderItem h, @NonNull MenuItem option) {
@ -429,7 +428,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
switch (id) { switch (id) {
case R.id.start: case R.id.start:
h.state = -1; h.state = -1;
h.size.setText(Utility.formatBytes(mission.length)); h.size.setText(Utility.formatBytes(mission.getLength()));
mDownloadManager.resumeMission(mission); mDownloadManager.resumeMission(mission);
return true; return true;
case R.id.pause: case R.id.pause:
@ -470,7 +469,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
mContext.startActivity(intent);*/ mContext.startActivity(intent);*/
try { try {
Intent intent = NavigationHelper.getIntentByLink(mContext, h.item.mission.source); Intent intent = NavigationHelper.getIntentByLink(mContext, h.item.mission.source);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
mContext.startActivity(intent); mContext.startActivity(intent);
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Selected item has a invalid source", e); Log.w(TAG, "Selected item has a invalid source", e);
@ -490,7 +489,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
if (mIterator.getOldListSize() > 0) { if (mIterator.getOldListSize() > 0) {
int lastItemType = mIterator.getSpecialAtItem(mIterator.getOldListSize() - 1); int lastItemType = mIterator.getSpecialAtItem(mIterator.getOldListSize() - 1);
mClear.setVisible(lastItemType == DownloadManager.SPECIAL_FINISHED); setClearButtonVisibility(lastItemType == DownloadManager.SPECIAL_FINISHED);
} }
} }
@ -498,6 +497,10 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
mIterator.start(); mIterator.start();
mIterator.end(); mIterator.end();
for (ViewHolderItem item: mPendingDownloadsItems) {
item.lastTimeStamp = -1;
}
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -505,6 +508,18 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item; mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
} }
public void setClearButton(MenuItem clearButton) {
if (mClear == null) {
int lastItemType = mIterator.getSpecialAtItem(mIterator.getOldListSize() - 1);
clearButton.setVisible(lastItemType == DownloadManager.SPECIAL_FINISHED);
}
mClear = clearButton;
}
private void setClearButtonVisibility(boolean flag) {
mClear.setVisible(flag);
}
private void checkEmptyMessageVisibility() { private void checkEmptyMessageVisibility() {
int flag = mIterator.getOldListSize() > 0 ? View.GONE : View.VISIBLE; int flag = mIterator.getOldListSize() > 0 ? View.GONE : View.VISIBLE;
if (mEmptyMessage.getVisibility() != flag) mEmptyMessage.setVisibility(flag); if (mEmptyMessage.getVisibility() != flag) mEmptyMessage.setVisibility(flag);
@ -577,7 +592,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
checksum = menu.findItem(R.id.checksum); checksum = menu.findItem(R.id.checksum);
itemView.setOnClickListener((v) -> { itemView.setOnClickListener((v) -> {
if(((DownloadMission)item.mission).isFinished()) if (((DownloadMission) item.mission).isFinished())
viewWithFileProvider(item.mission.getDownloadedFile()); viewWithFileProvider(item.mission.getDownloadedFile());
}); });
@ -607,9 +622,9 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
queue.setChecked(mission.enqueued); queue.setChecked(mission.enqueued);
start.setVisible(mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED);
delete.setVisible(true); delete.setVisible(true);
queue.setVisible(true); start.setVisible(mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED);
queue.setVisible(mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED);
} }
} }
} else { } else {

View file

@ -33,7 +33,7 @@ public class MissionsFragment extends Fragment {
private SharedPreferences mPrefs; private SharedPreferences mPrefs;
private boolean mLinear; private boolean mLinear;
private MenuItem mSwitch; private MenuItem mSwitch;
private MenuItem mClear; private MenuItem mClear = null;
private RecyclerView mList; private RecyclerView mList;
private View mEmpty; private View mEmpty;
@ -152,6 +152,7 @@ public class MissionsFragment extends Fragment {
public void onPrepareOptionsMenu(Menu menu) { public void onPrepareOptionsMenu(Menu menu) {
mSwitch = menu.findItem(R.id.switch_mode); mSwitch = menu.findItem(R.id.switch_mode);
mClear = menu.findItem(R.id.clear_list); mClear = menu.findItem(R.id.clear_list);
if (mAdapter != null) mAdapter.setClearButton(mClear);
super.onPrepareOptionsMenu(menu); super.onPrepareOptionsMenu(menu);
} }

View file

@ -56,6 +56,7 @@
android:layout_toRightOf="@id/item_size" android:layout_toRightOf="@id/item_size"
android:padding="6dp" android:padding="6dp"
android:singleLine="true" android:singleLine="true"
android:textStyle="bold"
android:text="0%" android:text="0%"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="12sp" /> android:textSize="12sp" />

View file

@ -2,7 +2,6 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="30dp" android:layout_height="30dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
@ -18,9 +17,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textColor="@color/drawer_header_font_color" android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" /> android:textStyle="bold"
android:text="relative header"/>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -514,8 +514,9 @@ abrir en modo popup</string>
<string name="file_deleted">Archivo borrado</string> <string name="file_deleted">Archivo borrado</string>
<!-- download done notifications --> <!-- download notifications -->
<string name="download_finished">Descarga finalizada: %s</string> <string name="download_failed">Descarga fallida</string>
<string name="download_finished">Descarga finalizada</string>
<string name="download_finished_more">%s descargas finalizadas</string> <string name="download_finished_more">%s descargas finalizadas</string>
<!-- dialog about existing downloads --> <!-- dialog about existing downloads -->

View file

@ -539,8 +539,9 @@
<string name="permission_denied">Action denied by the system</string> <string name="permission_denied">Action denied by the system</string>
<!-- download done notifications --> <!-- download notifications -->
<string name="download_finished">Download finished: %s</string> <string name="download_failed">Download failed</string>
<string name="download_finished">Download finished</string>
<string name="download_finished_more">%s downloads finished</string> <string name="download_finished_more">%s downloads finished</string>
<!-- dialog about existing downloads --> <!-- dialog about existing downloads -->