diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 0418eadae..4f98f7f28 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -416,7 +416,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck // fallback // 1st loop match country & language // 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 i = 0; i < streams.size(); i++) { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index 851b5cb1b..79e01b8cc 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -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 * @return the relative position in bytes or zero */ - long getBlockBytePosition(int threadId) { + long getThreadBytePosition(int threadId) { return threadBytePositions.get(threadId); } @@ -256,6 +256,8 @@ public class DownloadMission extends Mission { } } + conn.connect(); + int statusCode = conn.getResponseCode(); switch (statusCode) { case 204: @@ -446,6 +448,8 @@ public class DownloadMission extends Mission { return; } + if (postprocessingRunning) return; + // wait for all threads are suspended before save the state runAsync(-1, () -> { try { @@ -590,7 +594,7 @@ public class DownloadMission extends Mission { @Override public String getMessage() { - return "Http status code" + String.valueOf(statusCode); + return "Http status code: " + String.valueOf(statusCode); } } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index 95f4758f9..336bc13ee 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -2,11 +2,10 @@ package us.shandian.giga.get; import android.util.Log; -import java.io.BufferedInputStream; import java.io.FileNotFoundException; +import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; -import java.net.URL; import java.nio.channels.ClosedByInterruptException; import static org.schabi.newpipe.BuildConfig.DEBUG; @@ -38,8 +37,8 @@ public class DownloadRunnable implements Runnable { Log.d(TAG, mId + ":recovered: " + mMission.recovered); } - BufferedInputStream ipt = null; RandomAccessFile f; + InputStream is = null; try { f = new RandomAccessFile(mMission.getDownloadedFile(), "rw"); @@ -82,9 +81,11 @@ public class DownloadRunnable implements Runnable { mMission.preserveBlock(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; + start += mMission.getThreadBytePosition(mId); + if (end >= mMission.length) { end = mMission.length - 1; } @@ -107,11 +108,11 @@ public class DownloadRunnable implements Runnable { f.seek(mMission.offsets[mMission.current] + start); - ipt = new BufferedInputStream(conn.getInputStream()); + is = conn.getInputStream(); byte[] buf = new byte[DownloadMission.BUFFER_SIZE]; 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); start += len; total += len; @@ -119,7 +120,8 @@ public class DownloadRunnable implements Runnable { } 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 @@ -132,7 +134,7 @@ public class DownloadRunnable implements Runnable { if (e instanceof ClosedByInterruptException) break; - if (retryCount++ > mMission.maxRetry) { + if (retryCount++ >= mMission.maxRetry) { mMission.notifyError(e); break; } @@ -140,6 +142,8 @@ public class DownloadRunnable implements Runnable { if (DEBUG) { Log.d(TAG, mId + ":position " + blockPosition + " retrying due exception", e); } + + retry = true; } } @@ -150,7 +154,7 @@ public class DownloadRunnable implements Runnable { } try { - if (ipt != null) ipt.close(); + if (is != null) is.close(); } catch (Exception err) { // nothing to do } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java index b648dd812..5ef4ed90e 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnableFallback.java @@ -4,8 +4,8 @@ import android.annotation.SuppressLint; import android.support.annotation.NonNull; import android.util.Log; -import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.nio.channels.ClosedByInterruptException; @@ -24,18 +24,18 @@ public class DownloadRunnableFallback implements Runnable { private final DownloadMission mMission; private int retryCount = 0; - private BufferedInputStream ipt; + private InputStream is; private RandomAccessFile f; DownloadRunnableFallback(@NonNull DownloadMission mission) { mMission = mission; - ipt = null; + is = null; f = null; } private void dispose() { try { - if (ipt != null) ipt.close(); + if (is != null) is.close(); } catch (IOException e) { // nothing to do } @@ -55,7 +55,7 @@ public class DownloadRunnableFallback implements Runnable { long start = 0; if (!mMission.unknownLength) { - start = mMission.getBlockBytePosition(0); + start = mMission.getThreadBytePosition(0); if (DEBUG && start > 0) { 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.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; - 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); start += 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 @@ -96,7 +93,7 @@ public class DownloadRunnableFallback implements Runnable { if (e instanceof ClosedByInterruptException) return; - if (retryCount++ > mMission.maxRetry) { + if (retryCount++ >= mMission.maxRetry) { mMission.notifyError(e); return; } diff --git a/app/src/main/java/us/shandian/giga/postprocessing/io/CircularFile.java b/app/src/main/java/us/shandian/giga/postprocessing/io/CircularFile.java index 1454c1f2d..7e5ad9929 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/io/CircularFile.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/io/CircularFile.java @@ -10,8 +10,10 @@ import java.util.ArrayList; public class CircularFile extends SharpStream { 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 boolean IMMEDIATE_AUX_BUFFER_FLUSH = false; private RandomAccessFile out; private long position; @@ -45,7 +47,7 @@ public class CircularFile extends SharpStream { throw err; } - auxiliaryBuffers = new ArrayList<>(1); + auxiliaryBuffers = new ArrayList<>(15); callback = checker; startOffset = offset; reportPosition = offset; @@ -122,7 +124,7 @@ public class CircularFile extends SharpStream { while (available > 0 && auxiliaryBuffers.size() > 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)) { available -= aux.size; writeQueue(aux.buffer, 0, aux.size); @@ -131,26 +133,27 @@ public class CircularFile extends SharpStream { continue; } - // try flush contents to avoid allocate another auxiliar buffer - if (aux.available() < len && available > queue.size) { - int size = Math.min(len, aux.available()); - aux.write(b, off, size); + if (IMMEDIATE_AUX_BUFFER_FLUSH) { + // try flush contents to avoid allocate another auxiliary buffer + if (aux.available() < len && available > queue.size) { + int size = Math.min(len, aux.available()); + aux.write(b, off, size); - off += size; - len -= size; + off += size; + len -= size; - size = Math.min(aux.size, (int) available - queue.size); - if (size < 1) { - break; + size = Math.min(aux.size, (int) available - queue.size); + if (size < 1) { + break; + } + + writeQueue(aux.buffer, 0, size); + aux.dereference(size); + + available -= size; } - - writeQueue(aux.buffer, 0, size); - aux.dereference(size); - - available -= size; + break; } - - break; } if (len < 1) { @@ -174,7 +177,7 @@ public class CircularFile extends SharpStream { if (available < 1) { // secondary auxiliary buffer available = len; - aux = new ManagedBuffer(Math.max(len, AUX_BUFFER_SIZE)); + aux = new ManagedBuffer(Math.max(len, AUX_BUFFER_SIZE2)); auxiliaryBuffers.add(aux); i++; } else { @@ -184,10 +187,7 @@ public class CircularFile extends SharpStream { aux.write(b, off, (int) available); len -= available; - if (len < 1) { - break; - } - off += available; + if (len > 0) off += available; } } } @@ -361,12 +361,8 @@ public class CircularFile extends SharpStream { if (amount > size) { throw new IndexOutOfBoundsException("Invalid dereference amount (" + amount + ">=" + size + ")"); } - size -= amount; - - for (int i = 0; i < size; i++) { - buffer[i] = buffer[amount + i]; - } + System.arraycopy(buffer, amount, buffer, 0, size); } protected int available() { diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManager.java b/app/src/main/java/us/shandian/giga/service/DownloadManager.java index 31b5b16a9..55a22c8c5 100644 --- a/app/src/main/java/us/shandian/giga/service/DownloadManager.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManager.java @@ -277,6 +277,7 @@ public class DownloadManager { mDownloadDataSource.deleteMission(mission); } + mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED); mission.delete(); } } @@ -427,8 +428,8 @@ public class DownloadManager { if (!canDownloadInCurrentNetwork()) return false; for (DownloadMission mission : mMissionsPending) { - if (!mission.running && mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED && mission.enqueued) { - resumeMission(mMissionsPending.get(i)); + if (!mission.running && mission.errCode == DownloadMission.ERROR_NOTHING && mission.enqueued) { + resumeMission(mission); return true; } } diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 7b30740d5..7d88d9e2a 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -28,6 +28,7 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.content.PermissionChecker; import android.util.Log; +import android.util.SparseArray; import android.widget.Toast; import org.schabi.newpipe.R; @@ -36,7 +37,6 @@ import org.schabi.newpipe.player.helper.LockManager; import java.io.File; import java.util.ArrayList; -import java.util.Iterator; import us.shandian.giga.get.DownloadMission; import us.shandian.giga.service.DownloadManager.NetworkState; @@ -46,13 +46,14 @@ import static org.schabi.newpipe.BuildConfig.DEBUG; 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_PAUSED = 2; - public static final int MESSAGE_FINISHED = 3; - public static final int MESSAGE_PROGRESS = 4; - public static final int MESSAGE_ERROR = 5; + public static final int MESSAGE_RUNNING = 0; + public static final int MESSAGE_PAUSED = 1; + public static final int MESSAGE_FINISHED = 2; + public static final int MESSAGE_PROGRESS = 3; + 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 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_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 DownloadManager mManager; private Notification mNotification; private Handler mHandler; + private boolean mForeground = false; + private NotificationManager notificationManager = null; + private boolean mDownloadNotificationEnable = true; + private int downloadDoneCount = 0; private Builder downloadDoneNotification = null; private StringBuilder downloadDoneList = null; - NotificationManager notificationManager = null; - private boolean mForeground = false; private final ArrayList mEchoObservers = new ArrayList<>(1); @@ -90,9 +94,14 @@ public class DownloadManagerService extends Service { private LockManager wakeLock = null; private int downloadFailedNotificationID = DOWNLOADS_NOTIFICATION_ID + 1; + private Builder downloadFailedNotification = null; + private SparseArray mFailedDownloads = new SparseArray<>(5); private Bitmap icLauncher; private Bitmap icDownloadDone; + private Bitmap icDownloadFailed; + + private PendingIntent mOpenDownloadList; /** * notify media scanner on downloaded media file ... @@ -124,14 +133,14 @@ public class DownloadManagerService extends Service { Intent openDownloadListIntent = new Intent(this, DownloadActivity.class) .setAction(Intent.ACTION_MAIN); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, + mOpenDownloadList = PendingIntent.getActivity(this, 0, openDownloadListIntent, PendingIntent.FLAG_UPDATE_CURRENT); icLauncher = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher); Builder builder = new Builder(this, getString(R.string.notification_channel_id)) - .setContentIntent(pendingIntent) + .setContentIntent(mOpenDownloadList) .setSmallIcon(android.R.drawable.stat_sys_download) .setLargeIcon(icLauncher) .setContentTitle(getString(R.string.msg_running)) @@ -155,6 +164,9 @@ public class DownloadManagerService extends Service { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mPrefs.registerOnSharedPreferenceChangeListener(mPrefChangeListener); + handlePreferenceChange(mPrefs, getString(R.string.downloads_cross_network)); + handlePreferenceChange(mPrefs, getString(R.string.downloads_max_retry)); + 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)); - } else if (downloadDoneNotification != null && action.equals(ACTION_RESET_DOWNLOAD_COUNT)) { - downloadDoneCount = 0; - downloadDoneList.setLength(0); + } else if (downloadDoneNotification != null) { + if (action.equals(ACTION_RESET_DOWNLOAD_FINISHED) || action.equals(ACTION_OPEN_DOWNLOADS_FINISHED)) { + 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; @@ -213,7 +233,8 @@ public class DownloadManagerService extends Service { unregisterReceiver(mNetworkStateListener); mPrefs.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener); - icDownloadDone.recycle(); + if (icDownloadDone != null) icDownloadDone.recycle(); + if (icDownloadFailed != null) icDownloadFailed.recycle(); icLauncher.recycle(); } @@ -250,7 +271,7 @@ public class DownloadManagerService extends Service { updateForegroundState(true); break; case MESSAGE_ERROR: - notifyFailedDownload(mission.name); + notifyFailedDownload(mission); updateForegroundState(mManager.runAnotherMission()); break; case MESSAGE_PAUSED: @@ -258,19 +279,16 @@ public class DownloadManagerService extends Service { break; } + if (msg.what != MESSAGE_ERROR) + mFailedDownloads.delete(mFailedDownloads.indexOfValue(mission)); synchronized (mEchoObservers) { - Iterator iterator = mEchoObservers.iterator(); - while (iterator.hasNext()) { - Handler handler = iterator.next(); - if (handler.getLooper().getThread().isAlive()) { - Message echo = new Message(); - echo.what = msg.what; - echo.obj = msg.obj; - handler.sendMessage(echo); - } else { - iterator.remove();// ¿missing call to removeMissionEventListener()? - } + for (Handler handler : mEchoObservers) { + Message echo = new Message(); + echo.what = msg.what; + echo.obj = msg.obj; + + handler.sendMessage(echo); } } } @@ -306,11 +324,14 @@ public class DownloadManagerService extends Service { private void handlePreferenceChange(SharedPreferences prefs, String key) { if (key.equals(getString(R.string.downloads_max_retry))) { - mManager.mPrefMaxRetry = Integer.parseInt( - prefs.getString(key, getString(R.string.default_max_retry)) - ); + try { + 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(); - } 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); } } @@ -368,7 +389,7 @@ public class DownloadManagerService extends Service { } public void notifyFinishedDownload(String name) { - if (notificationManager == null) { + if (!mDownloadNotificationEnable || notificationManager == null) { return; } @@ -380,14 +401,8 @@ public class DownloadManagerService extends Service { .setAutoCancel(true) .setLargeIcon(icDownloadDone) .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setDeleteIntent(PendingIntent.getService(this, (int) System.currentTimeMillis(), - new Intent(this, DownloadManagerService.class) - .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)); + .setDeleteIntent(makePendingIntent(ACTION_RESET_DOWNLOAD_FINISHED)) + .setContentIntent(makePendingIntent(ACTION_OPEN_DOWNLOADS_FINISHED)); } if (downloadDoneCount < 1) { @@ -417,33 +432,38 @@ public class DownloadManagerService extends Service { downloadDoneCount++; } - public void notifyFailedDownload(String name) { - if (icDownloadDone == null) { - // TODO: use a proper icon for failed downloads - icDownloadDone = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done); - } + public void notifyFailedDownload(DownloadMission mission) { + if (!mDownloadNotificationEnable || mFailedDownloads.indexOfValue(mission) >= 0) return; - Builder notification = new Builder(this, getString(R.string.notification_channel_id)) - .setAutoCancel(true) - .setLargeIcon(icDownloadDone) - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis() + 1, - new Intent(this, DownloadActivity.class) - .setAction(Intent.ACTION_MAIN), - PendingIntent.FLAG_UPDATE_CURRENT)); + int id = downloadFailedNotificationID++; + mFailedDownloads.put(id, mission); + + if (downloadFailedNotification == null) { + icDownloadFailed = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_warning); + downloadFailedNotification = new Builder(this, getString(R.string.notification_channel_id)) + .setAutoCancel(true) + .setLargeIcon(icDownloadFailed) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setContentIntent(mOpenDownloadList); + } if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - notification.setContentTitle(getString(R.string.app_name)); - notification.setStyle(new NotificationCompat.BigTextStyle() - .bigText(getString(R.string.download_failed).concat(": ").concat(name))); + downloadFailedNotification.setContentTitle(getString(R.string.app_name)); + downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle() + .bigText(getString(R.string.download_failed).concat(": ").concat(mission.name))); } else { - notification.setContentTitle(getString(R.string.download_failed)); - notification.setContentText(name); - notification.setStyle(new NotificationCompat.BigTextStyle() - .bigText(name)); + downloadFailedNotification.setContentTitle(getString(R.string.download_failed)); + downloadFailedNotification.setContentText(mission.name); + downloadFailedNotification.setStyle(new NotificationCompat.BigTextStyle() + .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) { @@ -470,12 +490,26 @@ public class DownloadManagerService extends Service { manageObservers(handler, false); } - public void resetFinishedDownloadCount() { - if (notificationManager == null || downloadDoneNotification == null) return; - notificationManager.cancel(DOWNLOADS_NOTIFICATION_ID); - downloadDoneList.setLength(0); - downloadDoneCount = 0; + public void clearDownloadNotifications() { + if (notificationManager == null) return; + if (downloadDoneNotification != null) { + notificationManager.cancel(DOWNLOADS_NOTIFICATION_ID); + 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 { diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 4dc40c420..8c332565b 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -20,6 +20,7 @@ import android.support.v7.app.AlertDialog; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; +import android.support.v7.widget.RecyclerView.Adapter; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; @@ -40,7 +41,6 @@ import org.schabi.newpipe.util.NavigationHelper; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Locale; import us.shandian.giga.get.DownloadMission; 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_READ_URI_PERMISSION; -public class MissionAdapter extends RecyclerView.Adapter { +public class MissionAdapter extends Adapter { private static final SparseArray ALGORITHMS = new SparseArray<>(); private static final String TAG = "MissionAdapter"; + private static final String UNDEFINED_SPEED = "--.-%"; static { ALGORITHMS.put(R.id.md5, "MD5"); @@ -89,6 +90,7 @@ public class MissionAdapter extends RecyclerView.Adapter { case DownloadManagerService.MESSAGE_ERROR: case DownloadManagerService.MESSAGE_FINISHED: onServiceMessage(msg); + break; } } }; @@ -120,7 +122,10 @@ public class MissionAdapter extends RecyclerView.Adapter { if (view instanceof ViewHolderHeader) return; 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.item = null; @@ -153,10 +158,11 @@ public class MissionAdapter extends RecyclerView.Adapter { h.item = item; 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.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)); @@ -187,66 +193,60 @@ public class MissionAdapter extends RecyclerView.Adapter { private void updateProgress(ViewHolderItem h) { if (h == null || h.item == null || h.item.mission instanceof FinishedMission) return; + long now = System.currentTimeMillis(); 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) { h.lastCurrent = mission.current; - h.lastDone = 0; 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 deltaDone = mission.done - h.lastDone; 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 - h.progress.setMarquee(!hasError && (mission.done < 1 || mission.unknownLength)); + // on error hide marquee or show if condition (mission.done < 1 || mission.unknownLength) is true + h.progress.setMarquee(!hasError && (mission.done < 1 || mission.unknownLength)); - float progress; - if (mission.unknownLength) { - progress = Float.NaN; - h.progress.setProgress(0f); - } else { - progress = (float) ((double) mission.done / mission.length); - if (mission.urls.length > 1 && mission.current < mission.urls.length) { - progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length); - } + float progress; + if (mission.unknownLength) { + progress = Float.NaN; + h.progress.setProgress(0f); + } else { + progress = (float) ((double) mission.done / mission.length); + if (mission.urls.length > 1 && mission.current < mission.urls.length) { + progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length); } + } - if (hasError) { - if (Float.isNaN(progress) || Float.isInfinite(progress)) - h.progress.setProgress(1f); - h.status.setText(R.string.msg_error); - } else if (Float.isNaN(progress) || Float.isInfinite(progress)) { - h.status.setText("--.-%"); - } else { - h.status.setText(String.format("%.2f%%", progress * 100)); - h.progress.setProgress(progress); - } + if (hasError) { + if (Float.isNaN(progress) || Float.isInfinite(progress)) + h.progress.setProgress(1f); + h.status.setText(R.string.msg_error); + } else if (Float.isNaN(progress) || Float.isInfinite(progress)) { + h.status.setText(UNDEFINED_SPEED); + } else { + h.status.setText(String.format("%.2f%%", progress * 100)); + h.progress.setProgress(progress); } long length = mission.getLength(); - int state = 0; - if (!mission.isFinished()) { - if (!mission.running) { - state = mission.enqueued ? 1 : 2; - } else if (mission.postprocessingRunning) { - state = 3; - } + int state; + if (!mission.running) { + state = mission.enqueued ? 1 : 2; + } else if (mission.postprocessingRunning) { + state = 3; + } else { + state = 0; } if (state != 0) { + // update state without download speed if (h.state != state) { String statusStr; h.state = state; @@ -267,7 +267,7 @@ public class MissionAdapter extends RecyclerView.Adapter { } h.size.setText(Utility.formatBytes(length).concat(" (").concat(statusStr).concat(")")); - } else if (deltaTime > 1000 && deltaDone > 0) { + } else if (deltaDone > 0) { h.lastTimeStamp = now; h.lastDone = mission.done; } @@ -275,10 +275,10 @@ public class MissionAdapter extends RecyclerView.Adapter { return; } + if (deltaDone > 0 && deltaTime > 0) { + float speed = (deltaDone * 1000f) / deltaTime; - if (deltaTime > 1000 && deltaDone > 0) { - float speed = (float) ((double) deltaDone / deltaTime); - String speedStr = Utility.formatSpeed(speed * 1000); + String speedStr = Utility.formatSpeed(speed); String sizeStr = Utility.formatBytes(length); h.size.setText(sizeStr.concat(" ").concat(speedStr)); @@ -325,6 +325,8 @@ public class MissionAdapter extends RecyclerView.Adapter { private void onServiceMessage(@NonNull Message msg) { switch (msg.what) { case DownloadManagerService.MESSAGE_PROGRESS: + setAutoRefresh(true); + return; case DownloadManagerService.MESSAGE_ERROR: case DownloadManagerService.MESSAGE_FINISHED: break; @@ -339,8 +341,6 @@ public class MissionAdapter extends RecyclerView.Adapter { if (msg.what == DownloadManagerService.MESSAGE_FINISHED) { // DownloadManager should mark the download as finished applyChanges(); - - mPendingDownloadsItems.remove(i); return; } @@ -396,7 +396,9 @@ public class MissionAdapter extends RecyclerView.Adapter { break; default: 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) { str.append("(not_decelerated_error_code)"); } @@ -436,7 +438,7 @@ public class MissionAdapter extends RecyclerView.Adapter { case R.id.pause: h.state = -1; mDownloadManager.pauseMission(mission); - notifyItemChanged(h.getAdapterPosition()); + updateProgress(h); h.lastTimeStamp = -1; h.lastDone = -1; return true; @@ -542,6 +544,43 @@ public class MissionAdapter extends RecyclerView.Adapter { } + 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 { DownloadManager.MissionItem item; diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index f04361f19..aa9c497f1 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -51,7 +51,7 @@ public class MissionsFragment extends Fragment { @Override public void onServiceConnected(ComponentName name, IBinder binder) { mBinder = (DownloadManagerService.DMBinder) binder; - mBinder.resetFinishedDownloadCount(); + mBinder.clearDownloadNotifications(); mAdapter = new MissionAdapter(mActivity, mBinder.getDownloadManager(), mClear, mEmpty); mAdapter.deleterLoad(mBundle, getView()); @@ -59,6 +59,7 @@ public class MissionsFragment extends Fragment { mBundle = null; mBinder.addMissionEventListener(mAdapter.getMessenger()); + mBinder.enableNotifications(false); updateList(); } @@ -130,7 +131,7 @@ public class MissionsFragment extends Fragment { @Override public void onAttach(Activity activity) { super.onAttach(activity); - + mActivity = activity; } @@ -141,6 +142,7 @@ public class MissionsFragment extends Fragment { if (mBinder == null || mAdapter == null) return; mBinder.removeMissionEventListener(mAdapter.getMessenger()); + mBinder.enableNotifications(true); mActivity.unbindService(mConnection); mAdapter.deleterDispose(null); @@ -181,7 +183,7 @@ public class MissionsFragment extends Fragment { // destroy all created views in the recycler mList.setAdapter(null); mAdapter.notifyDataSetChanged(); - + // re-attach the adapter in grid/lineal mode mAdapter.setLinear(mLinear); mList.setAdapter(mAdapter); @@ -201,14 +203,13 @@ public class MissionsFragment extends Fragment { mAdapter.deleterDispose(outState); mForceUpdate = true; mBinder.removeMissionEventListener(mAdapter.getMessenger()); - } } @Override public void onResume() { super.onResume(); - + if (mAdapter != null) { mAdapter.deleterResume(); @@ -219,5 +220,13 @@ public class MissionsFragment extends Fragment { 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); } } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 2973b69ef..5125752b4 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -176,7 +176,9 @@ @string/charset_most_special_characters_value downloads_max_retry + 3 + @string/minimize_on_exit_none_description 1 2 3 @@ -186,9 +188,8 @@ 10 15 - - 3 - cross_network_downloads + + cross_network_downloads default_download_threads diff --git a/app/src/main/res/xml/download_settings.xml b/app/src/main/res/xml/download_settings.xml index 7175ae711..e7faf40dd 100644 --- a/app/src/main/res/xml/download_settings.xml +++ b/app/src/main/res/xml/download_settings.xml @@ -30,7 +30,7 @@ android:title="@string/settings_file_replacement_character_title"/>