Merge branch 'fix_download' into dev
|
@ -119,7 +119,6 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ReCaptchaActivity"
|
android:name=".ReCaptchaActivity"
|
||||||
android:label="@string/reCaptchaActivity"/>
|
android:label="@string/reCaptchaActivity"/>
|
||||||
<activity android:name=".download.ExtSDDownloadFailedActivity" />
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
|
|
@ -457,7 +457,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
break;
|
break;
|
||||||
case R.id.subtitle_button:
|
case R.id.subtitle_button:
|
||||||
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||||
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video go together
|
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video files go together
|
||||||
kind = 's';
|
kind = 's';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -477,7 +477,6 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
final String finalFileName = fileName;
|
final String finalFileName = fileName;
|
||||||
|
|
||||||
DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> {
|
DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> {
|
||||||
// should be safe run the following code without "getActivity().runOnUiThread()"
|
|
||||||
if (listed) {
|
if (listed) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
builder.setTitle(R.string.download_dialog_title)
|
builder.setTitle(R.string.download_dialog_title)
|
||||||
|
@ -511,11 +510,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
|
||||||
|
|
||||||
if (secondaryStream != null) {
|
if (secondaryStream != null) {
|
||||||
secondaryStreamUrl = secondaryStream.getStream().getUrl();
|
secondaryStreamUrl = secondaryStream.getStream().getUrl();
|
||||||
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
|
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||||
psArgs = null;
|
psArgs = null;
|
||||||
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
|
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
|
// set nearLength, only, if both sizes are fetched or known. this probably does not work on slow networks
|
||||||
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
|
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
|
||||||
nearLength = secondaryStream.getSizeInBytes() + videoSize;
|
nearLength = secondaryStream.getSizeInBytes() + videoSize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
package org.schabi.newpipe.download;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
|
||||||
|
|
||||||
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
new AlertDialog.Builder(this)
|
|
||||||
.setTitle(R.string.download_to_sdcard_error_title)
|
|
||||||
.setMessage(R.string.download_to_sdcard_error_message)
|
|
||||||
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
|
|
||||||
NewPipeSettings.resetDownloadFolders(this);
|
|
||||||
finish();
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
|
|
||||||
dialogInterface.dismiss();
|
|
||||||
finish();
|
|
||||||
})
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -36,7 +36,6 @@ public class SecondaryStreamHelper<T extends Stream> {
|
||||||
* @return selected audio stream or null if a candidate was not found
|
* @return selected audio stream or null if a candidate was not found
|
||||||
*/
|
*/
|
||||||
public static AudioStream getAudioStreamFor(@NonNull List<AudioStream> audioStreams, @NonNull VideoStream videoStream) {
|
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()) {
|
switch (videoStream.getFormat()) {
|
||||||
case WEBM:
|
case WEBM:
|
||||||
case MPEG_4:
|
case MPEG_4:
|
||||||
|
|
|
@ -156,7 +156,6 @@ public class DownloadInitializer extends Thread {
|
||||||
|
|
||||||
if (retryCount++ > mMission.maxRetry) {
|
if (retryCount++ > mMission.maxRetry) {
|
||||||
Log.e(TAG, "initializer failed", e);
|
Log.e(TAG, "initializer failed", e);
|
||||||
mMission.running = false;
|
|
||||||
mMission.notifyError(e);
|
mMission.notifyError(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class DownloadMission extends Mission {
|
||||||
public static final int ERROR_SSL_EXCEPTION = 1004;
|
public static final int ERROR_SSL_EXCEPTION = 1004;
|
||||||
public static final int ERROR_UNKNOWN_HOST = 1005;
|
public static final int ERROR_UNKNOWN_HOST = 1005;
|
||||||
public static final int ERROR_CONNECT_HOST = 1006;
|
public static final int ERROR_CONNECT_HOST = 1006;
|
||||||
public static final int ERROR_POSTPROCESSING_FAILED = 1007;
|
public static final int ERROR_POSTPROCESSING = 1007;
|
||||||
public static final int ERROR_HTTP_NO_CONTENT = 204;
|
public static final int ERROR_HTTP_NO_CONTENT = 204;
|
||||||
public static final int ERROR_HTTP_UNSUPPORTED_RANGE = 206;
|
public static final int ERROR_HTTP_UNSUPPORTED_RANGE = 206;
|
||||||
|
|
||||||
|
@ -79,9 +79,12 @@ public class DownloadMission extends Mission {
|
||||||
public String postprocessingName;
|
public String postprocessingName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the post-processing algorithm is actually running, used to detect corrupt downloads
|
* Indicates if the post-processing state:
|
||||||
|
* 0: ready
|
||||||
|
* 1: running
|
||||||
|
* 2: completed
|
||||||
*/
|
*/
|
||||||
public boolean postprocessingRunning;
|
public int postprocessingState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate if the post-processing algorithm works on the same file
|
* Indicate if the post-processing algorithm works on the same file
|
||||||
|
@ -356,7 +359,7 @@ public class DownloadMission extends Mission {
|
||||||
finishCount++;
|
finishCount++;
|
||||||
|
|
||||||
if (finishCount == currentThreadCount) {
|
if (finishCount == currentThreadCount) {
|
||||||
if (errCode > ERROR_NOTHING) return;
|
if (errCode != ERROR_NOTHING) return;
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onFinish" + (current + 1) + "/" + urls.length);
|
Log.d(TAG, "onFinish" + (current + 1) + "/" + urls.length);
|
||||||
|
@ -382,19 +385,26 @@ public class DownloadMission extends Mission {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyPostProcessing(boolean processing) {
|
private void notifyPostProcessing(int state) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, (processing ? "enter" : "exit") + " postprocessing on " + location + File.separator + name);
|
String action;
|
||||||
|
switch (state) {
|
||||||
|
case 1:
|
||||||
|
action = "Running";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
action = "Completed";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
action = "Failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, action + " postprocessing on " + location + File.separator + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (blockState) {
|
synchronized (blockState) {
|
||||||
if (!processing) {
|
|
||||||
postprocessingName = null;
|
|
||||||
postprocessingArgs = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't return without fully write the current state
|
// don't return without fully write the current state
|
||||||
postprocessingRunning = processing;
|
postprocessingState = state;
|
||||||
Utility.writeToFile(metadata, DownloadMission.this);
|
Utility.writeToFile(metadata, DownloadMission.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -403,16 +413,30 @@ public class DownloadMission extends Mission {
|
||||||
* Start downloading with multiple threads.
|
* Start downloading with multiple threads.
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
if (running || current >= urls.length) return;
|
if (running || isFinished()) return;
|
||||||
|
|
||||||
// ensure that the previous state is completely paused.
|
// ensure that the previous state is completely paused.
|
||||||
joinForThread(init);
|
joinForThread(init);
|
||||||
for (Thread thread : threads) joinForThread(thread);
|
if (threads != null)
|
||||||
|
for (Thread thread : threads) joinForThread(thread);
|
||||||
|
|
||||||
enqueued = false;
|
enqueued = false;
|
||||||
running = true;
|
running = true;
|
||||||
errCode = ERROR_NOTHING;
|
errCode = ERROR_NOTHING;
|
||||||
|
|
||||||
|
if (current >= urls.length && postprocessingName != null) {
|
||||||
|
runAsync(1, () -> {
|
||||||
|
if (doPostprocessing()) {
|
||||||
|
running = false;
|
||||||
|
deleteThisFromFile();
|
||||||
|
|
||||||
|
notify(DownloadManagerService.MESSAGE_FINISHED);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (blocks < 0) {
|
if (blocks < 0) {
|
||||||
initializer();
|
initializer();
|
||||||
return;
|
return;
|
||||||
|
@ -420,7 +444,7 @@ public class DownloadMission extends Mission {
|
||||||
|
|
||||||
init = null;
|
init = null;
|
||||||
|
|
||||||
if (threads.length < 1) {
|
if (threads == null || threads.length < 1) {
|
||||||
threads = new Thread[currentThreadCount];
|
threads = new Thread[currentThreadCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,18 +468,18 @@ public class DownloadMission extends Mission {
|
||||||
public synchronized void pause() {
|
public synchronized void pause() {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
|
|
||||||
running = false;
|
if (isPsRunning()) {
|
||||||
recovered = true;
|
|
||||||
enqueued = false;
|
|
||||||
|
|
||||||
if (postprocessingRunning) {
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, "pause during post-processing is not applicable.");
|
Log.w(TAG, "pause during post-processing is not applicable.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (init != null && init.isAlive()) {
|
running = false;
|
||||||
|
recovered = true;
|
||||||
|
enqueued = false;
|
||||||
|
|
||||||
|
if (init != null && Thread.currentThread() != init && init.isAlive()) {
|
||||||
init.interrupt();
|
init.interrupt();
|
||||||
synchronized (blockState) {
|
synchronized (blockState) {
|
||||||
resetState();
|
resetState();
|
||||||
|
@ -532,13 +556,36 @@ public class DownloadMission extends Mission {
|
||||||
mWritingToFile = false;
|
mWritingToFile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the download if fully finished
|
||||||
|
*
|
||||||
|
* @return true, otherwise, false
|
||||||
|
*/
|
||||||
public boolean isFinished() {
|
public boolean isFinished() {
|
||||||
return current >= urls.length && postprocessingName == null;
|
return current >= urls.length && (postprocessingName == null || postprocessingState == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the download file is corrupt due a failed post-processing
|
||||||
|
*
|
||||||
|
* @return {@code true} if this mission is unrecoverable
|
||||||
|
*/
|
||||||
|
public boolean isPsFailed() {
|
||||||
|
return postprocessingName != null && errCode == DownloadMission.ERROR_POSTPROCESSING && postprocessingThis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if a post-processing algorithm is running
|
||||||
|
*
|
||||||
|
* @return true, otherwise, false
|
||||||
|
*/
|
||||||
|
public boolean isPsRunning() {
|
||||||
|
return postprocessingName != null && postprocessingState == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
long calculated;
|
long calculated;
|
||||||
if (postprocessingRunning) {
|
if (postprocessingState == 1) {
|
||||||
calculated = length;
|
calculated = length;
|
||||||
} else {
|
} else {
|
||||||
calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length;
|
calculated = offsets[current < offsets.length ? current : (offsets.length - 1)] + length;
|
||||||
|
@ -550,16 +597,19 @@ public class DownloadMission extends Mission {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doPostprocessing() {
|
private boolean doPostprocessing() {
|
||||||
if (postprocessingName == null) return true;
|
if (postprocessingName == null || postprocessingState == 2) return true;
|
||||||
|
|
||||||
|
notifyPostProcessing(1);
|
||||||
|
notifyProgress(0);
|
||||||
|
|
||||||
|
Thread.currentThread().setName("[" + TAG + "] post-processing = " + postprocessingName + " filename = " + name);
|
||||||
|
|
||||||
|
Exception exception = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
notifyPostProcessing(true);
|
Postprocessing
|
||||||
notifyProgress(0);
|
.getAlgorithm(postprocessingName, this)
|
||||||
|
.run();
|
||||||
Thread.currentThread().setName("[" + TAG + "] post-processing = " + postprocessingName + " filename = " + name);
|
|
||||||
|
|
||||||
Postprocessing algorithm = Postprocessing.getAlgorithm(postprocessingName, this);
|
|
||||||
algorithm.run();
|
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
StringBuilder args = new StringBuilder(" ");
|
StringBuilder args = new StringBuilder(" ");
|
||||||
if (postprocessingArgs != null) {
|
if (postprocessingArgs != null) {
|
||||||
|
@ -571,15 +621,21 @@ public class DownloadMission extends Mission {
|
||||||
}
|
}
|
||||||
Log.e(TAG, String.format("Post-processing failed. algorithm = %s args = [%s]", postprocessingName, args), err);
|
Log.e(TAG, String.format("Post-processing failed. algorithm = %s args = [%s]", postprocessingName, args), err);
|
||||||
|
|
||||||
notifyError(ERROR_POSTPROCESSING_FAILED, err);
|
if (errCode == ERROR_NOTHING) errCode = ERROR_POSTPROCESSING;
|
||||||
return false;
|
|
||||||
|
exception = err;
|
||||||
} finally {
|
} finally {
|
||||||
notifyPostProcessing(false);
|
notifyPostProcessing(errCode == ERROR_NOTHING ? 2 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errCode != ERROR_NOTHING) notify(DownloadManagerService.MESSAGE_ERROR);
|
if (errCode != ERROR_NOTHING) {
|
||||||
|
if (exception == null) exception = errObject;
|
||||||
|
notifyError(ERROR_POSTPROCESSING, exception);
|
||||||
|
|
||||||
return errCode == ERROR_NOTHING;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean deleteThisFromFile() {
|
private boolean deleteThisFromFile() {
|
||||||
|
|
|
@ -13,9 +13,7 @@ import us.shandian.giga.get.DownloadMission;
|
||||||
class Mp4DashMuxer extends Postprocessing {
|
class Mp4DashMuxer extends Postprocessing {
|
||||||
|
|
||||||
Mp4DashMuxer(DownloadMission mission) {
|
Mp4DashMuxer(DownloadMission mission) {
|
||||||
super(mission);
|
super(mission, 15360 * 1024/* 15 MiB */, true);
|
||||||
recommendedReserve = 15360 * 1024;// 15 MiB
|
|
||||||
worksOnSameFile = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
136
app/src/main/java/us/shandian/giga/postprocessing/Mp4Muxer.java
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package us.shandian.giga.postprocessing;
|
||||||
|
|
||||||
|
import android.media.MediaCodec.BufferInfo;
|
||||||
|
import android.media.MediaExtractor;
|
||||||
|
import android.media.MediaMuxer;
|
||||||
|
import android.media.MediaMuxer.OutputFormat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.BuildConfig.DEBUG;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import us.shandian.giga.get.DownloadMission;
|
||||||
|
|
||||||
|
|
||||||
|
class Mp4Muxer extends Postprocessing {
|
||||||
|
private static final String TAG = "Mp4Muxer";
|
||||||
|
private static final int NOTIFY_BYTES_INTERVAL = 128 * 1024;// 128 KiB
|
||||||
|
|
||||||
|
Mp4Muxer(DownloadMission mission) {
|
||||||
|
super(mission, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int process(SharpStream out, SharpStream... sources) throws IOException {
|
||||||
|
File dlFile = mission.getDownloadedFile();
|
||||||
|
File tmpFile = new File(mission.location, mission.name.concat(".tmp"));
|
||||||
|
|
||||||
|
if (tmpFile.exists())
|
||||||
|
if (!tmpFile.delete()) return DownloadMission.ERROR_FILE_CREATION;
|
||||||
|
|
||||||
|
if (!tmpFile.createNewFile()) return DownloadMission.ERROR_FILE_CREATION;
|
||||||
|
|
||||||
|
FileInputStream source = null;
|
||||||
|
MediaMuxer muxer = null;
|
||||||
|
|
||||||
|
//noinspection TryFinallyCanBeTryWithResources
|
||||||
|
try {
|
||||||
|
source = new FileInputStream(dlFile);
|
||||||
|
MediaExtractor tracks[] = {
|
||||||
|
getMediaExtractor(source, mission.offsets[0], mission.offsets[1] - mission.offsets[0]),
|
||||||
|
getMediaExtractor(source, mission.offsets[1], mission.length - mission.offsets[1])
|
||||||
|
};
|
||||||
|
|
||||||
|
muxer = new MediaMuxer(tmpFile.getAbsolutePath(), OutputFormat.MUXER_OUTPUT_MPEG_4);
|
||||||
|
|
||||||
|
int tracksIndex[] = {
|
||||||
|
muxer.addTrack(tracks[0].getTrackFormat(0)),
|
||||||
|
muxer.addTrack(tracks[1].getTrackFormat(0))
|
||||||
|
};
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(512 * 1024);// 512 KiB
|
||||||
|
BufferInfo info = new BufferInfo();
|
||||||
|
|
||||||
|
long written = 0;
|
||||||
|
long nextReport = NOTIFY_BYTES_INTERVAL;
|
||||||
|
|
||||||
|
muxer.start();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int done = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
|
if (tracksIndex[i] < 0) continue;
|
||||||
|
|
||||||
|
info.set(0,
|
||||||
|
tracks[i].readSampleData(buffer, 0),
|
||||||
|
tracks[i].getSampleTime(),
|
||||||
|
tracks[i].getSampleFlags()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (info.size >= 0) {
|
||||||
|
muxer.writeSampleData(tracksIndex[i], buffer, info);
|
||||||
|
written += info.size;
|
||||||
|
done++;
|
||||||
|
}
|
||||||
|
if (!tracks[i].advance()) {
|
||||||
|
// EOF reached
|
||||||
|
tracks[i].release();
|
||||||
|
tracksIndex[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (written > nextReport) {
|
||||||
|
nextReport = written + NOTIFY_BYTES_INTERVAL;
|
||||||
|
super.progressReport(written);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done < 1) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this part should not fail
|
||||||
|
if (!dlFile.delete()) return DownloadMission.ERROR_FILE_CREATION;
|
||||||
|
if (!tmpFile.renameTo(dlFile)) return DownloadMission.ERROR_FILE_CREATION;
|
||||||
|
|
||||||
|
return OK_RESULT;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (muxer != null) {
|
||||||
|
muxer.stop();
|
||||||
|
muxer.release();
|
||||||
|
}
|
||||||
|
} catch (Exception err) {
|
||||||
|
if (DEBUG)
|
||||||
|
Log.e(TAG, "muxer stop/release failed", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source != null) {
|
||||||
|
try {
|
||||||
|
source.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the operation fails, delete the temporal file
|
||||||
|
if (tmpFile.exists()) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
tmpFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaExtractor getMediaExtractor(FileInputStream source, long offset, long length) throws IOException {
|
||||||
|
MediaExtractor extractor = new MediaExtractor();
|
||||||
|
extractor.setDataSource(source.getFD(), offset, length);
|
||||||
|
extractor.selectTrack(0);
|
||||||
|
|
||||||
|
return extractor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,21 +18,21 @@ public abstract class Postprocessing {
|
||||||
|
|
||||||
public static final String ALGORITHM_TTML_CONVERTER = "ttml";
|
public static final String ALGORITHM_TTML_CONVERTER = "ttml";
|
||||||
public static final String ALGORITHM_MP4_DASH_MUXER = "mp4D";
|
public static final String ALGORITHM_MP4_DASH_MUXER = "mp4D";
|
||||||
|
public static final String ALGORITHM_MP4_MUXER = "mp4";
|
||||||
public static final String ALGORITHM_WEBM_MUXER = "webm";
|
public static final String ALGORITHM_WEBM_MUXER = "webm";
|
||||||
private static final String ALGORITHM_TEST_ALGO = "test";
|
|
||||||
|
|
||||||
public static Postprocessing getAlgorithm(String algorithmName, DownloadMission mission) {
|
public static Postprocessing getAlgorithm(String algorithmName, DownloadMission mission) {
|
||||||
if (null == algorithmName) {
|
if (null == algorithmName) {
|
||||||
throw new NullPointerException("algorithmName");
|
throw new NullPointerException("algorithmName");
|
||||||
} else switch (algorithmName) {
|
} else switch (algorithmName) {
|
||||||
case ALGORITHM_TTML_CONVERTER:
|
case ALGORITHM_TTML_CONVERTER:
|
||||||
return new TttmlConverter(mission);
|
return new TtmlConverter(mission);
|
||||||
case ALGORITHM_MP4_DASH_MUXER:
|
case ALGORITHM_MP4_DASH_MUXER:
|
||||||
return new Mp4DashMuxer(mission);
|
return new Mp4DashMuxer(mission);
|
||||||
|
case ALGORITHM_MP4_MUXER:
|
||||||
|
return new Mp4Muxer(mission);
|
||||||
case ALGORITHM_WEBM_MUXER:
|
case ALGORITHM_WEBM_MUXER:
|
||||||
return new WebMMuxer(mission);
|
return new WebMMuxer(mission);
|
||||||
case ALGORITHM_TEST_ALGO:
|
|
||||||
return new TestAlgo(mission);
|
|
||||||
/*case "example-algorithm":
|
/*case "example-algorithm":
|
||||||
return new ExampleAlgorithm(mission);*/
|
return new ExampleAlgorithm(mission);*/
|
||||||
default:
|
default:
|
||||||
|
@ -52,71 +52,84 @@ public abstract class Postprocessing {
|
||||||
*/
|
*/
|
||||||
public int recommendedReserve;
|
public int recommendedReserve;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the download to post-process
|
||||||
|
*/
|
||||||
protected DownloadMission mission;
|
protected DownloadMission mission;
|
||||||
|
|
||||||
Postprocessing(DownloadMission mission) {
|
Postprocessing(DownloadMission mission, int recommendedReserve, boolean worksOnSameFile) {
|
||||||
this.mission = mission;
|
this.mission = mission;
|
||||||
|
this.recommendedReserve = recommendedReserve;
|
||||||
|
this.worksOnSameFile = worksOnSameFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() throws IOException {
|
public void run() throws IOException {
|
||||||
File file = mission.getDownloadedFile();
|
File file = mission.getDownloadedFile();
|
||||||
CircularFile out = null;
|
CircularFile out = null;
|
||||||
ChunkFileInputStream[] sources = new ChunkFileInputStream[mission.urls.length];
|
int result;
|
||||||
|
long finalLength = -1;
|
||||||
|
|
||||||
try {
|
mission.done = 0;
|
||||||
int i = 0;
|
mission.length = file.length();
|
||||||
for (; i < sources.length - 1; i++) {
|
|
||||||
sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.offsets[i + 1], "rw");
|
|
||||||
}
|
|
||||||
sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.getDownloadedFile().length(), "rw");
|
|
||||||
|
|
||||||
int[] idx = {0};
|
if (worksOnSameFile) {
|
||||||
CircularFile.OffsetChecker checker = () -> {
|
ChunkFileInputStream[] sources = new ChunkFileInputStream[mission.urls.length];
|
||||||
while (idx[0] < sources.length) {
|
try {
|
||||||
/*
|
int i = 0;
|
||||||
* WARNING: never use rewind() in any chunk after any writing (especially on first chunks)
|
for (; i < sources.length - 1; i++) {
|
||||||
* or the CircularFile can lead to unexpected results
|
sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.offsets[i + 1], "rw");
|
||||||
*/
|
}
|
||||||
if (sources[idx[0]].isDisposed() || sources[idx[0]].available() < 1) {
|
sources[i] = new ChunkFileInputStream(file, mission.offsets[i], mission.getDownloadedFile().length(), "rw");
|
||||||
idx[0]++;
|
|
||||||
continue;// the selected source is not used anymore
|
int[] idx = {0};
|
||||||
|
CircularFile.OffsetChecker checker = () -> {
|
||||||
|
while (idx[0] < sources.length) {
|
||||||
|
/*
|
||||||
|
* WARNING: never use rewind() in any chunk after any writing (especially on first chunks)
|
||||||
|
* or the CircularFile can lead to unexpected results
|
||||||
|
*/
|
||||||
|
if (sources[idx[0]].isDisposed() || sources[idx[0]].available() < 1) {
|
||||||
|
idx[0]++;
|
||||||
|
continue;// the selected source is not used anymore
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources[idx[0]].getFilePointer() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sources[idx[0]].getFilePointer() - 1;
|
return -1;
|
||||||
|
};
|
||||||
|
out = new CircularFile(file, 0, this::progressReport, checker);
|
||||||
|
|
||||||
|
result = process(out, sources);
|
||||||
|
|
||||||
|
if (result == OK_RESULT)
|
||||||
|
finalLength = out.finalizeFile();
|
||||||
|
} finally {
|
||||||
|
for (SharpStream source : sources) {
|
||||||
|
if (source != null && !source.isDisposed()) {
|
||||||
|
source.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (out != null) {
|
||||||
return -1;
|
out.dispose();
|
||||||
};
|
|
||||||
|
|
||||||
out = new CircularFile(file, 0, this::progressReport, checker);
|
|
||||||
|
|
||||||
mission.done = 0;
|
|
||||||
mission.length = file.length();
|
|
||||||
|
|
||||||
int result = process(out, sources);
|
|
||||||
|
|
||||||
if (result == OK_RESULT) {
|
|
||||||
long finalLength = out.finalizeFile();
|
|
||||||
mission.done = finalLength;
|
|
||||||
mission.length = finalLength;
|
|
||||||
} else {
|
|
||||||
mission.errCode = DownloadMission.ERROR_UNKNOWN_EXCEPTION;
|
|
||||||
mission.errObject = new RuntimeException("post-processing algorithm returned " + result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != OK_RESULT && worksOnSameFile) {
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
new File(mission.location, mission.name).delete();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
for (SharpStream source : sources) {
|
|
||||||
if (source != null && !source.isDisposed()) {
|
|
||||||
source.dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (out != null) {
|
} else {
|
||||||
out.dispose();
|
result = process(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result == OK_RESULT) {
|
||||||
|
if (finalLength < 0) finalLength = file.length();
|
||||||
|
mission.done = finalLength;
|
||||||
|
mission.length = finalLength;
|
||||||
|
} else {
|
||||||
|
mission.errCode = DownloadMission.ERROR_UNKNOWN_EXCEPTION;
|
||||||
|
mission.errObject = new RuntimeException("post-processing algorithm returned " + result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != OK_RESULT && worksOnSameFile) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
file.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +151,7 @@ public abstract class Postprocessing {
|
||||||
return mission.postprocessingArgs[index];
|
return mission.postprocessingArgs[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void progressReport(long done) {
|
void progressReport(long done) {
|
||||||
mission.done = done;
|
mission.done = done;
|
||||||
if (mission.length < mission.done) mission.length = mission.done;
|
if (mission.length < mission.done) mission.length = mission.done;
|
||||||
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
package us.shandian.giga.postprocessing;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import us.shandian.giga.get.DownloadMission;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Algorithm for testing proposes
|
|
||||||
*/
|
|
||||||
class TestAlgo extends Postprocessing {
|
|
||||||
|
|
||||||
public TestAlgo(DownloadMission mission) {
|
|
||||||
super(mission);
|
|
||||||
|
|
||||||
worksOnSameFile = true;
|
|
||||||
recommendedReserve = 4096 * 1024;// 4 KiB
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int process(SharpStream out, SharpStream... sources) throws IOException {
|
|
||||||
|
|
||||||
int written = 0;
|
|
||||||
int size = 5 * 1024 * 1024;// 5 MiB
|
|
||||||
byte[] buffer = new byte[8 * 1024];//8 KiB
|
|
||||||
mission.length = size;
|
|
||||||
|
|
||||||
Random rnd = new Random();
|
|
||||||
|
|
||||||
// only write random data
|
|
||||||
sources[0].dispose();
|
|
||||||
|
|
||||||
while (written < size) {
|
|
||||||
rnd.nextBytes(buffer);
|
|
||||||
|
|
||||||
int read = Math.min(buffer.length, size - written);
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep((int) (Math.random() * 10));
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
written += read;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Postprocessing.OK_RESULT;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,13 +18,12 @@ import us.shandian.giga.postprocessing.io.SharpInputStream;
|
||||||
/**
|
/**
|
||||||
* @author kapodamy
|
* @author kapodamy
|
||||||
*/
|
*/
|
||||||
class TttmlConverter extends Postprocessing {
|
class TtmlConverter extends Postprocessing {
|
||||||
private static final String TAG = "TttmlConverter";
|
private static final String TAG = "TtmlConverter";
|
||||||
|
|
||||||
TttmlConverter(DownloadMission mission) {
|
TtmlConverter(DownloadMission mission) {
|
||||||
super(mission);
|
// due how XmlPullParser works, the xml is fully loaded on the ram
|
||||||
recommendedReserve = 0;// due how XmlPullParser works, the xml is fully loaded on the ram
|
super(mission, 0, true);
|
||||||
worksOnSameFile = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -41,7 +40,7 @@ class TttmlConverter extends Postprocessing {
|
||||||
out,
|
out,
|
||||||
getArgumentAt(1, "true").equals("true"),
|
getArgumentAt(1, "true").equals("true"),
|
||||||
getArgumentAt(2, "true").equals("true")
|
getArgumentAt(2, "true").equals("true")
|
||||||
);
|
);
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
Log.e(TAG, "subtitle parse failed", err);
|
Log.e(TAG, "subtitle parse failed", err);
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ class TttmlConverter extends Postprocessing {
|
||||||
} else if (err instanceof XPathExpressionException) {
|
} else if (err instanceof XPathExpressionException) {
|
||||||
return 7;
|
return 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 8;
|
return 8;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,7 @@ import us.shandian.giga.get.DownloadMission;
|
||||||
class WebMMuxer extends Postprocessing {
|
class WebMMuxer extends Postprocessing {
|
||||||
|
|
||||||
WebMMuxer(DownloadMission mission) {
|
WebMMuxer(DownloadMission mission) {
|
||||||
super(mission);
|
super(mission, 2048 * 1024/* 2 MiB */, true);
|
||||||
recommendedReserve = 2048 * 1024;// 2 MiB
|
|
||||||
worksOnSameFile = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -141,15 +141,18 @@ public class DownloadManager {
|
||||||
File dl = mis.getDownloadedFile();
|
File dl = mis.getDownloadedFile();
|
||||||
boolean exists = dl.exists();
|
boolean exists = dl.exists();
|
||||||
|
|
||||||
if (mis.postprocessingRunning && mis.postprocessingThis) {
|
if (mis.isPsRunning()) {
|
||||||
// Incomplete post-processing results in a corrupted download file
|
if (mis.postprocessingThis) {
|
||||||
// because the selected algorithm works on the same file to save space.
|
// Incomplete post-processing results in a corrupted download file
|
||||||
if (!dl.delete()) {
|
// because the selected algorithm works on the same file to save space.
|
||||||
Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath());
|
if (exists && dl.isFile() && !dl.delete())
|
||||||
|
Log.w(TAG, "Unable to delete incomplete download file: " + sub.getPath());
|
||||||
|
|
||||||
|
exists = true;
|
||||||
}
|
}
|
||||||
exists = true;
|
|
||||||
mis.postprocessingRunning = false;
|
mis.postprocessingState = 0;
|
||||||
mis.errCode = DownloadMission.ERROR_POSTPROCESSING_FAILED;
|
mis.errCode = DownloadMission.ERROR_POSTPROCESSING;
|
||||||
mis.errObject = new RuntimeException("stopped unexpectedly");
|
mis.errObject = new RuntimeException("stopped unexpectedly");
|
||||||
} else if (exists && !dl.isFile()) {
|
} else if (exists && !dl.isFile()) {
|
||||||
// probably a folder, this should never happens
|
// probably a folder, this should never happens
|
||||||
|
@ -332,7 +335,7 @@ public class DownloadManager {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
for (DownloadMission mission : mMissionsPending) {
|
for (DownloadMission mission : mMissionsPending) {
|
||||||
if (mission.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && !mission.isFinished())
|
if (mission.running && !mission.isFinished() && !mission.isPsFailed())
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,7 +474,7 @@ public class DownloadManager {
|
||||||
boolean flag = false;
|
boolean flag = false;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
for (DownloadMission mission : mMissionsPending) {
|
for (DownloadMission mission : mMissionsPending) {
|
||||||
if (mission.running && mission.isFinished() && !mission.postprocessingRunning) {
|
if (mission.running && !mission.isFinished() && !mission.isPsRunning()) {
|
||||||
flag = true;
|
flag = true;
|
||||||
mission.pause();
|
mission.pause();
|
||||||
}
|
}
|
||||||
|
@ -528,6 +531,8 @@ public class DownloadManager {
|
||||||
ArrayList<Object> current;
|
ArrayList<Object> current;
|
||||||
ArrayList<Mission> hidden;
|
ArrayList<Mission> hidden;
|
||||||
|
|
||||||
|
boolean hasFinished = false;
|
||||||
|
|
||||||
private MissionIterator() {
|
private MissionIterator() {
|
||||||
hidden = new ArrayList<>(2);
|
hidden = new ArrayList<>(2);
|
||||||
current = null;
|
current = null;
|
||||||
|
@ -563,6 +568,7 @@ public class DownloadManager {
|
||||||
list.addAll(finished);
|
list.addAll(finished);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasFinished = finished.size() > 0;
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -637,6 +643,10 @@ public class DownloadManager {
|
||||||
hidden.remove(mission);
|
hidden.remove(mission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasFinishedMissions() {
|
||||||
|
return hasFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOldListSize() {
|
public int getOldListSize() {
|
||||||
|
|
|
@ -59,7 +59,7 @@ import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_UNSUPPORTED_RANGE;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING;
|
import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION;
|
import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_PERMISSION_DENIED;
|
import static us.shandian.giga.get.DownloadMission.ERROR_PERMISSION_DENIED;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_FAILED;
|
import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION;
|
import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION;
|
import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION;
|
||||||
import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST;
|
import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST;
|
||||||
|
@ -67,7 +67,8 @@ import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST;
|
||||||
public class MissionAdapter extends Adapter<ViewHolder> {
|
public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
private static final SparseArray<String> ALGORITHMS = new SparseArray<>();
|
private static final SparseArray<String> ALGORITHMS = new SparseArray<>();
|
||||||
private static final String TAG = "MissionAdapter";
|
private static final String TAG = "MissionAdapter";
|
||||||
private static final String UNDEFINED_SPEED = "--.-%";
|
private static final String UNDEFINED_PROGRESS = "--.-%";
|
||||||
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALGORITHMS.put(R.id.md5, "MD5");
|
ALGORITHMS.put(R.id.md5, "MD5");
|
||||||
|
@ -158,7 +159,7 @@ public class MissionAdapter extends 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;
|
||||||
setClearButtonVisibility(true);
|
mClear.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
((ViewHolderHeader) view).header.setText(str);
|
((ViewHolderHeader) view).header.setText(str);
|
||||||
|
@ -178,7 +179,7 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
if (h.item.mission instanceof DownloadMission) {
|
if (h.item.mission instanceof DownloadMission) {
|
||||||
DownloadMission mission = (DownloadMission) item.mission;
|
DownloadMission mission = (DownloadMission) item.mission;
|
||||||
String length = Utility.formatBytes(mission.getLength());
|
String length = Utility.formatBytes(mission.getLength());
|
||||||
if (mission.running && !mission.postprocessingRunning) length += " --.- kB/s";
|
if (mission.running && !mission.isPsRunning()) length += " --.- kB/s";
|
||||||
|
|
||||||
h.size.setText(length);
|
h.size.setText(length);
|
||||||
h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause);
|
h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause);
|
||||||
|
@ -238,11 +239,10 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
if (Float.isNaN(progress) || Float.isInfinite(progress))
|
h.progress.setProgress(isNotFinite(progress) ? 1f : progress);
|
||||||
h.progress.setProgress(1f);
|
|
||||||
h.status.setText(R.string.msg_error);
|
h.status.setText(R.string.msg_error);
|
||||||
} else if (Float.isNaN(progress) || Float.isInfinite(progress)) {
|
} else if (isNotFinite(progress)) {
|
||||||
h.status.setText(UNDEFINED_SPEED);
|
h.status.setText(UNDEFINED_PROGRESS);
|
||||||
} else {
|
} else {
|
||||||
h.status.setText(String.format("%.2f%%", progress * 100));
|
h.status.setText(String.format("%.2f%%", progress * 100));
|
||||||
h.progress.setProgress(progress);
|
h.progress.setProgress(progress);
|
||||||
|
@ -251,11 +251,11 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
long length = mission.getLength();
|
long length = mission.getLength();
|
||||||
|
|
||||||
int state;
|
int state;
|
||||||
if (mission.errCode == ERROR_POSTPROCESSING_FAILED) {
|
if (mission.isPsFailed()) {
|
||||||
state = 0;
|
state = 0;
|
||||||
} else if (!mission.running) {
|
} else if (!mission.running) {
|
||||||
state = mission.enqueued ? 1 : 2;
|
state = mission.enqueued ? 1 : 2;
|
||||||
} else if (mission.postprocessingRunning) {
|
} else if (mission.isPsRunning()) {
|
||||||
state = 3;
|
state = 3;
|
||||||
} else {
|
} else {
|
||||||
state = 0;
|
state = 0;
|
||||||
|
@ -406,7 +406,7 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
case ERROR_CONNECT_HOST:
|
case ERROR_CONNECT_HOST:
|
||||||
str.append(mContext.getString(R.string.error_connect_host));
|
str.append(mContext.getString(R.string.error_connect_host));
|
||||||
break;
|
break;
|
||||||
case ERROR_POSTPROCESSING_FAILED:
|
case ERROR_POSTPROCESSING:
|
||||||
str.append(mContext.getString(R.string.error_postprocessing_failed));
|
str.append(mContext.getString(R.string.error_postprocessing_failed));
|
||||||
case ERROR_UNKNOWN_EXCEPTION:
|
case ERROR_UNKNOWN_EXCEPTION:
|
||||||
break;
|
break;
|
||||||
|
@ -437,7 +437,7 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
public void clearFinishedDownloads() {
|
public void clearFinishedDownloads() {
|
||||||
mDownloadManager.forgetFinishedDownloads();
|
mDownloadManager.forgetFinishedDownloads();
|
||||||
applyChanges();
|
applyChanges();
|
||||||
setClearButtonVisibility(false);
|
mClear.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handlePopupItem(@NonNull ViewHolderItem h, @NonNull MenuItem option) {
|
private boolean handlePopupItem(@NonNull ViewHolderItem h, @NonNull MenuItem option) {
|
||||||
|
@ -447,6 +447,7 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
if (mission != null) {
|
if (mission != null) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.id.start:
|
case R.id.start:
|
||||||
|
h.status.setText(UNDEFINED_PROGRESS);
|
||||||
h.state = -1;
|
h.state = -1;
|
||||||
h.size.setText(Utility.formatBytes(mission.getLength()));
|
h.size.setText(Utility.formatBytes(mission.getLength()));
|
||||||
mDownloadManager.resumeMission(mission);
|
mDownloadManager.resumeMission(mission);
|
||||||
|
@ -506,11 +507,7 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
mIterator.end();
|
mIterator.end();
|
||||||
|
|
||||||
checkEmptyMessageVisibility();
|
checkEmptyMessageVisibility();
|
||||||
|
mClear.setVisible(mIterator.hasFinishedMissions());
|
||||||
if (mIterator.getOldListSize() > 0) {
|
|
||||||
int lastItemType = mIterator.getSpecialAtItem(mIterator.getOldListSize() - 1);
|
|
||||||
setClearButtonVisibility(lastItemType == DownloadManager.SPECIAL_FINISHED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceUpdate() {
|
public void forceUpdate() {
|
||||||
|
@ -529,17 +526,10 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClearButton(MenuItem clearButton) {
|
public void setClearButton(MenuItem clearButton) {
|
||||||
if (mClear == null) {
|
if (mClear == null) clearButton.setVisible(mIterator.hasFinishedMissions());
|
||||||
int lastItemType = mIterator.getSpecialAtItem(mIterator.getOldListSize() - 1);
|
|
||||||
clearButton.setVisible(lastItemType == DownloadManager.SPECIAL_FINISHED);
|
|
||||||
}
|
|
||||||
mClear = clearButton;
|
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);
|
||||||
|
@ -596,6 +586,10 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isNotFinite(Float value) {
|
||||||
|
return Float.isNaN(value) || Float.isInfinite(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ViewHolderItem extends RecyclerView.ViewHolder {
|
class ViewHolderItem extends RecyclerView.ViewHolder {
|
||||||
DownloadManager.MissionItem item;
|
DownloadManager.MissionItem item;
|
||||||
|
@ -667,7 +661,7 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
DownloadMission mission = item.mission instanceof DownloadMission ? (DownloadMission) item.mission : null;
|
DownloadMission mission = item.mission instanceof DownloadMission ? (DownloadMission) item.mission : null;
|
||||||
|
|
||||||
if (mission != null) {
|
if (mission != null) {
|
||||||
if (!mission.postprocessingRunning) {
|
if (!mission.isPsRunning()) {
|
||||||
if (mission.running) {
|
if (mission.running) {
|
||||||
pause.setVisible(true);
|
pause.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -678,8 +672,10 @@ public class MissionAdapter extends Adapter<ViewHolder> {
|
||||||
queue.setChecked(mission.enqueued);
|
queue.setChecked(mission.enqueued);
|
||||||
|
|
||||||
delete.setVisible(true);
|
delete.setVisible(true);
|
||||||
start.setVisible(mission.errCode != ERROR_POSTPROCESSING_FAILED);
|
|
||||||
queue.setVisible(mission.errCode != ERROR_POSTPROCESSING_FAILED);
|
boolean flag = !mission.isPsFailed();
|
||||||
|
start.setVisible(flag);
|
||||||
|
queue.setVisible(flag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import us.shandian.giga.service.DownloadManager;
|
import us.shandian.giga.service.DownloadManager;
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
import us.shandian.giga.service.DownloadManagerService;
|
||||||
|
@ -40,7 +41,7 @@ public class MissionsFragment extends Fragment {
|
||||||
private MissionAdapter mAdapter;
|
private MissionAdapter mAdapter;
|
||||||
private GridLayoutManager mGridManager;
|
private GridLayoutManager mGridManager;
|
||||||
private LinearLayoutManager mLinearManager;
|
private LinearLayoutManager mLinearManager;
|
||||||
private Context mActivity;
|
private Context mContext;
|
||||||
|
|
||||||
private DMBinder mBinder;
|
private DMBinder mBinder;
|
||||||
private Bundle mBundle;
|
private Bundle mBundle;
|
||||||
|
@ -53,7 +54,7 @@ public class MissionsFragment extends Fragment {
|
||||||
mBinder = (DownloadManagerService.DMBinder) binder;
|
mBinder = (DownloadManagerService.DMBinder) binder;
|
||||||
mBinder.clearDownloadNotifications();
|
mBinder.clearDownloadNotifications();
|
||||||
|
|
||||||
mAdapter = new MissionAdapter(mActivity, mBinder.getDownloadManager(), mClear, mEmpty);
|
mAdapter = new MissionAdapter(mContext, mBinder.getDownloadManager(), mClear, mEmpty);
|
||||||
mAdapter.deleterLoad(mBundle, getView());
|
mAdapter.deleterLoad(mBundle, getView());
|
||||||
|
|
||||||
mBundle = null;
|
mBundle = null;
|
||||||
|
@ -79,17 +80,17 @@ public class MissionsFragment extends Fragment {
|
||||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
mLinear = mPrefs.getBoolean("linear", false);
|
mLinear = mPrefs.getBoolean("linear", false);
|
||||||
|
|
||||||
mActivity = getActivity();
|
//mContext = getActivity().getApplicationContext();
|
||||||
mBundle = savedInstanceState;
|
mBundle = savedInstanceState;
|
||||||
|
|
||||||
// Bind the service
|
// Bind the service
|
||||||
mActivity.bindService(new Intent(mActivity, DownloadManagerService.class), mConnection, Context.BIND_AUTO_CREATE);
|
mContext.bindService(new Intent(mContext, DownloadManagerService.class), mConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
mEmpty = v.findViewById(R.id.list_empty_view);
|
mEmpty = v.findViewById(R.id.list_empty_view);
|
||||||
mList = v.findViewById(R.id.mission_recycler);
|
mList = v.findViewById(R.id.mission_recycler);
|
||||||
|
|
||||||
// Init
|
// Init layouts managers
|
||||||
mGridManager = new GridLayoutManager(getActivity(), SPAN_SIZE);
|
mGridManager = new GridLayoutManager(getActivity(), SPAN_SIZE);
|
||||||
mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -103,7 +104,6 @@ public class MissionsFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mLinearManager = new LinearLayoutManager(getActivity());
|
mLinearManager = new LinearLayoutManager(getActivity());
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
@ -115,13 +115,13 @@ public class MissionsFragment extends Fragment {
|
||||||
* Added in API level 23.
|
* Added in API level 23.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context activity) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(activity);
|
super.onAttach(context);
|
||||||
|
|
||||||
// Bug: in api< 23 this is never called
|
// Bug: in api< 23 this is never called
|
||||||
// so mActivity=null
|
// so mActivity=null
|
||||||
// so app crashes with nullpointer exception
|
// so app crashes with null-pointer exception
|
||||||
mActivity = activity;
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,7 +132,7 @@ public class MissionsFragment extends Fragment {
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
|
|
||||||
mActivity = activity;
|
mContext = activity.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ public class MissionsFragment extends Fragment {
|
||||||
|
|
||||||
mBinder.removeMissionEventListener(mAdapter.getMessenger());
|
mBinder.removeMissionEventListener(mAdapter.getMessenger());
|
||||||
mBinder.enableNotifications(true);
|
mBinder.enableNotifications(true);
|
||||||
mActivity.unbindService(mConnection);
|
mContext.unbindService(mConnection);
|
||||||
mAdapter.deleterDispose(null);
|
mAdapter.deleterDispose(null);
|
||||||
|
|
||||||
mBinder = null;
|
mBinder = null;
|
||||||
|
@ -189,7 +189,15 @@ public class MissionsFragment extends Fragment {
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
if (mSwitch != null) {
|
if (mSwitch != null) {
|
||||||
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
|
boolean isLight = ThemeHelper.isLightThemeSelected(mContext);
|
||||||
|
int icon;
|
||||||
|
|
||||||
|
if (mLinear)
|
||||||
|
icon = isLight ? R.drawable.ic_list_black_24dp : R.drawable.ic_list_white_24dp;
|
||||||
|
else
|
||||||
|
icon = isLight ? R.drawable.ic_grid_black_24dp : R.drawable.ic_grid_white_24dp;
|
||||||
|
|
||||||
|
mSwitch.setIcon(icon);
|
||||||
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
|
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
|
||||||
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 317 B |
BIN
app/src/main/res/drawable-hdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 319 B |
BIN
app/src/main/res/drawable-hdpi/ic_grid_black_24dp.png
Normal file
After Width: | Height: | Size: 422 B |
BIN
app/src/main/res/drawable-hdpi/ic_grid_white_24dp.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
app/src/main/res/drawable-hdpi/ic_list_black_24dp.png
Normal file
After Width: | Height: | Size: 265 B |
BIN
app/src/main/res/drawable-hdpi/ic_list_white_24dp.png
Normal file
After Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 198 B |
BIN
app/src/main/res/drawable-mdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 198 B |
BIN
app/src/main/res/drawable-mdpi/ic_grid_black_24dp.png
Normal file
After Width: | Height: | Size: 270 B |
BIN
app/src/main/res/drawable-mdpi/ic_grid_white_24dp.png
Normal file
After Width: | Height: | Size: 279 B |
BIN
app/src/main/res/drawable-mdpi/ic_list_black_24dp.png
Normal file
After Width: | Height: | Size: 248 B |
BIN
app/src/main/res/drawable-mdpi/ic_list_white_24dp.png
Normal file
After Width: | Height: | Size: 254 B |
BIN
app/src/main/res/drawable-xhdpi/ic_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 270 B |
BIN
app/src/main/res/drawable-xhdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 274 B |
BIN
app/src/main/res/drawable-xhdpi/ic_grid_black_24dp.png
Normal file
After Width: | Height: | Size: 276 B |
BIN
app/src/main/res/drawable-xhdpi/ic_grid_white_24dp.png
Normal file
After Width: | Height: | Size: 288 B |
BIN
app/src/main/res/drawable-xhdpi/ic_list_black_24dp.png
Normal file
After Width: | Height: | Size: 247 B |
BIN
app/src/main/res/drawable-xhdpi/ic_list_white_24dp.png
Normal file
After Width: | Height: | Size: 249 B |
Before Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 506 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 495 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_grid_black_24dp.png
Normal file
After Width: | Height: | Size: 427 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_grid_white_24dp.png
Normal file
After Width: | Height: | Size: 419 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_list_black_24dp.png
Normal file
After Width: | Height: | Size: 292 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_list_white_24dp.png
Normal file
After Width: | Height: | Size: 295 B |
Before Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_delete_black_24dp.png
Normal file
After Width: | Height: | Size: 541 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_delete_white_24dp.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_grid_black_24dp.png
Normal file
After Width: | Height: | Size: 363 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_grid_white_24dp.png
Normal file
After Width: | Height: | Size: 382 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_list_black_24dp.png
Normal file
After Width: | Height: | Size: 330 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_list_white_24dp.png
Normal file
After Width: | Height: | Size: 320 B |
|
@ -239,7 +239,7 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:src="@drawable/list"
|
android:src="@drawable/ic_list_white_24dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
|
||||||
|
|
|
@ -237,7 +237,7 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:padding="5dp"
|
android:padding="5dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:src="@drawable/list"
|
android:src="@drawable/ic_list_white_24dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
tools:ignore="ContentDescription,RtlHardcoded"/>
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
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="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
|
|
|
@ -3,17 +3,18 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:id="@+id/switch_mode"
|
<item android:id="@+id/switch_mode"
|
||||||
android:icon="@drawable/list"
|
android:icon="?attr/ic_grid"
|
||||||
android:title="@string/grid"
|
android:title="@string/grid"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item android:id="@+id/action_settings"
|
|
||||||
app:showAsAction="never"
|
|
||||||
android:title="@string/settings"/>
|
|
||||||
|
|
||||||
<item android:id="@+id/clear_list"
|
<item android:id="@+id/clear_list"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
android:icon="@drawable/ic_delete_sweep_white_24dp"
|
android:icon="?attr/ic_delete"
|
||||||
app:showAsAction="ifRoom"
|
android:title="@string/clear_finished_download"
|
||||||
android:title="@string/clear_finished_download"/>
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item android:id="@+id/action_settings"
|
||||||
|
android:title="@string/settings"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
<attr name="ic_add" format="reference"/>
|
<attr name="ic_add" format="reference"/>
|
||||||
<attr name="ic_restore_defaults" format="reference"/>
|
<attr name="ic_restore_defaults" format="reference"/>
|
||||||
<attr name="ic_blank_page" format="reference"/>
|
<attr name="ic_blank_page" format="reference"/>
|
||||||
|
<attr name="ic_list" format="reference"/>
|
||||||
|
<attr name="ic_grid" format="reference"/>
|
||||||
|
<attr name="ic_delete" format="reference"/>
|
||||||
|
|
||||||
<!-- Can't refer to colors directly in drawable's xml-->
|
<!-- Can't refer to colors directly in drawable's xml-->
|
||||||
<attr name="toolbar_shadow_drawable" format="reference"/>
|
<attr name="toolbar_shadow_drawable" format="reference"/>
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
<item name="info">@drawable/ic_info_outline_black_24dp</item>
|
<item name="info">@drawable/ic_info_outline_black_24dp</item>
|
||||||
<item name="bug">@drawable/ic_bug_report_black_24dp</item>
|
<item name="bug">@drawable/ic_bug_report_black_24dp</item>
|
||||||
<item name="audio">@drawable/ic_headset_black_24dp</item>
|
<item name="audio">@drawable/ic_headset_black_24dp</item>
|
||||||
<item name="clear_history">@drawable/ic_delete_sweep_white_24dp</item>
|
|
||||||
<item name="download">@drawable/ic_file_download_black_24dp</item>
|
<item name="download">@drawable/ic_file_download_black_24dp</item>
|
||||||
<item name="share">@drawable/ic_share_black_24dp</item>
|
<item name="share">@drawable/ic_share_black_24dp</item>
|
||||||
<item name="cast">@drawable/ic_cast_black_24dp</item>
|
<item name="cast">@drawable/ic_cast_black_24dp</item>
|
||||||
|
@ -54,6 +53,9 @@
|
||||||
<item name="ic_add">@drawable/ic_add_black_24dp</item>
|
<item name="ic_add">@drawable/ic_add_black_24dp</item>
|
||||||
<item name="ic_restore_defaults">@drawable/ic_settings_backup_restore_black_24dp</item>
|
<item name="ic_restore_defaults">@drawable/ic_settings_backup_restore_black_24dp</item>
|
||||||
<item name="ic_blank_page">@drawable/ic_blank_page_black_24dp</item>
|
<item name="ic_blank_page">@drawable/ic_blank_page_black_24dp</item>
|
||||||
|
<item name="ic_list">@drawable/ic_list_black_24dp</item>
|
||||||
|
<item name="ic_grid">@drawable/ic_grid_black_24dp</item>
|
||||||
|
<item name="ic_delete">@drawable/ic_delete_black_24dp</item>
|
||||||
|
|
||||||
<item name="separator_color">@color/light_separator_color</item>
|
<item name="separator_color">@color/light_separator_color</item>
|
||||||
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
||||||
|
@ -82,7 +84,6 @@
|
||||||
<item name="audio">@drawable/ic_headset_white_24dp</item>
|
<item name="audio">@drawable/ic_headset_white_24dp</item>
|
||||||
<item name="info">@drawable/ic_info_outline_white_24dp</item>
|
<item name="info">@drawable/ic_info_outline_white_24dp</item>
|
||||||
<item name="bug">@drawable/ic_bug_report_white_24dp</item>
|
<item name="bug">@drawable/ic_bug_report_white_24dp</item>
|
||||||
<item name="clear_history">@drawable/ic_delete_sweep_black_24dp</item>
|
|
||||||
<item name="download">@drawable/ic_file_download_white_24dp</item>
|
<item name="download">@drawable/ic_file_download_white_24dp</item>
|
||||||
<item name="share">@drawable/ic_share_white_24dp</item>
|
<item name="share">@drawable/ic_share_white_24dp</item>
|
||||||
<item name="cast">@drawable/ic_cast_white_24dp</item>
|
<item name="cast">@drawable/ic_cast_white_24dp</item>
|
||||||
|
@ -114,6 +115,9 @@
|
||||||
<item name="ic_add">@drawable/ic_add_white_24dp</item>
|
<item name="ic_add">@drawable/ic_add_white_24dp</item>
|
||||||
<item name="ic_restore_defaults">@drawable/ic_settings_backup_restore_white_24dp</item>
|
<item name="ic_restore_defaults">@drawable/ic_settings_backup_restore_white_24dp</item>
|
||||||
<item name="ic_blank_page">@drawable/ic_blank_page_white_24dp</item>
|
<item name="ic_blank_page">@drawable/ic_blank_page_white_24dp</item>
|
||||||
|
<item name="ic_list">@drawable/ic_list_white_24dp</item>
|
||||||
|
<item name="ic_grid">@drawable/ic_grid_white_24dp</item>
|
||||||
|
<item name="ic_delete">@drawable/ic_delete_white_24dp</item>
|
||||||
|
|
||||||
<item name="separator_color">@color/dark_separator_color</item>
|
<item name="separator_color">@color/dark_separator_color</item>
|
||||||
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
||||||
|
|