1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-11-16 06:44:55 +00:00

misc improvements

* don't show notifications while download activity
* proper icon in failed download notifications
* re-write list auto-refresh (MissionAdapter.java)
* improve I/O performance (CircularFile.java)
* fix implementation of "save thread position" on multi-thread downloads
This commit is contained in:
kapodamy 2018-11-26 00:20:25 -03:00
parent f3d4d4747a
commit eba3b32708
11 changed files with 270 additions and 184 deletions

View File

@ -416,7 +416,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
// fallback // fallback
// 1st loop match country & language // 1st loop match country & language
// 2nd loop match language only // 2nd loop match language only
String lang = loc.getLanguage().substring(0, loc.getLanguage().indexOf("-")); int index = loc.getLanguage().indexOf("-");
String lang = index > 0 ? loc.getLanguage().substring(0, index) : loc.getLanguage();
for (int j = 0; j < 2; j++) { for (int j = 0; j < 2; j++) {
for (int i = 0; i < streams.size(); i++) { for (int i = 0; i < streams.size(); i++) {

View File

@ -221,12 +221,12 @@ public class DownloadMission extends Mission {
} }
/** /**
* Get position inside of the block, where thread will be resumed * Get position inside of the thread, where thread will be resumed
* *
* @param threadId the identifier of the thread * @param threadId the identifier of the thread
* @return the relative position in bytes or zero * @return the relative position in bytes or zero
*/ */
long getBlockBytePosition(int threadId) { long getThreadBytePosition(int threadId) {
return threadBytePositions.get(threadId); return threadBytePositions.get(threadId);
} }
@ -256,6 +256,8 @@ public class DownloadMission extends Mission {
} }
} }
conn.connect();
int statusCode = conn.getResponseCode(); int statusCode = conn.getResponseCode();
switch (statusCode) { switch (statusCode) {
case 204: case 204:
@ -446,6 +448,8 @@ public class DownloadMission extends Mission {
return; return;
} }
if (postprocessingRunning) return;
// wait for all threads are suspended before save the state // wait for all threads are suspended before save the state
runAsync(-1, () -> { runAsync(-1, () -> {
try { try {
@ -590,7 +594,7 @@ public class DownloadMission extends Mission {
@Override @Override
public String getMessage() { public String getMessage() {
return "Http status code" + String.valueOf(statusCode); return "Http status code: " + String.valueOf(statusCode);
} }
} }
} }

View File

@ -2,11 +2,10 @@ package us.shandian.giga.get;
import android.util.Log; import android.util.Log;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedByInterruptException;
import static org.schabi.newpipe.BuildConfig.DEBUG; import static org.schabi.newpipe.BuildConfig.DEBUG;
@ -38,8 +37,8 @@ public class DownloadRunnable implements Runnable {
Log.d(TAG, mId + ":recovered: " + mMission.recovered); Log.d(TAG, mId + ":recovered: " + mMission.recovered);
} }
BufferedInputStream ipt = null;
RandomAccessFile f; RandomAccessFile f;
InputStream is = null;
try { try {
f = new RandomAccessFile(mMission.getDownloadedFile(), "rw"); f = new RandomAccessFile(mMission.getDownloadedFile(), "rw");
@ -82,9 +81,11 @@ public class DownloadRunnable implements Runnable {
mMission.preserveBlock(blockPosition); mMission.preserveBlock(blockPosition);
mMission.setBlockPosition(mId, blockPosition); mMission.setBlockPosition(mId, blockPosition);
long start = (blockPosition * DownloadMission.BLOCK_SIZE) + mMission.getBlockBytePosition(mId); long start = blockPosition * DownloadMission.BLOCK_SIZE;
long end = start + DownloadMission.BLOCK_SIZE - 1; long end = start + DownloadMission.BLOCK_SIZE - 1;
start += mMission.getThreadBytePosition(mId);
if (end >= mMission.length) { if (end >= mMission.length) {
end = mMission.length - 1; end = mMission.length - 1;
} }
@ -107,11 +108,11 @@ public class DownloadRunnable implements Runnable {
f.seek(mMission.offsets[mMission.current] + start); f.seek(mMission.offsets[mMission.current] + start);
ipt = new BufferedInputStream(conn.getInputStream()); is = conn.getInputStream();
byte[] buf = new byte[DownloadMission.BUFFER_SIZE]; byte[] buf = new byte[DownloadMission.BUFFER_SIZE];
int len; int len;
while (start < end && mMission.running && (len = ipt.read(buf, 0, buf.length)) != -1) { while (start < end && mMission.running && (len = is.read(buf, 0, buf.length)) != -1) {
f.write(buf, 0, len); f.write(buf, 0, len);
start += len; start += len;
total += len; total += len;
@ -119,7 +120,8 @@ public class DownloadRunnable implements Runnable {
} }
if (DEBUG && mMission.running) { if (DEBUG && mMission.running) {
Log.d(TAG, mId + ":position " + blockPosition + " finished, total length " + total); Log.d(TAG, mId + ":position " + blockPosition + " finished, " + total + " bytes downloaded");
mMission.setThreadBytePosition(mId, 0L);
} }
// if the download is paused, save progress for this thread // if the download is paused, save progress for this thread
@ -132,7 +134,7 @@ public class DownloadRunnable implements Runnable {
if (e instanceof ClosedByInterruptException) break; if (e instanceof ClosedByInterruptException) break;
if (retryCount++ > mMission.maxRetry) { if (retryCount++ >= mMission.maxRetry) {
mMission.notifyError(e); mMission.notifyError(e);
break; break;
} }
@ -140,6 +142,8 @@ public class DownloadRunnable implements Runnable {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, mId + ":position " + blockPosition + " retrying due exception", e); Log.d(TAG, mId + ":position " + blockPosition + " retrying due exception", e);
} }
retry = true;
} }
} }
@ -150,7 +154,7 @@ public class DownloadRunnable implements Runnable {
} }
try { try {
if (ipt != null) ipt.close(); if (is != null) is.close();
} catch (Exception err) { } catch (Exception err) {
// nothing to do // nothing to do
} }

View File

@ -4,8 +4,8 @@ import android.annotation.SuppressLint;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedByInterruptException;
@ -24,18 +24,18 @@ public class DownloadRunnableFallback implements Runnable {
private final DownloadMission mMission; private final DownloadMission mMission;
private int retryCount = 0; private int retryCount = 0;
private BufferedInputStream ipt; private InputStream is;
private RandomAccessFile f; private RandomAccessFile f;
DownloadRunnableFallback(@NonNull DownloadMission mission) { DownloadRunnableFallback(@NonNull DownloadMission mission) {
mMission = mission; mMission = mission;
ipt = null; is = null;
f = null; f = null;
} }
private void dispose() { private void dispose() {
try { try {
if (ipt != null) ipt.close(); if (is != null) is.close();
} catch (IOException e) { } catch (IOException e) {
// nothing to do // nothing to do
} }
@ -55,7 +55,7 @@ public class DownloadRunnableFallback implements Runnable {
long start = 0; long start = 0;
if (!mMission.unknownLength) { if (!mMission.unknownLength) {
start = mMission.getBlockBytePosition(0); start = mMission.getThreadBytePosition(0);
if (DEBUG && start > 0) { if (DEBUG && start > 0) {
Log.i(TAG, "Resuming a single-thread download at " + start); Log.i(TAG, "Resuming a single-thread download at " + start);
} }
@ -72,18 +72,15 @@ public class DownloadRunnableFallback implements Runnable {
f = new RandomAccessFile(mMission.getDownloadedFile(), "rw"); f = new RandomAccessFile(mMission.getDownloadedFile(), "rw");
f.seek(mMission.offsets[mMission.current] + start); f.seek(mMission.offsets[mMission.current] + start);
ipt = new BufferedInputStream(conn.getInputStream()); is = conn.getInputStream();
byte[] buf = new byte[DownloadMission.BUFFER_SIZE]; byte[] buf = new byte[64 * 1024];
int len = 0; int len = 0;
while (mMission.running && (len = ipt.read(buf, 0, buf.length)) != -1) { while (mMission.running && (len = is.read(buf, 0, buf.length)) != -1) {
f.write(buf, 0, len); f.write(buf, 0, len);
start += len; start += len;
mMission.notifyProgress(len); mMission.notifyProgress(len);
if (Thread.interrupted()) break;
} }
// if thread goes interrupted check if the last part is written. This avoid re-download the whole file // if thread goes interrupted check if the last part is written. This avoid re-download the whole file
@ -96,7 +93,7 @@ public class DownloadRunnableFallback implements Runnable {
if (e instanceof ClosedByInterruptException) return; if (e instanceof ClosedByInterruptException) return;
if (retryCount++ > mMission.maxRetry) { if (retryCount++ >= mMission.maxRetry) {
mMission.notifyError(e); mMission.notifyError(e);
return; return;
} }

View File

@ -10,8 +10,10 @@ 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 NOTIFY_BYTES_INTERVAL = 256 * 1024;// 256 KiB private final static int AUX_BUFFER_SIZE2 = 512 * 1024;// 512 KiB
private final static int NOTIFY_BYTES_INTERVAL = 64 * 1024;// 64 KiB
private final static int QUEUE_BUFFER_SIZE = 8 * 1024;// 8 KiB private final static int QUEUE_BUFFER_SIZE = 8 * 1024;// 8 KiB
private final static boolean IMMEDIATE_AUX_BUFFER_FLUSH = false;
private RandomAccessFile out; private RandomAccessFile out;
private long position; private long position;
@ -45,7 +47,7 @@ public class CircularFile extends SharpStream {
throw err; throw err;
} }
auxiliaryBuffers = new ArrayList<>(1); auxiliaryBuffers = new ArrayList<>(15);
callback = checker; callback = checker;
startOffset = offset; startOffset = offset;
reportPosition = offset; reportPosition = offset;
@ -122,7 +124,7 @@ public class CircularFile extends SharpStream {
while (available > 0 && auxiliaryBuffers.size() > 0) { while (available > 0 && auxiliaryBuffers.size() > 0) {
ManagedBuffer aux = auxiliaryBuffers.get(0); ManagedBuffer aux = auxiliaryBuffers.get(0);
// check if there is enough space to dump the auxiliar buffer // check if there is enough space to dump the auxiliary buffer
if (available >= (aux.size + queue.size)) { if (available >= (aux.size + queue.size)) {
available -= aux.size; available -= aux.size;
writeQueue(aux.buffer, 0, aux.size); writeQueue(aux.buffer, 0, aux.size);
@ -131,26 +133,27 @@ public class CircularFile extends SharpStream {
continue; continue;
} }
// try flush contents to avoid allocate another auxiliar buffer if (IMMEDIATE_AUX_BUFFER_FLUSH) {
if (aux.available() < len && available > queue.size) { // try flush contents to avoid allocate another auxiliary buffer
int size = Math.min(len, aux.available()); if (aux.available() < len && available > queue.size) {
aux.write(b, off, size); int size = Math.min(len, aux.available());
aux.write(b, off, size);
off += size; off += size;
len -= size; len -= size;
size = Math.min(aux.size, (int) available - queue.size); size = Math.min(aux.size, (int) available - queue.size);
if (size < 1) { if (size < 1) {
break; break;
}
writeQueue(aux.buffer, 0, size);
aux.dereference(size);
available -= size;
} }
break;
writeQueue(aux.buffer, 0, size);
aux.dereference(size);
available -= size;
} }
break;
} }
if (len < 1) { if (len < 1) {
@ -174,7 +177,7 @@ 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, AUX_BUFFER_SIZE)); aux = new ManagedBuffer(Math.max(len, AUX_BUFFER_SIZE2));
auxiliaryBuffers.add(aux); auxiliaryBuffers.add(aux);
i++; i++;
} else { } else {
@ -184,10 +187,7 @@ public class CircularFile extends SharpStream {
aux.write(b, off, (int) available); aux.write(b, off, (int) available);
len -= available; len -= available;
if (len < 1) { if (len > 0) off += available;
break;
}
off += available;
} }
} }
} }
@ -361,12 +361,8 @@ public class CircularFile extends SharpStream {
if (amount > size) { if (amount > size) {
throw new IndexOutOfBoundsException("Invalid dereference amount (" + amount + ">=" + size + ")"); throw new IndexOutOfBoundsException("Invalid dereference amount (" + amount + ">=" + size + ")");
} }
size -= amount; size -= amount;
System.arraycopy(buffer, amount, buffer, 0, size);
for (int i = 0; i < size; i++) {
buffer[i] = buffer[amount + i];
}
} }
protected int available() { protected int available() {

View File

@ -277,6 +277,7 @@ public class DownloadManager {
mDownloadDataSource.deleteMission(mission); mDownloadDataSource.deleteMission(mission);
} }
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED);
mission.delete(); mission.delete();
} }
} }
@ -427,8 +428,8 @@ public class DownloadManager {
if (!canDownloadInCurrentNetwork()) return false; if (!canDownloadInCurrentNetwork()) return false;
for (DownloadMission mission : mMissionsPending) { for (DownloadMission mission : mMissionsPending) {
if (!mission.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && mission.enqueued) { if (!mission.running && mission.errCode == DownloadMission.ERROR_NOTHING && mission.enqueued) {
resumeMission(mMissionsPending.get(i)); resumeMission(mission);
return true; return true;
} }
} }

View File

@ -28,6 +28,7 @@ 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;
import android.util.SparseArray;
import android.widget.Toast; import android.widget.Toast;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -36,7 +37,6 @@ import org.schabi.newpipe.player.helper.LockManager;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManager.NetworkState; import us.shandian.giga.service.DownloadManager.NetworkState;
@ -46,13 +46,14 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerService extends Service { public class DownloadManagerService extends Service {
private static final String TAG = DownloadManagerService.class.getSimpleName(); private static final String TAG = "DownloadManagerService";
public static final int MESSAGE_RUNNING = 1; public static final int MESSAGE_RUNNING = 0;
public static final int MESSAGE_PAUSED = 2; public static final int MESSAGE_PAUSED = 1;
public static final int MESSAGE_FINISHED = 3; public static final int MESSAGE_FINISHED = 2;
public static final int MESSAGE_PROGRESS = 4; public static final int MESSAGE_PROGRESS = 3;
public static final int MESSAGE_ERROR = 5; public static final int MESSAGE_ERROR = 4;
public static final int MESSAGE_DELETED = 5;
private static final int FOREGROUND_NOTIFICATION_ID = 1000; private static final int FOREGROUND_NOTIFICATION_ID = 1000;
private static final int DOWNLOADS_NOTIFICATION_ID = 1001; private static final int DOWNLOADS_NOTIFICATION_ID = 1001;
@ -67,17 +68,20 @@ public class DownloadManagerService extends Service {
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 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_FINISHED = APPLICATION_ID + ".reset_download_finished";
private static final String ACTION_OPEN_DOWNLOADS_FINISHED = APPLICATION_ID + ".open_downloads_finished";
private DMBinder mBinder; private DMBinder mBinder;
private DownloadManager mManager; private DownloadManager mManager;
private Notification mNotification; private Notification mNotification;
private Handler mHandler; private Handler mHandler;
private boolean mForeground = false;
private NotificationManager notificationManager = null;
private boolean mDownloadNotificationEnable = true;
private int downloadDoneCount = 0; private int downloadDoneCount = 0;
private Builder downloadDoneNotification = null; private Builder downloadDoneNotification = null;
private StringBuilder downloadDoneList = null; private StringBuilder downloadDoneList = null;
NotificationManager notificationManager = null;
private boolean mForeground = false;
private final ArrayList<Handler> mEchoObservers = new ArrayList<>(1); private final ArrayList<Handler> mEchoObservers = new ArrayList<>(1);
@ -90,9 +94,14 @@ public class DownloadManagerService extends Service {
private LockManager wakeLock = null; private LockManager wakeLock = null;
private int downloadFailedNotificationID = DOWNLOADS_NOTIFICATION_ID + 1; private int downloadFailedNotificationID = DOWNLOADS_NOTIFICATION_ID + 1;
private Builder downloadFailedNotification = null;
private SparseArray<DownloadMission> mFailedDownloads = new SparseArray<>(5);
private Bitmap icLauncher; private Bitmap icLauncher;
private Bitmap icDownloadDone; private Bitmap icDownloadDone;
private Bitmap icDownloadFailed;
private PendingIntent mOpenDownloadList;
/** /**
* notify media scanner on downloaded media file ... * notify media scanner on downloaded media file ...
@ -124,14 +133,14 @@ public class DownloadManagerService extends Service {
Intent openDownloadListIntent = new Intent(this, DownloadActivity.class) Intent openDownloadListIntent = new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN); .setAction(Intent.ACTION_MAIN);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, mOpenDownloadList = PendingIntent.getActivity(this, 0,
openDownloadListIntent, openDownloadListIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
icLauncher = 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(mOpenDownloadList)
.setSmallIcon(android.R.drawable.stat_sys_download) .setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(icLauncher) .setLargeIcon(icLauncher)
.setContentTitle(getString(R.string.msg_running)) .setContentTitle(getString(R.string.msg_running))
@ -155,6 +164,9 @@ public class DownloadManagerService extends Service {
mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs.registerOnSharedPreferenceChangeListener(mPrefChangeListener); mPrefs.registerOnSharedPreferenceChangeListener(mPrefChangeListener);
handlePreferenceChange(mPrefs, getString(R.string.downloads_cross_network));
handlePreferenceChange(mPrefs, getString(R.string.downloads_max_retry));
wakeLock = new LockManager(this); wakeLock = new LockManager(this);
} }
@ -183,9 +195,17 @@ public class DownloadManagerService extends Service {
mHandler.post(() -> mManager.startMission(urls, location, name, kind, threads, source, psName, psArgs, nearLength)); 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) {
downloadDoneCount = 0; if (action.equals(ACTION_RESET_DOWNLOAD_FINISHED) || action.equals(ACTION_OPEN_DOWNLOADS_FINISHED)) {
downloadDoneList.setLength(0); downloadDoneCount = 0;
downloadDoneList.setLength(0);
}
if (action.equals(ACTION_OPEN_DOWNLOADS_FINISHED)) {
startActivity(new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
);
}
} }
} }
return START_NOT_STICKY; return START_NOT_STICKY;
@ -213,7 +233,8 @@ public class DownloadManagerService extends Service {
unregisterReceiver(mNetworkStateListener); unregisterReceiver(mNetworkStateListener);
mPrefs.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); mPrefs.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener);
icDownloadDone.recycle(); if (icDownloadDone != null) icDownloadDone.recycle();
if (icDownloadFailed != null) icDownloadFailed.recycle();
icLauncher.recycle(); icLauncher.recycle();
} }
@ -250,7 +271,7 @@ public class DownloadManagerService extends Service {
updateForegroundState(true); updateForegroundState(true);
break; break;
case MESSAGE_ERROR: case MESSAGE_ERROR:
notifyFailedDownload(mission.name); notifyFailedDownload(mission);
updateForegroundState(mManager.runAnotherMission()); updateForegroundState(mManager.runAnotherMission());
break; break;
case MESSAGE_PAUSED: case MESSAGE_PAUSED:
@ -258,19 +279,16 @@ public class DownloadManagerService extends Service {
break; break;
} }
if (msg.what != MESSAGE_ERROR)
mFailedDownloads.delete(mFailedDownloads.indexOfValue(mission));
synchronized (mEchoObservers) { synchronized (mEchoObservers) {
Iterator<Handler> iterator = mEchoObservers.iterator(); for (Handler handler : mEchoObservers) {
while (iterator.hasNext()) { Message echo = new Message();
Handler handler = iterator.next(); echo.what = msg.what;
if (handler.getLooper().getThread().isAlive()) { echo.obj = msg.obj;
Message echo = new Message();
echo.what = msg.what; handler.sendMessage(echo);
echo.obj = msg.obj;
handler.sendMessage(echo);
} else {
iterator.remove();// ¿missing call to removeMissionEventListener()?
}
} }
} }
} }
@ -306,11 +324,14 @@ public class DownloadManagerService extends Service {
private void handlePreferenceChange(SharedPreferences prefs, String key) { private void handlePreferenceChange(SharedPreferences prefs, String key) {
if (key.equals(getString(R.string.downloads_max_retry))) { if (key.equals(getString(R.string.downloads_max_retry))) {
mManager.mPrefMaxRetry = Integer.parseInt( try {
prefs.getString(key, getString(R.string.default_max_retry)) String value = prefs.getString(key, getString(R.string.downloads_max_retry_default));
); mManager.mPrefMaxRetry = Integer.parseInt(value);
} catch (Exception e) {
mManager.mPrefMaxRetry = 0;
}
mManager.updateMaximumAttempts(); mManager.updateMaximumAttempts();
} else if (key.equals(getString(R.string.cross_network_downloads))) { } else if (key.equals(getString(R.string.downloads_cross_network))) {
mManager.mPrefCrossNetwork = prefs.getBoolean(key, false); mManager.mPrefCrossNetwork = prefs.getBoolean(key, false);
} }
} }
@ -368,7 +389,7 @@ public class DownloadManagerService extends Service {
} }
public void notifyFinishedDownload(String name) { public void notifyFinishedDownload(String name) {
if (notificationManager == null) { if (!mDownloadNotificationEnable || notificationManager == null) {
return; return;
} }
@ -380,14 +401,8 @@ public class DownloadManagerService extends Service {
.setAutoCancel(true) .setAutoCancel(true)
.setLargeIcon(icDownloadDone) .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(makePendingIntent(ACTION_RESET_DOWNLOAD_FINISHED))
new Intent(this, DownloadManagerService.class) .setContentIntent(makePendingIntent(ACTION_OPEN_DOWNLOADS_FINISHED));
.setAction(ACTION_RESET_DOWNLOAD_COUNT)
, PendingIntent.FLAG_UPDATE_CURRENT))
.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) {
@ -417,33 +432,38 @@ public class DownloadManagerService extends Service {
downloadDoneCount++; downloadDoneCount++;
} }
public void notifyFailedDownload(String name) { public void notifyFailedDownload(DownloadMission mission) {
if (icDownloadDone == null) { if (!mDownloadNotificationEnable || mFailedDownloads.indexOfValue(mission) >= 0) return;
// 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)) int id = downloadFailedNotificationID++;
.setAutoCancel(true) mFailedDownloads.put(id, mission);
.setLargeIcon(icDownloadDone)
.setSmallIcon(android.R.drawable.stat_sys_download_done) if (downloadFailedNotification == null) {
.setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis() + 1, icDownloadFailed = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_warning);
new Intent(this, DownloadActivity.class) downloadFailedNotification = new Builder(this, getString(R.string.notification_channel_id))
.setAction(Intent.ACTION_MAIN), .setAutoCancel(true)
PendingIntent.FLAG_UPDATE_CURRENT)); .setLargeIcon(icDownloadFailed)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setContentIntent(mOpenDownloadList);
}
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
notification.setContentTitle(getString(R.string.app_name)); downloadFailedNotification.setContentTitle(getString(R.string.app_name));
notification.setStyle(new NotificationCompat.BigTextStyle() downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(getString(R.string.download_failed).concat(": ").concat(name))); .bigText(getString(R.string.download_failed).concat(": ").concat(mission.name)));
} else { } else {
notification.setContentTitle(getString(R.string.download_failed)); downloadFailedNotification.setContentTitle(getString(R.string.download_failed));
notification.setContentText(name); downloadFailedNotification.setContentText(mission.name);
notification.setStyle(new NotificationCompat.BigTextStyle() downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(name)); .bigText(mission.name));
} }
notificationManager.notify(downloadFailedNotificationID++, notification.build()); notificationManager.notify(id, downloadFailedNotification.build());
}
private PendingIntent makePendingIntent(String action) {
Intent intent = new Intent(this, DownloadManagerService.class).setAction(action);
return PendingIntent.getService(this, intent.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
} }
private void manageObservers(Handler handler, boolean add) { private void manageObservers(Handler handler, boolean add) {
@ -470,12 +490,26 @@ public class DownloadManagerService extends Service {
manageObservers(handler, false); manageObservers(handler, false);
} }
public void resetFinishedDownloadCount() { public void clearDownloadNotifications() {
if (notificationManager == null || downloadDoneNotification == null) return; if (notificationManager == null) return;
notificationManager.cancel(DOWNLOADS_NOTIFICATION_ID); if (downloadDoneNotification != null) {
downloadDoneList.setLength(0); notificationManager.cancel(DOWNLOADS_NOTIFICATION_ID);
downloadDoneCount = 0; downloadDoneList.setLength(0);
downloadDoneCount = 0;
}
if (downloadFailedNotification != null) {
for (; downloadFailedNotificationID > DOWNLOADS_NOTIFICATION_ID; downloadFailedNotificationID--) {
notificationManager.cancel(downloadFailedNotificationID);
}
mFailedDownloads.clear();
downloadFailedNotificationID++;
}
} }
public void enableNotifications(boolean enable) {
mDownloadNotificationEnable = enable;
}
} }
public interface DMChecker { public interface DMChecker {

View File

@ -20,6 +20,7 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.util.DiffUtil; import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.RecyclerView.Adapter;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -40,7 +41,6 @@ import org.schabi.newpipe.util.NavigationHelper;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale;
import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.FinishedMission;
@ -53,9 +53,10 @@ import us.shandian.giga.util.Utility;
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
public class MissionAdapter extends RecyclerView.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 = "--.-%";
static { static {
ALGORITHMS.put(R.id.md5, "MD5"); ALGORITHMS.put(R.id.md5, "MD5");
@ -89,6 +90,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
case DownloadManagerService.MESSAGE_ERROR: case DownloadManagerService.MESSAGE_ERROR:
case DownloadManagerService.MESSAGE_FINISHED: case DownloadManagerService.MESSAGE_FINISHED:
onServiceMessage(msg); onServiceMessage(msg);
break;
} }
} }
}; };
@ -120,7 +122,10 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
if (view instanceof ViewHolderHeader) return; if (view instanceof ViewHolderHeader) return;
ViewHolderItem h = (ViewHolderItem) view; ViewHolderItem h = (ViewHolderItem) view;
if (h.item.mission instanceof DownloadMission) mPendingDownloadsItems.remove(h); if (h.item.mission instanceof DownloadMission) {
mPendingDownloadsItems.remove(h);
if (mPendingDownloadsItems.size() < 1) setAutoRefresh(false);
}
h.popupMenu.dismiss(); h.popupMenu.dismiss();
h.item = null; h.item = null;
@ -153,10 +158,11 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
h.item = item; h.item = item;
Utility.FileType type = Utility.getFileType(item.mission.kind, item.mission.name); Utility.FileType type = Utility.getFileType(item.mission.kind, item.mission.name);
long length = item.mission instanceof FinishedMission ? item.mission.length : ((DownloadMission) item.mission).getLength();
h.icon.setImageResource(Utility.getIconForFileType(type)); h.icon.setImageResource(Utility.getIconForFileType(type));
h.name.setText(item.mission.name); h.name.setText(item.mission.name);
h.size.setText(Utility.formatBytes(item.mission.length)); h.size.setText(Utility.formatBytes(length));
h.progress.setColors(Utility.getBackgroundForFileType(mContext, type), Utility.getForegroundForFileType(mContext, type)); h.progress.setColors(Utility.getBackgroundForFileType(mContext, type), Utility.getForegroundForFileType(mContext, type));
@ -187,66 +193,60 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
private void updateProgress(ViewHolderItem h) { private void updateProgress(ViewHolderItem h) {
if (h == null || h.item == null || h.item.mission instanceof FinishedMission) return; if (h == null || h.item == null || h.item.mission instanceof FinishedMission) return;
long now = System.currentTimeMillis();
DownloadMission mission = (DownloadMission) h.item.mission; DownloadMission mission = (DownloadMission) h.item.mission;
long now = System.currentTimeMillis();
if (h.lastTimeStamp == -1) {
h.lastTimeStamp = now;
}
if (h.lastDone == -1) {
h.lastDone = mission.done;
}
if (h.lastCurrent != mission.current) { if (h.lastCurrent != mission.current) {
h.lastCurrent = mission.current; h.lastCurrent = mission.current;
h.lastDone = 0;
h.lastTimeStamp = now; h.lastTimeStamp = now;
h.lastDone = 0;
} else {
if (h.lastTimeStamp == -1) h.lastTimeStamp = now;
if (h.lastDone == -1) h.lastDone = mission.done;
} }
long deltaTime = now - h.lastTimeStamp; long deltaTime = now - h.lastTimeStamp;
long deltaDone = mission.done - h.lastDone; long deltaDone = mission.done - h.lastDone;
boolean hasError = mission.errCode != DownloadMission.ERROR_NOTHING; boolean hasError = mission.errCode != DownloadMission.ERROR_NOTHING;
if (hasError || deltaTime == 0 || deltaTime > 1000) { // on error hide marquee or show if condition (mission.done < 1 || mission.unknownLength) is true
// on error hide marquee or show if condition (mission.done < 1 || mission.unknownLength) is true h.progress.setMarquee(!hasError && (mission.done < 1 || mission.unknownLength));
h.progress.setMarquee(!hasError && (mission.done < 1 || mission.unknownLength));
float progress; float progress;
if (mission.unknownLength) { if (mission.unknownLength) {
progress = Float.NaN; progress = Float.NaN;
h.progress.setProgress(0f); h.progress.setProgress(0f);
} else { } else {
progress = (float) ((double) mission.done / mission.length); progress = (float) ((double) mission.done / mission.length);
if (mission.urls.length > 1 && mission.current < mission.urls.length) { if (mission.urls.length > 1 && mission.current < mission.urls.length) {
progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length); progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length);
}
} }
}
if (hasError) { if (hasError) {
if (Float.isNaN(progress) || Float.isInfinite(progress)) if (Float.isNaN(progress) || Float.isInfinite(progress))
h.progress.setProgress(1f); 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 (Float.isNaN(progress) || Float.isInfinite(progress)) {
h.status.setText("--.-%"); h.status.setText(UNDEFINED_SPEED);
} 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);
}
} }
long length = mission.getLength(); long length = mission.getLength();
int state = 0; int state;
if (!mission.isFinished()) { if (!mission.running) {
if (!mission.running) { state = mission.enqueued ? 1 : 2;
state = mission.enqueued ? 1 : 2; } else if (mission.postprocessingRunning) {
} else if (mission.postprocessingRunning) { state = 3;
state = 3; } else {
} state = 0;
} }
if (state != 0) { if (state != 0) {
// update state without download speed
if (h.state != state) { if (h.state != state) {
String statusStr; String statusStr;
h.state = state; h.state = state;
@ -267,7 +267,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
} }
h.size.setText(Utility.formatBytes(length).concat(" (").concat(statusStr).concat(")")); h.size.setText(Utility.formatBytes(length).concat(" (").concat(statusStr).concat(")"));
} else if (deltaTime > 1000 && deltaDone > 0) { } else if (deltaDone > 0) {
h.lastTimeStamp = now; h.lastTimeStamp = now;
h.lastDone = mission.done; h.lastDone = mission.done;
} }
@ -275,10 +275,10 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
return; return;
} }
if (deltaDone > 0 && deltaTime > 0) {
float speed = (deltaDone * 1000f) / deltaTime;
if (deltaTime > 1000 && deltaDone > 0) { String speedStr = Utility.formatSpeed(speed);
float speed = (float) ((double) deltaDone / deltaTime);
String speedStr = Utility.formatSpeed(speed * 1000);
String sizeStr = Utility.formatBytes(length); String sizeStr = Utility.formatBytes(length);
h.size.setText(sizeStr.concat(" ").concat(speedStr)); h.size.setText(sizeStr.concat(" ").concat(speedStr));
@ -325,6 +325,8 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
private void onServiceMessage(@NonNull Message msg) { private void onServiceMessage(@NonNull Message msg) {
switch (msg.what) { switch (msg.what) {
case DownloadManagerService.MESSAGE_PROGRESS: case DownloadManagerService.MESSAGE_PROGRESS:
setAutoRefresh(true);
return;
case DownloadManagerService.MESSAGE_ERROR: case DownloadManagerService.MESSAGE_ERROR:
case DownloadManagerService.MESSAGE_FINISHED: case DownloadManagerService.MESSAGE_FINISHED:
break; break;
@ -339,8 +341,6 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
if (msg.what == DownloadManagerService.MESSAGE_FINISHED) { if (msg.what == DownloadManagerService.MESSAGE_FINISHED) {
// DownloadManager should mark the download as finished // DownloadManager should mark the download as finished
applyChanges(); applyChanges();
mPendingDownloadsItems.remove(i);
return; return;
} }
@ -396,7 +396,9 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
break; break;
default: default:
if (mission.errCode >= 100 && mission.errCode < 600) { if (mission.errCode >= 100 && mission.errCode < 600) {
str.append("HTTP"); str = new StringBuilder(8);
str.append("HTTP ");
str.append(mission.errCode);
} else if (mission.errObject == null) { } else if (mission.errObject == null) {
str.append("(not_decelerated_error_code)"); str.append("(not_decelerated_error_code)");
} }
@ -436,7 +438,7 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
case R.id.pause: case R.id.pause:
h.state = -1; h.state = -1;
mDownloadManager.pauseMission(mission); mDownloadManager.pauseMission(mission);
notifyItemChanged(h.getAdapterPosition()); updateProgress(h);
h.lastTimeStamp = -1; h.lastTimeStamp = -1;
h.lastDone = -1; h.lastDone = -1;
return true; return true;
@ -542,6 +544,43 @@ public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
} }
private boolean mUpdaterRunning = false;
private final Runnable rUpdater = this::updater;
public void onPaused() {
setAutoRefresh(false);
}
private void setAutoRefresh(boolean enabled) {
if (enabled && !mUpdaterRunning) {
mUpdaterRunning = true;
updater();
} else if (!enabled && mUpdaterRunning) {
mUpdaterRunning = false;
mHandler.removeCallbacks(rUpdater);
}
}
private void updater() {
if (!mUpdaterRunning) return;
boolean running = false;
for (ViewHolderItem h : mPendingDownloadsItems) {
// check if the mission is running first
if (!((DownloadMission) h.item.mission).running) continue;
updateProgress(h);
running = true;
}
if (running) {
mHandler.postDelayed(rUpdater, 1000);
} else {
mUpdaterRunning = false;
}
}
class ViewHolderItem extends RecyclerView.ViewHolder { class ViewHolderItem extends RecyclerView.ViewHolder {
DownloadManager.MissionItem item; DownloadManager.MissionItem item;

View File

@ -51,7 +51,7 @@ public class MissionsFragment extends Fragment {
@Override @Override
public void onServiceConnected(ComponentName name, IBinder binder) { public void onServiceConnected(ComponentName name, IBinder binder) {
mBinder = (DownloadManagerService.DMBinder) binder; mBinder = (DownloadManagerService.DMBinder) binder;
mBinder.resetFinishedDownloadCount(); mBinder.clearDownloadNotifications();
mAdapter = new MissionAdapter(mActivity, mBinder.getDownloadManager(), mClear, mEmpty); mAdapter = new MissionAdapter(mActivity, mBinder.getDownloadManager(), mClear, mEmpty);
mAdapter.deleterLoad(mBundle, getView()); mAdapter.deleterLoad(mBundle, getView());
@ -59,6 +59,7 @@ public class MissionsFragment extends Fragment {
mBundle = null; mBundle = null;
mBinder.addMissionEventListener(mAdapter.getMessenger()); mBinder.addMissionEventListener(mAdapter.getMessenger());
mBinder.enableNotifications(false);
updateList(); updateList();
} }
@ -141,6 +142,7 @@ public class MissionsFragment extends Fragment {
if (mBinder == null || mAdapter == null) return; if (mBinder == null || mAdapter == null) return;
mBinder.removeMissionEventListener(mAdapter.getMessenger()); mBinder.removeMissionEventListener(mAdapter.getMessenger());
mBinder.enableNotifications(true);
mActivity.unbindService(mConnection); mActivity.unbindService(mConnection);
mAdapter.deleterDispose(null); mAdapter.deleterDispose(null);
@ -201,7 +203,6 @@ public class MissionsFragment extends Fragment {
mAdapter.deleterDispose(outState); mAdapter.deleterDispose(outState);
mForceUpdate = true; mForceUpdate = true;
mBinder.removeMissionEventListener(mAdapter.getMessenger()); mBinder.removeMissionEventListener(mAdapter.getMessenger());
} }
} }
@ -219,5 +220,13 @@ public class MissionsFragment extends Fragment {
mBinder.addMissionEventListener(mAdapter.getMessenger()); mBinder.addMissionEventListener(mAdapter.getMessenger());
} }
if (mBinder != null) mBinder.enableNotifications(false);
}
@Override
public void onPause() {
super.onPause();
if (mAdapter != null) mAdapter.onPaused();
if (mBinder != null) mBinder.enableNotifications(true);
} }
} }

View File

@ -176,7 +176,9 @@
<string name="default_file_charset_value" translatable="false">@string/charset_most_special_characters_value</string> <string name="default_file_charset_value" translatable="false">@string/charset_most_special_characters_value</string>
<string name="downloads_max_retry" translatable="false">downloads_max_retry</string> <string name="downloads_max_retry" translatable="false">downloads_max_retry</string>
<string name="downloads_max_retry_default" translatable="false">3</string>
<string-array name="downloads_max_retry_list" translatable="false"> <string-array name="downloads_max_retry_list" translatable="false">
<item translatable="true">@string/minimize_on_exit_none_description</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>3</item> <item>3</item>
@ -187,8 +189,7 @@
<item>15</item> <item>15</item>
</string-array> </string-array>
<string name="default_max_retry" translatable="false">3</string> <string name="downloads_cross_network" translatable="false">cross_network_downloads</string>
<string name="cross_network_downloads" translatable="false">cross_network_downloads</string>
<string name="default_download_threads" translatable="false">default_download_threads</string> <string name="default_download_threads" translatable="false">default_download_threads</string>

View File

@ -30,7 +30,7 @@
android:title="@string/settings_file_replacement_character_title"/> android:title="@string/settings_file_replacement_character_title"/>
<ListPreference <ListPreference
android:defaultValue="@string/default_max_retry" android:defaultValue="@string/downloads_max_retry_default"
android:entries="@array/downloads_max_retry_list" android:entries="@array/downloads_max_retry_list"
android:entryValues="@array/downloads_max_retry_list" android:entryValues="@array/downloads_max_retry_list"
android:key="@string/downloads_max_retry" android:key="@string/downloads_max_retry"
@ -39,7 +39,7 @@
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="@string/cross_network_downloads" android:key="@string/downloads_cross_network"
android:summary="@string/pause_downloads_on_mobile_desc" android:summary="@string/pause_downloads_on_mobile_desc"
android:title="@string/pause_downloads_on_mobile" /> android:title="@string/pause_downloads_on_mobile" />