summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqinmin <qinmin@chromium.org>2016-01-19 00:56:00 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-19 08:57:56 +0000
commitb2905d4953376dbc18396972249efab6e636b765 (patch)
treeadef8e8e42499d4d913ffedb5b636b91aab04561
parent82b1f7600490a29b3f5e70ceed38f8bab6258c0f (diff)
downloadchromium_src-b2905d4953376dbc18396972249efab6e636b765.zip
chromium_src-b2905d4953376dbc18396972249efab6e636b765.tar.gz
chromium_src-b2905d4953376dbc18396972249efab6e636b765.tar.bz2
Add resume button to paused download notifications and support download resumption
This change adds a resume button to paused download notifications. Here is the UX mock up from rolfe@: https://folio.googleplex.com/chrome-ux/mocks/065-downloads/07-resumable/121615_NotificationChanges When clicking the resume button, chrome will try to launch the browser process to resume download. When browser gets launched, it will wait for download history to be loaded before it can resume. If the targeted download item is not created before time out, failure notification will be shown. BUG=545640 Review URL: https://codereview.chromium.org/1579743003 Cr-Commit-Position: refs/heads/master@{#370086}
-rw-r--r--chrome/android/java/AndroidManifest.xml3
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/DeferredStartupHandler.java4
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastReceiver.java83
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java146
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java82
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/download/OpenDownloadReceiver.java52
-rw-r--r--chrome/android/java/strings/android_chrome_strings.grd3
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java32
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java6
-rw-r--r--chrome/browser/android/chrome_jni_registrar.cc3
-rw-r--r--chrome/browser/android/download/download_manager_service.cc121
-rw-r--r--chrome/browser/android/download/download_manager_service.h76
-rw-r--r--chrome/browser/android/download/download_manager_service_unittest.cc222
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--content/browser/android/download_controller_android_impl.cc4
-rw-r--r--content/browser/android/download_controller_android_impl.h4
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/DownloadController.java8
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/DownloadInfo.java21
-rw-r--r--content/public/browser/android/download_controller_android.h5
20 files changed, 743 insertions, 136 deletions
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 73d3dbe..b21f383 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -642,7 +642,8 @@ android:value="true" />
android:exported="false" />
{% endfor %}
- <receiver android:name="org.chromium.chrome.browser.download.OpenDownloadReceiver">
+ <receiver android:name="org.chromium.chrome.browser.download.DownloadBroadcastReceiver"
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>
</intent-filter>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DeferredStartupHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/DeferredStartupHandler.java
index ac8e02b..79976e2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DeferredStartupHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DeferredStartupHandler.java
@@ -17,7 +17,6 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.bookmarkswidget.BookmarkThumbnailWidgetProviderBase;
import org.chromium.chrome.browser.crash.CrashFileManager;
import org.chromium.chrome.browser.crash.MinidumpUploadService;
-import org.chromium.chrome.browser.download.DownloadManagerService;
import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim;
import org.chromium.chrome.browser.physicalweb.PhysicalWeb;
@@ -107,9 +106,6 @@ public class DeferredStartupHandler {
// Starts syncing with GSA.
application.createGsaHelper().startSync();
- DownloadManagerService.getDownloadManagerService(application)
- .clearPendingDownloadNotifications();
-
application.initializeSharedClasses();
ShareHelper.clearSharedImages(application);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastReceiver.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastReceiver.java
new file mode 100644
index 0000000..65d4f3c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadBroadcastReceiver.java
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download;
+
+import android.app.DownloadManager;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+/**
+ * This {@link BroadcastReceiver} handles clicks to download notifications and their action buttons.
+ * Clicking on an in-progress or failed download will open the download manager. Clicking on
+ * a complete, successful download will open the file. Clicking on the resume button of a paused
+ * download will relaunch the browser process and try to resume the download from where it is
+ * stopped.
+ */
+public class DownloadBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ String action = intent.getAction();
+ switch (action) {
+ case DownloadManager.ACTION_NOTIFICATION_CLICKED:
+ openDownload(context, intent);
+ break;
+ case DownloadNotificationService.ACTION_DOWNLOAD_RESUME:
+ resumeDownload(context, intent);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Called to open a given download item that is downloaded by the android DownloadManager.
+ * @param context Context of the receiver.
+ * @param intent Intent from the android DownloadManager.
+ */
+ private void openDownload(final Context context, Intent intent) {
+ long ids[] =
+ intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
+ if (ids == null || ids.length == 0) {
+ DownloadManagerService.openDownloadsPage(context);
+ return;
+ }
+ long id = ids[0];
+ DownloadManager manager =
+ (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ Uri uri = manager.getUriForDownloadedFile(id);
+ if (uri == null) {
+ // Open the downloads page
+ DownloadManagerService.openDownloadsPage(context);
+ } else {
+ Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+ launchIntent.setDataAndType(uri, manager.getMimeTypeForDownloadedFile(id));
+ launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ context.startActivity(launchIntent);
+ } catch (ActivityNotFoundException e) {
+ DownloadManagerService.openDownloadsPage(context);
+ }
+ }
+ }
+
+ /**
+ * Called to handle download resumption. This will call the DownloadNotificationService
+ * to start the browser process asynchronously, and resume the download afterwards.
+ * @param context Context of the receiver.
+ * @param intent Intent from the android DownloadManager.
+ */
+ private void resumeDownload(final Context context, Intent intent) {
+ if (DownloadNotificationService.isDownloadResumptionIntent(intent)) {
+ Intent launchIntent = new Intent(intent);
+ launchIntent.setComponent(new ComponentName(
+ context.getPackageName(), DownloadNotificationService.class.getName()));
+ context.startService(launchIntent);
+ }
+ }
+} \ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index 60fd6c0..d6c3505 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -24,6 +24,7 @@ import android.util.Pair;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.SuppressFBWarnings;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler;
@@ -91,6 +92,7 @@ public class DownloadManagerService extends BroadcastReceiver implements
new LongSparseArray<DownloadInfo>();
private OMADownloadHandler mOMADownloadHandler;
private DownloadSnackbarController mDownloadSnackbarController;
+ private long mNativeDownloadManagerService;
/**
* Enum representing status of a download.
@@ -156,6 +158,50 @@ public class DownloadManagerService extends BroadcastReceiver implements
}
/**
+ * Class representing a pending notification entry.
+ */
+ @VisibleForTesting
+ static class PendingNotification {
+ public final int downloadId;
+ public final String fileName;
+ public final boolean isResumable;
+
+ PendingNotification(int downloadId, String fileName, boolean isResumable) {
+ this.downloadId = downloadId;
+ this.fileName = fileName;
+ this.isResumable = isResumable;
+ }
+
+ /**
+ * Parse the pending notification from a String object in SharedPrefs.
+ *
+ * @param notification String containing the notification ID, file name and whether it is
+ * resumable.
+ * @return a PendingNotification object.
+ */
+ static PendingNotification parseFromString(String notification) {
+ String[] values = notification.split(",", 3);
+ if (values.length == 3) {
+ try {
+ int id = Integer.parseInt(values[0]);
+ boolean isResumable = "1".equals(values[1]);
+ return new PendingNotification(id, values[2], isResumable);
+ } catch (NumberFormatException nfe) {
+ Log.w(TAG, "Exception while parsing pending download:" + notification);
+ }
+ }
+ return new PendingNotification(-1, "", false);
+ }
+
+ /**
+ * Generate a string for the PendingNotification instance to be inserted into SharedPrefs.
+ */
+ String getNotificationString() {
+ return downloadId + "," + (isResumable ? "1" : "0") + "," + fileName;
+ }
+ }
+
+ /**
* Creates DownloadManagerService.
*/
@SuppressFBWarnings("LI_LAZY_INIT") // Findbugs doesn't see this is only UI thread.
@@ -201,6 +247,16 @@ public class DownloadManagerService extends BroadcastReceiver implements
mIsUIUpdateScheduled = new AtomicBoolean(false);
mOMADownloadHandler = new OMADownloadHandler(context);
mDownloadSnackbarController = new DownloadSnackbarController(context);
+ clearPendingDownloadNotifications();
+ // Note that this technically leaks the native object, however, DownloadManagerService
+ // is a singleton that lives forever and there's no clean shutdown of Chrome on Android.
+ init();
+ }
+
+ @VisibleForTesting
+ protected void init() {
+ mNativeDownloadManagerService = nativeInit();
+ DownloadController.setDownloadNotificationService(this);
}
@Override
@@ -229,15 +285,9 @@ public class DownloadManagerService extends BroadcastReceiver implements
if (mSharedPrefs.contains(DOWNLOAD_NOTIFICATION_IDS)) {
mSharedPrefs.edit().remove(DOWNLOAD_NOTIFICATION_IDS).apply();
}
- List<Pair<Integer, String>> notifications =
- parseDownloadNotificationsFromSharedPrefs(mSharedPrefs);
- for (Pair<Integer, String> notification : notifications) {
- if (notification.first > 0) {
- mDownloadNotifier.cancelNotification(notification.first);
- Log.w(TAG, "Download failed: Cleared download id:" + notification.first);
- }
+ if (mSharedPrefs.contains(PENDING_DOWNLOAD_NOTIFICATIONS)) {
+ mSharedPrefs.edit().remove(PENDING_DOWNLOAD_NOTIFICATIONS).apply();
}
- mSharedPrefs.edit().remove(PENDING_DOWNLOAD_NOTIFICATIONS).apply();
if (mSharedPrefs.contains(PENDING_OMA_DOWNLOADS)) {
Set<String> omaDownloads = getStoredDownloadInfo(mSharedPrefs, PENDING_OMA_DOWNLOADS);
for (String omaDownload : omaDownloads) {
@@ -252,14 +302,14 @@ public class DownloadManagerService extends BroadcastReceiver implements
* @param sharedPrefs SharedPreferences that contains the download notifications.
* @return a list of parsed notifications.
*/
- static List<Pair<Integer, String>> parseDownloadNotificationsFromSharedPrefs(
+ static List<PendingNotification> parseDownloadNotificationsFromSharedPrefs(
SharedPreferences sharedPrefs) {
- List<Pair<Integer, String>> result = new ArrayList<Pair<Integer, String>>();
+ List<PendingNotification> result = new ArrayList<PendingNotification>();
if (sharedPrefs.contains(DownloadManagerService.PENDING_DOWNLOAD_NOTIFICATIONS)) {
Set<String> pendingDownloads = DownloadManagerService.getStoredDownloadInfo(
sharedPrefs, DownloadManagerService.PENDING_DOWNLOAD_NOTIFICATIONS);
for (String download : pendingDownloads) {
- result.add(DownloadManagerService.parseNotificationString(download));
+ result.add(PendingNotification.parseFromString(download));
}
}
return result;
@@ -354,36 +404,6 @@ public class DownloadManagerService extends BroadcastReceiver implements
}
/**
- * Parse the notification ID from a String object in SharedPrefs.
- *
- * @param notification String containing the notification ID and file name.
- * @return a pair of notification ID and file name.
- */
- static Pair<Integer, String> parseNotificationString(String notification) {
- int index = notification.indexOf(",");
- if (index <= 0) return Pair.create(-1, "");
- try {
- int id = Integer.parseInt(notification.substring(0, index));
- return Pair.create(id, notification.substring(index + 1));
- } catch (NumberFormatException nfe) {
- Log.w(TAG, "Exception while parsing pending download:" + notification);
- return Pair.create(-1, "");
- }
- }
-
- /**
- * Generate a string for the download Id and file name pair to be
- * inserted into SharedPrefs.
- *
- * @param downloadId ID of the download.
- * @param fileName Name of the file to be downloaded.
- * @return notification string containing the notification ID and file name.
- */
- static String getNotificationString(int downloadId, String fileName) {
- return downloadId + "," + fileName;
- }
-
- /**
* Broadcast that a download was successful.
* @param downloadInfo info about the download.
*/
@@ -408,8 +428,8 @@ public class DownloadManagerService extends BroadcastReceiver implements
Set<String> pendingDownloads =
getStoredDownloadInfo(mSharedPrefs, PENDING_DOWNLOAD_NOTIFICATIONS);
for (String download : pendingDownloads) {
- Pair<Integer, String> notification = parseNotificationString(download);
- if (notification.first == downloadId) {
+ PendingNotification notification = PendingNotification.parseFromString(download);
+ if (notification.downloadId == downloadId) {
pendingDownloads.remove(download);
storeDownloadInfo(PENDING_DOWNLOAD_NOTIFICATIONS, pendingDownloads);
break;
@@ -418,15 +438,14 @@ public class DownloadManagerService extends BroadcastReceiver implements
}
/**
- * Add a pending download to SharedPrefs, the string consists of both the download ID
- * and file name.
- * @param downloadId ID to be stored.
- * @param fileName Name of the file, used for notifications.
+ * Add a pending download to SharedPrefs, the string consists of the download ID, file name and
+ * whether it is resumable.
+ * @param pendingNotification Pending download entry.
*/
- private void addPendingDownloadToSharedPrefs(int downloadId, String fileName) {
+ private void addPendingDownloadToSharedPrefs(PendingNotification pendingNotification) {
Set<String> pendingDownloads =
getStoredDownloadInfo(mSharedPrefs, PENDING_DOWNLOAD_NOTIFICATIONS);
- pendingDownloads.add(getNotificationString(downloadId, fileName));
+ pendingDownloads.add(pendingNotification.getNotificationString());
storeDownloadInfo(PENDING_DOWNLOAD_NOTIFICATIONS, pendingDownloads);
}
@@ -659,7 +678,8 @@ public class DownloadManagerService extends BroadcastReceiver implements
if (status == DownloadStatus.IN_PROGRESS) {
// A new in-progress download, add an entry to shared prefs to make sure
// to clear the notification.
- addPendingDownloadToSharedPrefs(downloadId, downloadInfo.getFileName());
+ addPendingDownloadToSharedPrefs(new PendingNotification(
+ downloadId, downloadInfo.getFileName(), downloadInfo.isResumable()));
}
mDownloadProgressMap.putIfAbsent(downloadId, progress);
} else {
@@ -1071,4 +1091,30 @@ public class DownloadManagerService extends BroadcastReceiver implements
Log.e(TAG, "Cannot find Downloads app", e);
}
}
+
+ /**
+ * Called to resume a paused download.
+ * @param downloadId Id of the download.
+ */
+ void resumeDownload(int downloadId, String fileName) {
+ nativeResumeDownload(mNativeDownloadManagerService, downloadId, fileName);
+ mDownloadNotifier.notifyDownloadProgress(
+ new DownloadInfo.Builder()
+ .setDownloadId(downloadId)
+ .setFileName(fileName)
+ .setPercentCompleted(
+ DownloadNotificationService.INVALID_DOWNLOAD_PERCENTAGE)
+ .build(),
+ 0);
+ }
+
+ @CalledByNative
+ void onResumptionFailed(int downloadId, String fileName) {
+ mDownloadNotifier.notifyDownloadFailed(
+ new DownloadInfo.Builder().setDownloadId(downloadId).setFileName(fileName).build());
+ }
+
+ private native long nativeInit();
+ private native void nativeResumeDownload(
+ long nativeDownloadManagerService, int downloadId, String fileName);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
index 31ed6497..0690a7e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
@@ -8,6 +8,7 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -15,10 +16,16 @@ import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
-import android.util.Pair;
+import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
+import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeApplication;
+import org.chromium.chrome.browser.init.BrowserParts;
+import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
+import org.chromium.chrome.browser.init.EmptyBrowserParts;
+import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.ui.base.LocalizationUtils;
import java.text.NumberFormat;
@@ -30,8 +37,13 @@ import java.util.Locale;
* Chrome gets killed.
*/
public class DownloadNotificationService extends Service {
+ static final String EXTRA_DOWNLOAD_ID = "DownloadId";
+ static final String EXTRA_DOWNLOAD_FILE_NAME = "DownloadFileName";
+ static final String ACTION_DOWNLOAD_RESUME =
+ "org.chromium.chrome.browser.download.DOWNLOAD_RESUME";
+ static final int INVALID_DOWNLOAD_PERCENTAGE = -1;
private static final String NOTIFICATION_NAMESPACE = "DownloadNotificationService";
- private static final int INVALID_DOWNLOAD_PERCENTAGE = -1;
+ private static final String TAG = "DownloadNotification";
private final IBinder mBinder = new LocalBinder();
private NotificationManager mNotificationManager;
private Context mContext;
@@ -75,6 +87,29 @@ public class DownloadNotificationService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ if (isDownloadResumptionIntent(intent)) {
+ final int downloadId = IntentUtils.safeGetIntExtra(
+ intent, DownloadNotificationService.EXTRA_DOWNLOAD_ID, -1);
+ final String fileName = IntentUtils.safeGetStringExtra(
+ intent, DownloadNotificationService.EXTRA_DOWNLOAD_FILE_NAME);
+ BrowserParts parts = new EmptyBrowserParts() {
+ @Override
+ public void finishNativeInitialization() {
+ DownloadManagerService service =
+ DownloadManagerService.getDownloadManagerService(
+ getApplicationContext());
+ service.resumeDownload(downloadId, fileName);
+ }
+ };
+ try {
+ ChromeBrowserInitializer.getInstance(mContext).handlePreNativeStartup(parts);
+ ChromeBrowserInitializer.getInstance(mContext).handlePostNativeStartup(true, parts);
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "Unable to load native library.", e);
+ ChromeApplication.reportStartupErrorAndExit(e);
+ }
+ }
+
// This should restart the service after Chrome gets killed. However, this
// doesn't work on Android 4.4.2.
return START_STICKY;
@@ -123,12 +158,25 @@ public class DownloadNotificationService extends Service {
* Change a download notification to paused state.
* @param downloadId ID of the download.
* @param fileName File name of the download.
+ * @param isResumable whether download is resumable.
*/
- public void notifyDownloadPaused(int downloadId, String fileName) {
+ public void notifyDownloadPaused(int downloadId, String fileName, boolean isResumable) {
NotificationCompat.Builder builder = buildNotification(
android.R.drawable.ic_media_pause,
fileName,
mContext.getResources().getString(R.string.download_notification_paused));
+ if (isResumable) {
+ ComponentName component = new ComponentName(
+ mContext.getPackageName(), DownloadBroadcastReceiver.class.getName());
+ Intent intent = new Intent(ACTION_DOWNLOAD_RESUME);
+ intent.setComponent(component);
+ intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
+ intent.putExtra(EXTRA_DOWNLOAD_FILE_NAME, fileName);
+ builder.addAction(android.R.drawable.stat_sys_download_done,
+ mContext.getResources().getString(R.string.download_notification_resume_button),
+ PendingIntent.getBroadcast(
+ mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+ }
updateNotification(downloadId, builder.build());
}
@@ -171,11 +219,12 @@ public class DownloadNotificationService extends Service {
void pauseAllDownloads() {
SharedPreferences sharedPrefs =
PreferenceManager.getDefaultSharedPreferences(mContext);
- List<Pair<Integer, String>> notifications =
+ List<DownloadManagerService.PendingNotification> notifications =
DownloadManagerService.parseDownloadNotificationsFromSharedPrefs(sharedPrefs);
- for (Pair<Integer, String> notification : notifications) {
- if (notification.first > 0) {
- notifyDownloadPaused(notification.first, notification.second);
+ for (DownloadManagerService.PendingNotification notification : notifications) {
+ if (notification.downloadId > 0) {
+ notifyDownloadPaused(
+ notification.downloadId, notification.fileName, notification.isResumable);
}
}
}
@@ -207,4 +256,23 @@ public class DownloadNotificationService extends Service {
void updateNotification(int id, Notification notification) {
mNotificationManager.notify(NOTIFICATION_NAMESPACE, id, notification);
}
+
+ /**
+ * Checks if an intent is about to resume a download.
+ * @param intent An intent to validate.
+ * @return true if the intent is for download resumption, or false otherwise.
+ */
+ static boolean isDownloadResumptionIntent(Intent intent) {
+ if (!intent.hasExtra(DownloadNotificationService.EXTRA_DOWNLOAD_ID)
+ || !intent.hasExtra(DownloadNotificationService.EXTRA_DOWNLOAD_FILE_NAME)) {
+ return false;
+ }
+ final int downloadId = IntentUtils.safeGetIntExtra(
+ intent, DownloadNotificationService.EXTRA_DOWNLOAD_ID, -1);
+ if (downloadId == -1) return false;
+ final String fileName = IntentUtils.safeGetStringExtra(
+ intent, DownloadNotificationService.EXTRA_DOWNLOAD_FILE_NAME);
+ if (fileName == null) return false;
+ return true;
+ }
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OpenDownloadReceiver.java b/chrome/android/java/src/org/chromium/chrome/browser/download/OpenDownloadReceiver.java
deleted file mode 100644
index b6ee223..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/OpenDownloadReceiver.java
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.download;
-
-import android.app.DownloadManager;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-/**
- * This {@link BroadcastReceiver} handles clicks to notifications that
- * downloads from the browser are in progress/complete. Clicking on an
- * in-progress or failed download will open the download manager. Clicking on
- * a complete, successful download will open the file.
- */
-public class OpenDownloadReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, Intent intent) {
- String action = intent.getAction();
- if (!DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(action)) {
- DownloadManagerService.openDownloadsPage(context);
- return;
- }
- long ids[] = intent.getLongArrayExtra(
- DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
- if (ids == null || ids.length == 0) {
- DownloadManagerService.openDownloadsPage(context);
- return;
- }
- long id = ids[0];
- DownloadManager manager = (DownloadManager) context.getSystemService(
- Context.DOWNLOAD_SERVICE);
- Uri uri = manager.getUriForDownloadedFile(id);
- if (uri == null) {
- // Open the downloads page
- DownloadManagerService.openDownloadsPage(context);
- } else {
- Intent launchIntent = new Intent(Intent.ACTION_VIEW);
- launchIntent.setDataAndType(uri, manager.getMimeTypeForDownloadedFile(id));
- launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- context.startActivity(launchIntent);
- } catch (ActivityNotFoundException e) {
- DownloadManagerService.openDownloadsPage(context);
- }
- }
- }
-} \ No newline at end of file
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 8b9b17c..cbaaf07 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -1453,6 +1453,9 @@ You are signing in with a managed account and giving its administrator control o
<message name="IDS_DOWNLOAD_NOTIFICATION_PAUSED" desc="Download notification to be displayed when a download pauses.">
Download paused.
</message>
+ <message name="IDS_DOWNLOAD_NOTIFICATION_RESUME_BUTTON" desc="Text on the button that resumes a paused download.">
+ Resume
+ </message>
<message name="IDS_DOWNLOAD_FAILED_REASON_FILE_ALREADY_EXISTS" desc="Message to explain that the download failed because file already exists.">
<ph name="FILE_NAME">%1$s<ex>http://abc.com/test.pdf</ex></ph> download prevented because file already exists.
</message>
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
index d482a98..efa4f28 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
@@ -255,6 +255,9 @@ public class DownloadManagerServiceTest extends InstrumentationTestCase {
protected long addCompletedDownload(DownloadInfo downloadInfo) {
return 1L;
}
+
+ @Override
+ protected void init() {}
}
private static Handler getTestHandler() {
@@ -419,15 +422,11 @@ public class DownloadManagerServiceTest extends InstrumentationTestCase {
assertTrue("All downloads should be updated.", matchSet.mMatches.isEmpty());
// Check if notifications are removed when clearPendingNotifications is called.
- matchSet = new OneTimeMatchSet(download1.getDownloadId(),
- download2.getDownloadId(), download3.getDownloadId());
- notifier.expect(MethodID.CANCEL_DOWNLOAD_ID, matchSet)
- .andThen(MethodID.CANCEL_DOWNLOAD_ID, matchSet)
- .andThen(MethodID.CANCEL_DOWNLOAD_ID, matchSet);
-
dService.clearPendingDownloadNotifications();
- notifier.waitTillExpectedCallsComplete();
- assertTrue("All downloads should be removed.", matchSet.mMatches.isEmpty());
+ Set<String> downloads = dService.getStoredDownloadInfo(
+ PreferenceManager.getDefaultSharedPreferences(getTestContext()),
+ DownloadManagerService.PENDING_DOWNLOAD_NOTIFICATIONS);
+ assertTrue("All downloads should be removed.", downloads.isEmpty());
}
/**
@@ -567,4 +566,21 @@ public class DownloadManagerServiceTest extends InstrumentationTestCase {
.build()));
}
+ @SmallTest
+ @Feature({"Download"})
+ public void testParseDownloadNotifications() {
+ String notification = "1,0,test.pdf";
+ DownloadManagerService.PendingNotification pendingNotification =
+ DownloadManagerService.PendingNotification.parseFromString(notification);
+ assertEquals(1, pendingNotification.downloadId);
+ assertEquals("test.pdf", pendingNotification.fileName);
+ assertFalse(pendingNotification.isResumable);
+
+ notification = "2,1,test,2.pdf";
+ pendingNotification =
+ DownloadManagerService.PendingNotification.parseFromString(notification);
+ assertEquals(2, pendingNotification.downloadId);
+ assertEquals("test,2.pdf", pendingNotification.fileName);
+ assertTrue(pendingNotification.isResumable);
+ }
}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
index a92991c..51507d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java
@@ -73,8 +73,10 @@ public class DownloadNotificationServiceTest extends
Context mockContext = new AdvancedMockContext(getSystemContext());
getService().setContext(mockContext);
Set<String> notifications = new HashSet<String>();
- notifications.add(DownloadManagerService.getNotificationString(1, "test1"));
- notifications.add(DownloadManagerService.getNotificationString(2, "test2"));
+ notifications.add(new DownloadManagerService.PendingNotification(
+ 1, "test1", true).getNotificationString());
+ notifications.add(new DownloadManagerService.PendingNotification(
+ 2, "test2", true).getNotificationString());
SharedPreferences sharedPrefs =
PreferenceManager.getDefaultSharedPreferences(mockContext);
SharedPreferences.Editor editor = sharedPrefs.edit();
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index a0f30cc..ec04fc9 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -39,6 +39,7 @@
#include "chrome/browser/android/document/document_web_contents_delegate.h"
#include "chrome/browser/android/dom_distiller/distiller_ui_handle_android.h"
#include "chrome/browser/android/download/chrome_download_delegate.h"
+#include "chrome/browser/android/download/download_manager_service.h"
#include "chrome/browser/android/favicon_helper.h"
#include "chrome/browser/android/feature_utilities.h"
#include "chrome/browser/android/feedback/connectivity_checker.h"
@@ -255,6 +256,8 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"DomDistillerServiceFactory",
dom_distiller::android::DomDistillerServiceFactoryAndroid::Register},
{"DomDistillerTabUtils", RegisterDomDistillerTabUtils},
+ {"DownloadManagerService",
+ DownloadManagerService::RegisterDownloadManagerService},
{"DownloadOverwriteInfoBarDelegate",
RegisterDownloadOverwriteInfoBarDelegate},
{"EditBookmarkHelper", RegisterEditBookmarkHelper},
diff --git a/chrome/browser/android/download/download_manager_service.cc b/chrome/browser/android/download/download_manager_service.cc
new file mode 100644
index 0000000..3d4fe8a
--- /dev/null
+++ b/chrome/browser/android/download/download_manager_service.cc
@@ -0,0 +1,121 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/download/download_manager_service.h"
+
+#include "base/android/jni_string.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "content/public/browser/android/download_controller_android.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_item.h"
+#include "jni/DownloadManagerService_jni.h"
+
+using base::android::JavaParamRef;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+
+namespace {
+// The retry interval when resuming a download. This is needed because
+// when the browser process is launched, we have to wait until the download
+// history get loaded before a download can be resumed. However, we don't want
+// to retry after a long period of time as the same download Id can be reused
+// later.
+const int kResumeRetryIntervalInMilliseconds = 3000;
+}
+
+// static
+bool DownloadManagerService::RegisterDownloadManagerService(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+static jlong Init(JNIEnv* env, const JavaParamRef<jobject>& jobj) {
+ Profile* profile = ProfileManager::GetActiveUserProfile();
+ content::DownloadManager* manager =
+ content::BrowserContext::GetDownloadManager(profile);
+ DownloadManagerService* service =
+ new DownloadManagerService(env, jobj, manager);
+ return reinterpret_cast<intptr_t>(service);
+}
+
+DownloadManagerService::DownloadManagerService(
+ JNIEnv* env,
+ jobject obj,
+ content::DownloadManager* manager)
+ : java_ref_(env, obj), manager_(manager) {
+ manager_->AddObserver(this);
+}
+
+DownloadManagerService::~DownloadManagerService() {
+ if (manager_)
+ manager_->RemoveObserver(this);
+}
+
+void DownloadManagerService::ResumeDownload(JNIEnv* env,
+ jobject obj,
+ uint32_t download_id,
+ jstring fileName) {
+ ResumeDownloadInternal(download_id, ConvertJavaStringToUTF8(env, fileName),
+ true);
+}
+
+void DownloadManagerService::ManagerGoingDown(
+ content::DownloadManager* manager) {
+ manager_ = nullptr;
+}
+
+void DownloadManagerService::ResumeDownloadItem(content::DownloadItem* item,
+ const std::string& fileName) {
+ if (!item->CanResume()) {
+ OnResumptionFailed(item->GetId(), fileName);
+ return;
+ }
+ item->AddObserver(content::DownloadControllerAndroid::Get());
+ item->Resume();
+ if (!resume_callback_for_testing_.is_null())
+ resume_callback_for_testing_.Run(true);
+}
+
+void DownloadManagerService::ResumeDownloadInternal(uint32_t download_id,
+ const std::string& fileName,
+ bool retry) {
+ if (!manager_) {
+ OnResumptionFailed(download_id, fileName);
+ return;
+ }
+ content::DownloadItem* item = manager_->GetDownload(download_id);
+ if (item) {
+ ResumeDownloadItem(item, fileName);
+ return;
+ }
+ if (!retry) {
+ OnResumptionFailed(download_id, fileName);
+ return;
+ }
+ // Post a delayed task to wait for the download history to load the download
+ // item. If the download item is not loaded when the delayed task runs, show
+ // an download failed notification. Alternatively, we can have the
+ // DownloadManager inform us when a download item is created. However, there
+ // is no guarantee when the download item will be created, since the newly
+ // created item might not be loaded from download history. So user might wait
+ // indefinitely to see the failed notification. See http://crbug.com/577893.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DownloadManagerService::ResumeDownloadInternal,
+ base::Unretained(this), download_id, fileName, false),
+ base::TimeDelta::FromMilliseconds(kResumeRetryIntervalInMilliseconds));
+}
+
+void DownloadManagerService::OnResumptionFailed(uint32_t download_id,
+ const std::string& fileName) {
+ if (!java_ref_.is_null()) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_DownloadManagerService_onResumptionFailed(
+ env, java_ref_.obj(), download_id,
+ ConvertUTF8ToJavaString(env, fileName).obj());
+ }
+ if (!resume_callback_for_testing_.is_null())
+ resume_callback_for_testing_.Run(false);
+}
diff --git a/chrome/browser/android/download/download_manager_service.h b/chrome/browser/android/download/download_manager_service.h
new file mode 100644
index 0000000..6d5a935
--- /dev/null
+++ b/chrome/browser/android/download/download_manager_service.h
@@ -0,0 +1,76 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_DOWNLOAD_DOWNLOAD_MANAGER_SERVICE_H_
+#define CHROME_BROWSER_ANDROID_DOWNLOAD_DOWNLOAD_MANAGER_SERVICE_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "content/public/browser/download_manager.h"
+
+namespace content {
+class DownloadItem;
+}
+
+// Native side of DownloadManagerService.java. The native object is owned by its
+// Java object.
+class DownloadManagerService : public content::DownloadManager::Observer {
+ public:
+ // JNI registration.
+ static bool RegisterDownloadManagerService(JNIEnv* env);
+
+ DownloadManagerService(JNIEnv* env,
+ jobject jobj,
+ content::DownloadManager* manager);
+ ~DownloadManagerService() override;
+
+ // Called to resume downloading the item that has ID equal to |download_id|.
+ // If the DownloadItem is not yet created, put the ID into
+ // |pending_download_ids_|.
+ void ResumeDownload(JNIEnv* env,
+ jobject obj,
+ uint32_t download_id,
+ jstring fileName);
+
+ // content::DownloadManager::Observer methods.
+ void ManagerGoingDown(content::DownloadManager* manager) override;
+
+ private:
+ // For testing.
+ friend class DownloadManagerServiceTest;
+
+ // Resume downloading the given DownloadItem.
+ void ResumeDownloadItem(content::DownloadItem* item,
+ const std::string& fileName);
+
+ // Helper function to start the download resumption. If |retry| is true,
+ // chrome will retry the resumption if the download item is not loaded.
+ void ResumeDownloadInternal(uint32_t download_id,
+ const std::string& fileName,
+ bool retry);
+
+ // Called to notify the java side that download resumption failed.
+ void OnResumptionFailed(uint32_t download_id, const std::string& fileName);
+
+ typedef base::Callback<void(bool)> ResumeCallback;
+ void set_resume_callback_for_testing(const ResumeCallback& resume_cb) {
+ resume_callback_for_testing_ = resume_cb;
+ }
+
+ // Reference to the Java object.
+ base::android::ScopedJavaGlobalRef<jobject> java_ref_;
+
+ // Download manager this class observes
+ content::DownloadManager* manager_;
+
+ ResumeCallback resume_callback_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadManagerService);
+};
+
+#endif // CHROME_BROWSER_ANDROID_DOWNLOAD_DOWNLOAD_MANAGER_SERVICE_H_
diff --git a/chrome/browser/android/download/download_manager_service_unittest.cc b/chrome/browser/android/download/download_manager_service_unittest.cc
new file mode 100644
index 0000000..c400c8a
--- /dev/null
+++ b/chrome/browser/android/download/download_manager_service_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/download/download_manager_service.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_url_parameters.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
+
+using ::testing::_;
+
+namespace content {
+class BrowserContext;
+class ByteStreamReader;
+class DownloadManagerDelegate;
+struct DownloadCreateInfo;
+}
+
+// Mock implementation of content::DownloadItem.
+class MockDownloadItem : public content::DownloadItem {
+ public:
+ explicit MockDownloadItem(bool can_resume) : can_resume_(can_resume) {}
+ ~MockDownloadItem() override {}
+ bool CanResume() const override { return can_resume_; }
+
+ MOCK_METHOD1(AddObserver, void(content::DownloadItem::Observer*));
+ MOCK_METHOD1(RemoveObserver, void(content::DownloadItem::Observer*));
+ MOCK_METHOD0(UpdateObservers, void());
+ MOCK_METHOD0(ValidateDangerousDownload, void());
+ MOCK_METHOD1(StealDangerousDownload,
+ void(const content::DownloadItem::AcquireFileCallback&));
+ MOCK_METHOD0(Pause, void());
+ MOCK_METHOD0(Resume, void());
+ MOCK_METHOD1(Cancel, void(bool));
+ MOCK_METHOD0(Remove, void());
+ MOCK_METHOD0(OpenDownload, void());
+ MOCK_METHOD0(ShowDownloadInShell, void());
+ MOCK_CONST_METHOD0(GetId, uint32_t());
+ MOCK_CONST_METHOD0(GetState, content::DownloadItem::DownloadState());
+ MOCK_CONST_METHOD0(GetLastReason, content::DownloadInterruptReason());
+ MOCK_CONST_METHOD0(IsPaused, bool());
+ MOCK_CONST_METHOD0(IsTemporary, bool());
+ MOCK_CONST_METHOD0(IsDone, bool());
+ MOCK_CONST_METHOD0(GetURL, const GURL&());
+ MOCK_CONST_METHOD0(GetUrlChain, std::vector<GURL>&());
+ MOCK_CONST_METHOD0(GetOriginalUrl, const GURL&());
+ MOCK_CONST_METHOD0(GetReferrerUrl, const GURL&());
+ MOCK_CONST_METHOD0(GetTabUrl, const GURL&());
+ MOCK_CONST_METHOD0(GetTabReferrerUrl, const GURL&());
+ MOCK_CONST_METHOD0(GetSuggestedFilename, std::string());
+ MOCK_CONST_METHOD0(GetContentDisposition, std::string());
+ MOCK_CONST_METHOD0(GetMimeType, std::string());
+ MOCK_CONST_METHOD0(GetOriginalMimeType, std::string());
+ MOCK_CONST_METHOD0(GetRemoteAddress, std::string());
+ MOCK_CONST_METHOD0(HasUserGesture, bool());
+ MOCK_CONST_METHOD0(GetTransitionType, ui::PageTransition());
+ MOCK_CONST_METHOD0(GetLastModifiedTime, const std::string&());
+ MOCK_CONST_METHOD0(GetETag, const std::string&());
+ MOCK_CONST_METHOD0(IsSavePackageDownload, bool());
+ MOCK_CONST_METHOD0(GetFullPath, const base::FilePath&());
+ MOCK_CONST_METHOD0(GetTargetFilePath, const base::FilePath&());
+ MOCK_CONST_METHOD0(GetForcedFilePath, const base::FilePath&());
+ MOCK_CONST_METHOD0(GetFileNameToReportUser, base::FilePath());
+ MOCK_CONST_METHOD0(GetTargetDisposition,
+ content::DownloadItem::TargetDisposition());
+ MOCK_CONST_METHOD0(GetHash, const std::string&());
+ MOCK_CONST_METHOD0(GetHashState, const std::string&());
+ MOCK_CONST_METHOD0(GetFileExternallyRemoved, bool());
+ MOCK_METHOD1(DeleteFile, void(const base::Callback<void(bool)>&));
+ MOCK_CONST_METHOD0(IsDangerous, bool());
+ MOCK_CONST_METHOD0(GetDangerType, content::DownloadDangerType());
+ MOCK_CONST_METHOD1(TimeRemaining, bool(base::TimeDelta*));
+ MOCK_CONST_METHOD0(CurrentSpeed, int64_t());
+ MOCK_CONST_METHOD0(PercentComplete, int());
+ MOCK_CONST_METHOD0(AllDataSaved, bool());
+ MOCK_CONST_METHOD0(GetTotalBytes, int64_t());
+ MOCK_CONST_METHOD0(GetReceivedBytes, int64_t());
+ MOCK_CONST_METHOD0(GetStartTime, base::Time());
+ MOCK_CONST_METHOD0(GetEndTime, base::Time());
+ MOCK_METHOD0(CanShowInFolder, bool());
+ MOCK_METHOD0(CanOpenDownload, bool());
+ MOCK_METHOD0(ShouldOpenFileBasedOnExtension, bool());
+ MOCK_CONST_METHOD0(GetOpenWhenComplete, bool());
+ MOCK_METHOD0(GetAutoOpened, bool());
+ MOCK_CONST_METHOD0(GetOpened, bool());
+ MOCK_CONST_METHOD0(GetBrowserContext, content::BrowserContext*());
+ MOCK_CONST_METHOD0(GetWebContents, content::WebContents*());
+ MOCK_METHOD1(OnContentCheckCompleted, void(content::DownloadDangerType));
+ MOCK_METHOD1(SetOpenWhenComplete, void(bool));
+ MOCK_METHOD1(SetIsTemporary, void(bool));
+ MOCK_METHOD1(SetOpened, void(bool));
+ MOCK_METHOD1(SetDisplayName, void(const base::FilePath&));
+ MOCK_CONST_METHOD1(DebugString, std::string(bool));
+
+ private:
+ bool can_resume_;
+};
+
+// Mock implementation of content::DownloadManager.
+class MockDownloadManager : public content::DownloadManager {
+ public:
+ MockDownloadManager() {}
+ ~MockDownloadManager() override {}
+
+ MOCK_METHOD1(SetDelegate, void(content::DownloadManagerDelegate*));
+ MOCK_CONST_METHOD0(GetDelegate, content::DownloadManagerDelegate*());
+ MOCK_METHOD0(Shutdown, void());
+ MOCK_METHOD1(GetAllDownloads, void(DownloadVector*));
+ MOCK_METHOD3(RemoveDownloadsByOriginAndTime,
+ int(const url::Origin&, base::Time, base::Time));
+ MOCK_METHOD2(RemoveDownloadsBetween, int(base::Time, base::Time));
+ MOCK_METHOD1(RemoveDownloads, int(base::Time));
+ MOCK_METHOD0(RemoveAllDownloads, int());
+ void DownloadUrl(scoped_ptr<content::DownloadUrlParameters>) override {}
+ MOCK_METHOD1(AddObserver, void(content::DownloadManager::Observer*));
+ MOCK_METHOD1(RemoveObserver, void(content::DownloadManager::Observer*));
+ MOCK_CONST_METHOD0(InProgressCount, int());
+ MOCK_CONST_METHOD0(NonMaliciousInProgressCount, int());
+ MOCK_CONST_METHOD0(GetBrowserContext, content::BrowserContext*());
+ MOCK_METHOD0(CheckForHistoryFilesRemoval, void());
+ void StartDownload(
+ scoped_ptr<content::DownloadCreateInfo>,
+ scoped_ptr<content::ByteStreamReader>,
+ const content::DownloadUrlParameters::OnStartedCallback&) override {}
+ content::DownloadItem* CreateDownloadItem(
+ uint32_t id,
+ const base::FilePath& current_path,
+ const base::FilePath& target_path,
+ const std::vector<GURL>& url_chain,
+ const GURL& referrer_url,
+ const std::string& mime_type,
+ const std::string& original_mime_type,
+ const base::Time& start_time,
+ const base::Time& end_time,
+ const std::string& etag,
+ const std::string& last_modified,
+ int64_t received_bytes,
+ int64_t total_bytes,
+ content::DownloadItem::DownloadState state,
+ content::DownloadDangerType danger_type,
+ content::DownloadInterruptReason interrupt_reason,
+ bool opened) override {
+ return nullptr;
+ }
+ content::DownloadItem* GetDownload(uint32_t id) override {
+ return download_item_.get();
+ }
+ void SetDownload(MockDownloadItem* item) { download_item_.reset(item); }
+
+ private:
+ scoped_ptr<MockDownloadItem> download_item_;
+};
+
+class DownloadManagerServiceTest : public testing::Test {
+ public:
+ DownloadManagerServiceTest()
+ : service_(
+ new DownloadManagerService(base::android::AttachCurrentThread(),
+ nullptr,
+ &manager_)),
+ finished_(false),
+ success_(false) {}
+
+ void OnResumptionDone(bool success) {
+ finished_ = true;
+ success_ = success;
+ }
+
+ void StartDownload(int download_id) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ service_->set_resume_callback_for_testing(base::Bind(
+ &DownloadManagerServiceTest::OnResumptionDone, base::Unretained(this)));
+ service_->ResumeDownload(
+ env, nullptr, download_id,
+ base::android::ConvertUTF8ToJavaString(env, "test").obj());
+ while (!finished_)
+ message_loop_.RunUntilIdle();
+ }
+
+ void CreateDownloadItem(bool can_resume) {
+ manager_.SetDownload(new MockDownloadItem(can_resume));
+ }
+
+ protected:
+ base::MessageLoop message_loop_;
+ MockDownloadManager manager_;
+ DownloadManagerService* service_;
+ bool finished_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadManagerServiceTest);
+};
+
+// Test that resumption will fail if no download item is found before times out.
+TEST_F(DownloadManagerServiceTest, ResumptionTimeOut) {
+ StartDownload(1);
+ EXPECT_FALSE(success_);
+}
+
+// Test that resumption succeeds if the download item is found and can be
+// resumed.
+TEST_F(DownloadManagerServiceTest, ResumptionWithResumableItem) {
+ CreateDownloadItem(true);
+ StartDownload(1);
+ EXPECT_TRUE(success_);
+}
+
+// Test that resumption fails if the target download item is not resumable.
+TEST_F(DownloadManagerServiceTest, ResumptionWithNonResumableItem) {
+ CreateDownloadItem(false);
+ StartDownload(1);
+ EXPECT_FALSE(success_);
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index aec8cb2..cd435cc 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -738,6 +738,8 @@
'browser/android/download/chrome_download_delegate.h',
'browser/android/download/chrome_download_manager_overwrite_infobar_delegate.cc',
'browser/android/download/chrome_download_manager_overwrite_infobar_delegate.h',
+ 'browser/android/download/download_manager_service.cc',
+ 'browser/android/download/download_manager_service.h',
'browser/android/download/download_overwrite_infobar_delegate.cc',
'browser/android/download/download_overwrite_infobar_delegate.h',
'browser/android/download/mock_download_controller_android.cc',
@@ -1837,6 +1839,7 @@
'android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java',
'android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerUIUtils.java',
'android/java/src/org/chromium/chrome/browser/download/ChromeDownloadDelegate.java',
+ 'android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java',
'android/java/src/org/chromium/chrome/browser/favicon/FaviconHelper.java',
'android/java/src/org/chromium/chrome/browser/favicon/LargeIconBridge.java',
'android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index da03420..3cfed39 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -18,6 +18,7 @@
'browser/android/data_usage/data_use_ui_tab_model_unittest.cc',
'browser/android/data_usage/external_data_use_observer_unittest.cc',
'browser/android/data_usage/tab_data_use_entry_unittest.cc',
+ 'browser/android/download/download_manager_service_unittest.cc',
'browser/android/history_report/delta_file_backend_leveldb_unittest.cc',
'browser/android/history_report/delta_file_commons_unittest.cc',
'browser/android/history_report/usage_reports_buffer_backend_unittest.cc',
diff --git a/content/browser/android/download_controller_android_impl.cc b/content/browser/android/download_controller_android_impl.cc
index feb8de3..a314d08 100644
--- a/content/browser/android/download_controller_android_impl.cc
+++ b/content/browser/android/download_controller_android_impl.cc
@@ -451,7 +451,9 @@ void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem* item) {
base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(),
jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true,
item->GetId(), item->PercentComplete(), time_delta.InMilliseconds(),
- item->HasUserGesture());
+ item->HasUserGesture(),
+ // Get all requirements that allows a download to be resumable.
+ !item->GetBrowserContext()->IsOffTheRecord());
break;
}
case DownloadItem::COMPLETE:
diff --git a/content/browser/android/download_controller_android_impl.h b/content/browser/android/download_controller_android_impl.h
index 36c108e..386135e 100644
--- a/content/browser/android/download_controller_android_impl.h
+++ b/content/browser/android/download_controller_android_impl.h
@@ -30,7 +30,6 @@
#include "base/memory/scoped_vector.h"
#include "base/memory/singleton.h"
#include "content/public/browser/android/download_controller_android.h"
-#include "content/public/browser/download_item.h"
#include "net/cookies/cookie_monster.h"
#include "url/gurl.h"
@@ -44,8 +43,7 @@ class DeferredDownloadObserver;
class RenderViewHost;
class WebContents;
-class DownloadControllerAndroidImpl : public DownloadControllerAndroid,
- public DownloadItem::Observer {
+class DownloadControllerAndroidImpl : public DownloadControllerAndroid {
public:
static DownloadControllerAndroidImpl* GetInstance();
diff --git a/content/public/android/java/src/org/chromium/content/browser/DownloadController.java b/content/public/android/java/src/org/chromium/content/browser/DownloadController.java
index 98c16e1..b190609 100644
--- a/content/public/android/java/src/org/chromium/content/browser/DownloadController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/DownloadController.java
@@ -134,9 +134,10 @@ public class DownloadController {
* network stack use custom notification to display the progress of downloads.
*/
@CalledByNative
- private void onDownloadUpdated(Context context, String url, String mimeType,
- String filename, String path, long contentLength, boolean successful, int downloadId,
- int percentCompleted, long timeRemainingInMs, boolean hasUserGesture) {
+ private void onDownloadUpdated(Context context, String url, String mimeType, String filename,
+ String path, long contentLength, boolean successful, int downloadId,
+ int percentCompleted, long timeRemainingInMs, boolean hasUserGesture,
+ boolean isResumable) {
if (sDownloadNotificationService != null) {
DownloadInfo downloadInfo = new DownloadInfo.Builder()
.setUrl(url)
@@ -151,6 +152,7 @@ public class DownloadController {
.setPercentCompleted(percentCompleted)
.setTimeRemainingInMillis(timeRemainingInMs)
.setHasUserGesture(hasUserGesture)
+ .setIsResumable(isResumable)
.build();
sDownloadNotificationService.onDownloadUpdated(downloadInfo);
}
diff --git a/content/public/android/java/src/org/chromium/content/browser/DownloadInfo.java b/content/public/android/java/src/org/chromium/content/browser/DownloadInfo.java
index 069c6dea..095a3f6 100644
--- a/content/public/android/java/src/org/chromium/content/browser/DownloadInfo.java
+++ b/content/public/android/java/src/org/chromium/content/browser/DownloadInfo.java
@@ -25,6 +25,7 @@ public final class DownloadInfo {
private final boolean mIsSuccessful;
private final int mPercentCompleted;
private final long mTimeRemainingInMillis;
+ private final boolean mIsResumable;
private DownloadInfo(Builder builder) {
mUrl = builder.mUrl;
@@ -44,6 +45,7 @@ public final class DownloadInfo {
mContentDisposition = builder.mContentDisposition;
mPercentCompleted = builder.mPercentCompleted;
mTimeRemainingInMillis = builder.mTimeRemainingInMillis;
+ mIsResumable = builder.mIsResumable;
}
public String getUrl() {
@@ -117,6 +119,13 @@ public final class DownloadInfo {
return mTimeRemainingInMillis;
}
+ public boolean isResumable() {
+ return mIsResumable;
+ }
+
+ /**
+ * Helper class for building the DownloadInfo object.
+ */
public static class Builder {
private String mUrl;
private String mUserAgent;
@@ -135,6 +144,7 @@ public final class DownloadInfo {
private String mContentDisposition;
private int mPercentCompleted = -1;
private long mTimeRemainingInMillis;
+ private boolean mIsResumable = true;
public Builder setUrl(String url) {
mUrl = url;
@@ -222,6 +232,11 @@ public final class DownloadInfo {
return this;
}
+ public Builder setIsResumable(boolean isResumable) {
+ mIsResumable = isResumable;
+ return this;
+ }
+
public DownloadInfo build() {
return new DownloadInfo(this);
}
@@ -233,8 +248,7 @@ public final class DownloadInfo {
*/
public static Builder fromDownloadInfo(final DownloadInfo downloadInfo) {
Builder builder = new Builder();
- builder
- .setUrl(downloadInfo.getUrl())
+ builder.setUrl(downloadInfo.getUrl())
.setUserAgent(downloadInfo.getUserAgent())
.setMimeType(downloadInfo.getMimeType())
.setCookie(downloadInfo.getCookie())
@@ -250,7 +264,8 @@ public final class DownloadInfo {
.setIsGETRequest(downloadInfo.isGETRequest())
.setIsSuccessful(downloadInfo.isSuccessful())
.setPercentCompleted(downloadInfo.getPercentCompleted())
- .setTimeRemainingInMillis(downloadInfo.getTimeRemainingInMillis());
+ .setTimeRemainingInMillis(downloadInfo.getTimeRemainingInMillis())
+ .setIsResumable(downloadInfo.isResumable());
return builder;
}
diff --git a/content/public/browser/android/download_controller_android.h b/content/public/browser/android/download_controller_android.h
index 859a8a0..08fe6f7 100644
--- a/content/public/browser/android/download_controller_android.h
+++ b/content/public/browser/android/download_controller_android.h
@@ -7,6 +7,7 @@
#include "base/callback.h"
#include "content/common/content_export.h"
+#include "content/public/browser/download_item.h"
#include "content/public/common/context_menu_params.h"
namespace content {
@@ -15,7 +16,7 @@ class WebContents;
// Interface to request GET downloads and send notifications for POST
// downloads.
-class CONTENT_EXPORT DownloadControllerAndroid {
+class CONTENT_EXPORT DownloadControllerAndroid : public DownloadItem::Observer {
public:
// Returns the singleton instance of the DownloadControllerAndroid.
static DownloadControllerAndroid* Get();
@@ -57,7 +58,7 @@ class CONTENT_EXPORT DownloadControllerAndroid {
virtual void SetApproveFileAccessRequestForTesting(bool approve) {};
protected:
- virtual ~DownloadControllerAndroid() {};
+ ~DownloadControllerAndroid() override {};
static DownloadControllerAndroid* download_controller_;
};