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:
parent
fef9d541ed
commit
d647555e3a
19 changed files with 400 additions and 150 deletions
|
@ -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,39 +485,23 @@ 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) {
|
||||||
|
@ -498,17 +509,17 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
Loading…
Reference in a new issue