summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/drawable/download_pause.xml16
-rw-r--r--res/values/cm_plurals.xml33
-rw-r--r--res/values/cm_strings.xml36
-rw-r--r--src/com/android/providers/downloads/Constants.java9
-rw-r--r--src/com/android/providers/downloads/DownloadNotifier.java119
-rw-r--r--src/com/android/providers/downloads/DownloadReceiver.java20
-rw-r--r--src/com/android/providers/downloads/DownloadStorageProvider.java17
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java7
-rw-r--r--ui/src/com/android/providers/downloads/ui/TrampolineActivity.java16
9 files changed, 259 insertions, 14 deletions
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <group>
+ <path
+ android:fillColor="#fafafa"
+ android:pathData="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ </group>
+</vector>
diff --git a/res/values/cm_plurals.xml b/res/values/cm_plurals.xml
new file mode 100644
index 00000000..b201ff51
--- /dev/null
+++ b/res/values/cm_plurals.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The CyanogenMod Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Phrase describing a time duration using seconds [CHAR LIMIT=16] -->
+ <plurals name="duration_seconds">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> sec</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> secs</item>
+ </plurals>
+ <!-- Phrase describing a time duration using minutes [CHAR LIMIT=16] -->
+ <plurals name="duration_minutes">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> mins</item>
+ </plurals>
+ <!-- Phrase describing a time duration using hours [CHAR LIMIT=16] -->
+ <plurals name="duration_hours">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> hr</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> hrs</item>
+ </plurals>
+</resources>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
new file mode 100644
index 00000000..38964af0
--- /dev/null
+++ b/res/values/cm_strings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013-2016 The CyanogenMod Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Notification text containing download duration left and download speed -->
+ <string name="text_download_speed"><xliff:g id="text">%1$s</xliff:g>, <xliff:g id="size" example="230 kB">%2$s</xliff:g>/s</string>
+
+ <!-- Status indicating that the download has been paused to start in the future. Appears for an
+ individual item in the download list. [CHAR LIMIT=24] -->
+ <string name="download_paused">Paused</string>
+
+ <!-- Representation of download progress percentage when paused. [CHAR LIMIT=24] -->
+ <string name="download_paused_percent">Paused, %d%%</string>
+
+ <!-- Text for button to manually resume all manually paused downloads [CHAR LIMIT=25] -->
+ <string name="button_resume_download">Resume</string>
+
+ <!-- Text for button to manually manually pause all running downloads [CHAR LIMIT=25] -->
+ <string name="button_pause_download">Pause</string>
+
+ <!-- Title summarizing paused downloads. [CHAR LIMIT=32] -->
+ <string name="notif_summary_paused">%d paused</string>
+</resources>
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 d38aa755..db24db66 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;
@@ -40,6 +41,7 @@ import android.provider.Downloads;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.Log;
@@ -47,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;
@@ -221,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);
@@ -252,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);
@@ -285,6 +344,7 @@ public class DownloadNotifier {
// Calculate and show progress
String remainingText = null;
String percentText = null;
+ String speedAsSizeText = null;
if (type == TYPE_ACTIVE) {
long current = 0;
long total = 0;
@@ -311,8 +371,26 @@ public class DownloadNotifier {
if (speed > 0) {
final long remainingMillis = ((total - current) * 1000) / speed;
+ final int duration, durationResId;
+
+ // This duplicates DateUtils.formatDuration(),
+ // but uses our abbreviated plurals.
+ if (remainingMillis >= DateUtils.HOUR_IN_MILLIS) {
+ duration = (int) ((remainingMillis + 1800000)
+ / DateUtils.HOUR_IN_MILLIS);
+ durationResId = R.plurals.duration_hours;
+ } else if (remainingMillis >= DateUtils.MINUTE_IN_MILLIS) {
+ duration = (int) ((remainingMillis + 30000)
+ / DateUtils.MINUTE_IN_MILLIS);
+ durationResId = R.plurals.duration_minutes;
+ } else {
+ duration = (int) ((remainingMillis + 500)
+ / DateUtils.SECOND_IN_MILLIS);
+ durationResId = R.plurals.duration_seconds;
+ }
remainingText = res.getString(R.string.download_remaining,
- DateUtils.formatDuration(remainingMillis));
+ res.getQuantityString(durationResId, duration, duration));
+ speedAsSizeText = Formatter.formatFileSize(mContext, speed);
}
final int percent = (int) ((current * 100) / total);
@@ -329,11 +407,18 @@ public class DownloadNotifier {
builder.setContentTitle(getDownloadTitle(res, cursor));
if (type == TYPE_ACTIVE) {
- final String description = cursor.getString(UpdateQuery.DESCRIPTION);
- if (!TextUtils.isEmpty(description)) {
- builder.setContentText(description);
+ 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 {
- builder.setContentText(remainingText);
+ final String description = cursor.getString(UpdateQuery.DESCRIPTION);
+ if (!TextUtils.isEmpty(description)) {
+ builder.setContentText(description);
+ } else {
+ builder.setContentText(remainingText);
+ }
}
builder.setContentInfo(percentText);
@@ -362,11 +447,27 @@ 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);
- inboxStyle.setSummaryText(remainingText);
+ if (speedAsSizeText != null) {
+ inboxStyle.setSummaryText(res.getString(R.string.text_download_speed,
+ remainingText, speedAsSizeText));
+ } else {
+ inboxStyle.setSummaryText(remainingText);
+ }
} else if (type == TYPE_WAITING) {
builder.setContentTitle(res.getQuantityString(
@@ -461,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 40b5e093..acd7070c 100644
--- a/src/com/android/providers/downloads/DownloadReceiver.java
+++ b/src/com/android/providers/downloads/DownloadReceiver.java
@@ -122,6 +122,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 fc7dd5ed..051cf35c 100644
--- a/src/com/android/providers/downloads/DownloadStorageProvider.java
+++ b/src/com/android/providers/downloads/DownloadStorageProvider.java
@@ -649,6 +649,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
@@ -660,14 +664,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 > 0) {
+ 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 > 0) {
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 bc7997f6..15b17297 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -29,6 +29,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;
@@ -159,9 +160,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;
@@ -211,6 +214,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 b3c08131..bc4a3c7d 100644
--- a/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java
+++ b/ui/src/com/android/providers/downloads/ui/TrampolineActivity.java
@@ -106,14 +106,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();