From 5d6ff486d60eca8d30177941762d184b7924e7bf Mon Sep 17 00:00:00 2001 From: Zhao Wei Liew Date: Sat, 1 Oct 2016 19:36:07 +0800 Subject: DownloadProvider: Add support for manual pause/resume Implement the following features: - Manually pause/resume individual downloads in DownloadManager - Manually pause/resume all downloads in notification Based on commit ecd609e7017b8a69688bbae25c17d878ea305f19. Change-Id: I433cdee2de8b3add0248bbb0a9d02f8da4e5bb38 --- res/drawable/download_pause.xml | 16 +++++ res/values/cm_strings.xml | 16 +++++ src/com/android/providers/downloads/Constants.java | 9 +++ .../providers/downloads/DownloadNotifier.java | 79 ++++++++++++++++++++-- .../providers/downloads/DownloadReceiver.java | 20 ++++++ .../downloads/DownloadStorageProvider.java | 17 ++++- .../providers/downloads/DownloadThread.java | 7 +- .../providers/downloads/ui/TrampolineActivity.java | 16 ++++- 8 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 res/drawable/download_pause.xml diff --git a/res/drawable/download_pause.xml b/res/drawable/download_pause.xml new file mode 100644 index 00000000..c82ec923 --- /dev/null +++ b/res/drawable/download_pause.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index 100318a2..3a28aafa 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -26,4 +26,20 @@ %d h + + + Paused + + + Paused, %d%% + + + Resume + + + pause + + + %d paused diff --git a/src/com/android/providers/downloads/Constants.java b/src/com/android/providers/downloads/Constants.java index 79daeaed..6633057e 100644 --- a/src/com/android/providers/downloads/Constants.java +++ b/src/com/android/providers/downloads/Constants.java @@ -57,6 +57,15 @@ public class Constants { /** the intent that gets sent when deleting the notification of a completed download */ public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; + /** the intent that gets sent when manually resuming a manually paused download queue */ + public static final String ACTION_RESUME_QUEUE = "android.intent.action.DOWNLOAD_RESUME_QUEUE"; + + /** the intent that gets sent when manually resuming a manually paused download */ + public static final String ACTION_RESUME = "android.intent.action.DOWNLOAD_RESUME"; + + /** the intent that gets sent when manually pausing a running download */ + public static final String ACTION_PAUSE = "android.intent.action.DOWNLOAD_PAUSE"; + /** The default base name for downloaded files if we can't get one at the HTTP level */ public static final String DEFAULT_DL_FILENAME = "downloadfile"; diff --git a/src/com/android/providers/downloads/DownloadNotifier.java b/src/com/android/providers/downloads/DownloadNotifier.java index d2c28600..ee302b4c 100644 --- a/src/com/android/providers/downloads/DownloadNotifier.java +++ b/src/com/android/providers/downloads/DownloadNotifier.java @@ -19,6 +19,7 @@ package com.android.providers.downloads; import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE; import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED; import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION; +import static android.provider.Downloads.Impl.STATUS_PAUSED_MANUAL; import static android.provider.Downloads.Impl.STATUS_QUEUED_FOR_WIFI; import static android.provider.Downloads.Impl.STATUS_RUNNING; @@ -48,6 +49,7 @@ import android.util.LongSparseLongArray; import com.android.internal.util.ArrayUtils; +import java.lang.StringBuilder; import java.text.NumberFormat; import javax.annotation.concurrent.GuardedBy; @@ -222,6 +224,36 @@ public class DownloadNotifier { builder.setWhen(firstShown); builder.setOnlyAlertOnce(true); + /** + * If *all* current downloads in the cluster are manually paused, + * display the appropriate notification icon and content. + * + * Also keep track of the number of paused downloads so as to + * display the current number of downloading files correctly. + */ + int numPaused = 0; + for (int j = 0; j < cluster.size(); j++) { + cursor.moveToPosition(cluster.get(j)); + int status = cursor.getInt(UpdateQuery.STATUS); + if (status == Downloads.Impl.STATUS_PAUSED_MANUAL) { + numPaused++; + } + } + boolean isClusterPaused = numPaused == cluster.size(); + + // Show relevant icon + if (type == TYPE_ACTIVE) { + if (isClusterPaused) { + builder.setSmallIcon(R.drawable.download_pause); + } else { + builder.setSmallIcon(android.R.drawable.stat_sys_download); + } + } else if (type == TYPE_WAITING) { + builder.setSmallIcon(android.R.drawable.stat_sys_warning); + } else if (type == TYPE_COMPLETE) { + builder.setSmallIcon(android.R.drawable.stat_sys_download_done); + } + // Build action intents if (type == TYPE_ACTIVE || type == TYPE_WAITING) { final long[] downloadIds = getDownloadIds(cursor, cluster); @@ -253,6 +285,32 @@ public class DownloadNotifier { PendingIntent.getBroadcast(mContext, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + if (isClusterPaused) { + // Add a Resume action + final Intent resumeIntent = new Intent(Constants.ACTION_RESUME_QUEUE, + uri, mContext, DownloadReceiver.class); + resumeIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, + downloadIds); + + builder.addAction( + com.android.internal.R.drawable.ic_media_play, + res.getString(R.string.button_resume_download), + PendingIntent.getBroadcast(mContext, + 0, resumeIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } else { + // Add a Pause action + final Intent pauseIntent = new Intent(Constants.ACTION_PAUSE, + uri, mContext, DownloadReceiver.class); + pauseIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, + downloadIds); + + builder.addAction( + com.android.internal.R.drawable.ic_media_pause, + res.getString(R.string.button_pause_download), + PendingIntent.getBroadcast(mContext, + 0, pauseIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + } + } else if (type == TYPE_COMPLETE) { cursor.moveToPosition(cluster.get(0)); final long id = cursor.getLong(UpdateQuery._ID); @@ -349,7 +407,9 @@ public class DownloadNotifier { builder.setContentTitle(getDownloadTitle(res, cursor)); if (type == TYPE_ACTIVE) { - if (speedAsSizeText != null) { + if (isClusterPaused) { + builder.setContentText(res.getText(R.string.download_paused)); + } else if (speedAsSizeText != null) { builder.setContentText(res.getString(R.string.text_download_speed, remainingText, speedAsSizeText)); } else { @@ -387,8 +447,19 @@ public class DownloadNotifier { } if (type == TYPE_ACTIVE) { - builder.setContentTitle(res.getQuantityString( - R.plurals.notif_summary_active, cluster.size(), cluster.size())); + if (isClusterPaused) { + builder.setContentTitle(res.getString(R.string.download_paused)); + } else if (numPaused > 0) { + StringBuilder sb = new StringBuilder(res.getQuantityString( + R.plurals.notif_summary_active, + cluster.size() - numPaused, cluster.size())); + sb.append(", "); + sb.append(res.getString(R.string.notif_summary_paused, numPaused)); + builder.setContentTitle(sb.toString()); + } else { + builder.setContentTitle(res.getQuantityString( + R.plurals.notif_summary_active, cluster.size(), cluster.size())); + } builder.setContentText(remainingText); builder.setContentInfo(percentText); if (speedAsSizeText != null) { @@ -491,7 +562,7 @@ public class DownloadNotifier { } private static boolean isActiveAndVisible(int status, int visibility) { - return status == STATUS_RUNNING && + return (status == STATUS_RUNNING || status == STATUS_PAUSED_MANUAL) && (visibility == VISIBILITY_VISIBLE || visibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED); } diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java index 92d0bad4..96b22621 100644 --- a/src/com/android/providers/downloads/DownloadReceiver.java +++ b/src/com/android/providers/downloads/DownloadReceiver.java @@ -114,6 +114,26 @@ public class DownloadReceiver extends BroadcastReceiver { NotificationManager notifManager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE); notifManager.cancel(notifTag, 0); + } else if (Constants.ACTION_PAUSE.equals(action)) { + long[] downloadIds = intent.getLongArrayExtra( + DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); + DownloadManager manager = (DownloadManager) context.getSystemService( + Context.DOWNLOAD_SERVICE); + for (long id : downloadIds) { + manager.pauseDownload(id); + } + } else if (Constants.ACTION_RESUME_QUEUE.equals(action)) { + long[] downloadIds = intent.getLongArrayExtra( + DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); + DownloadManager manager = (DownloadManager) context.getSystemService( + Context.DOWNLOAD_SERVICE); + for (long id : downloadIds) { + manager.resumeDownload(id); + Helpers.scheduleJob(context, DownloadInfo.queryDownloadInfo(context, id)); + } + } else if (Constants.ACTION_RESUME.equals(action)) { + long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); + Helpers.scheduleJob(context, DownloadInfo.queryDownloadInfo(context, id)); } } diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java index 92f4c021..efeb272b 100644 --- a/src/com/android/providers/downloads/DownloadStorageProvider.java +++ b/src/com/android/providers/downloads/DownloadStorageProvider.java @@ -459,6 +459,10 @@ public class DownloadStorageProvider extends FileSystemProvider { int extraFlags = Document.FLAG_PARTIAL; final int status = cursor.getInt( cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); + final long progress = cursor.getLong(cursor.getColumnIndexOrThrow( + DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); + final int reason = cursor.getInt( + cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON)); switch (status) { case DownloadManager.STATUS_SUCCESSFUL: // Verify that the document still exists in external storage. This is necessary @@ -470,14 +474,21 @@ public class DownloadStorageProvider extends FileSystemProvider { extraFlags = Document.FLAG_SUPPORTS_RENAME; // only successful is non-partial break; case DownloadManager.STATUS_PAUSED: - summary = getContext().getString(R.string.download_queued); + if (reason == DownloadManager.PAUSED_MANUAL) { + if (size != null) { + long percent = progress * 100 / size; + summary = getContext().getString(R.string.download_paused_percent, percent); + } else { + summary = getContext().getString(R.string.download_paused); + } + } else { + summary = getContext().getString(R.string.download_queued); + } break; case DownloadManager.STATUS_PENDING: summary = getContext().getString(R.string.download_queued); break; case DownloadManager.STATUS_RUNNING: - final long progress = cursor.getLong(cursor.getColumnIndexOrThrow( - DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); if (size != null) { String percent = NumberFormat.getPercentInstance().format((double) progress / size); diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index 9c920053..9dc51af9 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -27,6 +27,7 @@ import static android.provider.Downloads.Impl.STATUS_FILE_ERROR; import static android.provider.Downloads.Impl.STATUS_HTTP_DATA_ERROR; import static android.provider.Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR; import static android.provider.Downloads.Impl.STATUS_PAUSED_BY_APP; +import static android.provider.Downloads.Impl.STATUS_PAUSED_MANUAL; import static android.provider.Downloads.Impl.STATUS_QUEUED_FOR_WIFI; import static android.provider.Downloads.Impl.STATUS_RUNNING; import static android.provider.Downloads.Impl.STATUS_SUCCESS; @@ -156,9 +157,11 @@ public class DownloadThread extends Thread { private static final String NOT_DELETED = COLUMN_DELETED + " == '0'"; private static final String NOT_PAUSED = "(" + COLUMN_CONTROL + " IS NULL OR " + COLUMN_CONTROL + " != '" + CONTROL_PAUSED + "')"; + private static final String NOT_PAUSED_MANUAL = COLUMN_STATUS + " != '" + + STATUS_PAUSED_MANUAL + "'"; private static final String SELECTION_VALID = NOT_CANCELED + " AND " + NOT_DELETED + " AND " - + NOT_PAUSED; + + NOT_PAUSED + " AND " + NOT_PAUSED_MANUAL; public DownloadInfoDelta(DownloadInfo info) { mUri = info.mUri; @@ -208,6 +211,8 @@ public class DownloadThread extends Thread { buildContentValues(), SELECTION_VALID, null) == 0) { if (mInfo.queryDownloadControl() == CONTROL_PAUSED) { throw new StopRequestException(STATUS_PAUSED_BY_APP, "Download paused!"); + } else if (mInfo.queryDownloadStatus() == STATUS_PAUSED_MANUAL) { + throw new StopRequestException(STATUS_PAUSED_MANUAL, "Download paused manually!"); } else { throw new StopRequestException(STATUS_CANCELED, "Download deleted or missing!"); } diff --git a/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java b/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java index 41d7187a..01f3fcc8 100644 --- a/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java +++ b/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java @@ -94,14 +94,28 @@ public class TrampolineActivity extends Activity { Log.d(Constants.TAG, "Found " + id + " with status " + status + ", reason " + reason); switch (status) { case DownloadManager.STATUS_PENDING: - case DownloadManager.STATUS_RUNNING: sendRunningDownloadClickedBroadcast(id); finish(); break; + case DownloadManager.STATUS_RUNNING: + // Pause and resume download manually + dm.pauseDownload(id); + finish(); + break; + case DownloadManager.STATUS_PAUSED: if (reason == DownloadManager.PAUSED_QUEUED_FOR_WIFI) { PausedDialogFragment.show(getFragmentManager(), id, size); + } else if (reason == DownloadManager.PAUSED_MANUAL) { + // Pause and resume download manually + dm.resumeDownload(id); + Intent intent = new Intent(Constants.ACTION_RESUME); + intent.setClassName("com.android.providers.downloads", + "com.android.providers.downloads.DownloadReceiver"); + intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, id); + sendBroadcast(intent); + finish(); } else { sendRunningDownloadClickedBroadcast(id); finish(); -- cgit v1.2.3