Add "Cancel" action to downloads in notification.
Oren Blasberg [Fri, 19 Jun 2015 18:31:38 +0000 (11:31 -0700)]
Add a "Cancel" action to in-progress downloads shown in notification
pane.
We add a new action type for a new "cancel" intent sent by
DownloadNotifier to DownloadReceiver, which in turn cancels the
download by way of DownloadManager.

BUG=19972464

Change-Id: I83cd2f40e1442c327f756027b99f9eac913a0e70

src/com/android/providers/downloads/Constants.java
src/com/android/providers/downloads/DownloadNotifier.java
src/com/android/providers/downloads/DownloadReceiver.java
tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java

index 7b8fcd2..6cea808 100644 (file)
@@ -54,6 +54,9 @@ public class Constants {
     /** the intent that gets sent when clicking an incomplete/failed download  */
     public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST";
 
+    /** the intent that gets sent when canceling a download  */
+    public static final String ACTION_CANCEL = "android.intent.action.DOWNLOAD_CANCEL";
+
     /** the intent that gets sent when deleting the notification of a completed download */
     public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE";
 
index 60c249f..2ff8b63 100644 (file)
@@ -122,6 +122,15 @@ public class DownloadNotifier {
         }
     }
 
+    private static boolean isClusterDeleted(Collection<DownloadInfo> cluster) {
+        boolean wasDeleted = true;
+        for (DownloadInfo info : cluster) {
+            wasDeleted = wasDeleted && info.mDeleted;
+        }
+
+        return wasDeleted;
+    }
+
     private void updateWithLocked(Collection<DownloadInfo> downloads) {
         final Resources res = mContext.getResources();
 
@@ -139,6 +148,11 @@ public class DownloadNotifier {
             final int type = getNotificationTagType(tag);
             final Collection<DownloadInfo> cluster = clustered.get(tag);
 
+            // If each of the downloads was canceled, don't show notification for the cluster
+            if (isClusterDeleted(cluster)) {
+                continue;
+            }
+
             final Notification.Builder builder = new Notification.Builder(mContext);
             builder.setColor(res.getColor(
                     com.android.internal.R.color.system_notification_accent_color));
@@ -164,16 +178,31 @@ public class DownloadNotifier {
 
             // Build action intents
             if (type == TYPE_ACTIVE || type == TYPE_WAITING) {
+                long[] downloadIds = getDownloadIds(cluster);
+
                 // build a synthetic uri for intent identification purposes
                 final Uri uri = new Uri.Builder().scheme("active-dl").appendPath(tag).build();
                 final Intent intent = new Intent(Constants.ACTION_LIST,
                         uri, mContext, DownloadReceiver.class);
                 intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,
-                        getDownloadIds(cluster));
+                        downloadIds);
                 builder.setContentIntent(PendingIntent.getBroadcast(mContext,
                         0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                 builder.setOngoing(true);
 
+                // Add a Cancel action
+                final Uri cancelUri = new Uri.Builder().scheme("cancel-dl").appendPath(tag).build();
+                final Intent cancelIntent = new Intent(Constants.ACTION_CANCEL,
+                        cancelUri, mContext, DownloadReceiver.class);
+                cancelIntent.putExtra(DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_IDS, downloadIds);
+                cancelIntent.putExtra(DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG, tag);
+
+                builder.addAction(
+                    android.R.drawable.ic_menu_close_clear_cancel,
+                    res.getString(R.string.button_cancel_download),
+                    PendingIntent.getBroadcast(mContext,
+                            0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+
             } else if (type == TYPE_COMPLETE) {
                 final DownloadInfo info = cluster.iterator().next();
                 final Uri uri = ContentUris.withAppendedId(
index 28e2a67..2f50dcf 100644 (file)
@@ -21,6 +21,7 @@ import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY
 import static com.android.providers.downloads.Constants.TAG;
 
 import android.app.DownloadManager;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -45,6 +46,21 @@ import com.google.common.annotations.VisibleForTesting;
  * Receives system broadcasts (boot, network connectivity)
  */
 public class DownloadReceiver extends BroadcastReceiver {
+    /**
+     * Intent extra included with {@link #ACTION_CANCEL} intents, indicating the IDs (as array of
+     * long) of the downloads that were canceled.
+     */
+    public static final String EXTRA_CANCELED_DOWNLOAD_IDS =
+            "com.android.providers.downloads.extra.CANCELED_DOWNLOAD_IDS";
+
+    /**
+     * Intent extra included with {@link #ACTION_CANCEL} intents, indicating the tag of the
+     * notification corresponding to the download(s) that were canceled; this notification must be
+     * canceled.
+     */
+    public static final String EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG =
+            "com.android.providers.downloads.extra.CANCELED_DOWNLOAD_NOTIFICATION_TAG";
+
     private static Handler sAsyncHandler;
 
     static {
@@ -107,6 +123,18 @@ public class DownloadReceiver extends BroadcastReceiver {
                     }
                 });
             }
+        } else if (Constants.ACTION_CANCEL.equals(action)) {
+            long[] downloadIds = intent.getLongArrayExtra(
+                    DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_IDS);
+            DownloadManager manager = (DownloadManager) context.getSystemService(
+                    Context.DOWNLOAD_SERVICE);
+            manager.remove(downloadIds);
+
+            String notifTag = intent.getStringExtra(
+                    DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG);
+            NotificationManager notifManager = (NotificationManager) context.getSystemService(
+                    Context.NOTIFICATION_SERVICE);
+            notifManager.cancel(notifTag, 0);
         }
     }
 
index 28c5dc7..6934b86 100644 (file)
@@ -18,11 +18,13 @@ package com.android.providers.downloads;
 
 import static org.mockito.Mockito.mock;
 
+import android.app.DownloadManager;
 import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ProviderInfo;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
@@ -99,6 +101,7 @@ public abstract class AbstractDownloadProviderFunctionalTest extends
 
         private final ContentResolver mResolver;
         private final NotificationManager mNotifManager;
+        private final DownloadManager mDownloadManager;
 
         boolean mHasServiceBeenStarted = false;
 
@@ -106,6 +109,7 @@ public abstract class AbstractDownloadProviderFunctionalTest extends
             super(realContext, FILENAME_PREFIX);
             mResolver = new MockContentResolverWithNotify(this);
             mNotifManager = mock(NotificationManager.class);
+            mDownloadManager = mock(DownloadManager.class);
         }
 
         /**
@@ -123,6 +127,8 @@ public abstract class AbstractDownloadProviderFunctionalTest extends
         public Object getSystemService(String name) {
             if (Context.NOTIFICATION_SERVICE.equals(name)) {
                 return mNotifManager;
+            } else if (Context.DOWNLOAD_SERVICE.equals(name)) {
+                return mDownloadManager;
             }
 
             return super.getSystemService(name);
@@ -162,7 +168,10 @@ public abstract class AbstractDownloadProviderFunctionalTest extends
 
         final DownloadProvider provider = new DownloadProvider();
         provider.mSystemFacade = mSystemFacade;
-        provider.attachInfo(mTestContext, null);
+
+        ProviderInfo info = new ProviderInfo();
+        info.authority = "downloads";
+        provider.attachInfo(mTestContext, info);
 
         mResolver.addProvider(PROVIDER_AUTHORITY, provider);
 
index d1048b0..17fed6d 100644 (file)
@@ -49,6 +49,8 @@ import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.Suppress;
 import android.text.format.DateUtils;
 
+import com.android.providers.downloads.Constants;
+import com.android.providers.downloads.DownloadReceiver;
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.RecordedRequest;
 import com.google.mockwebserver.SocketPolicy;
@@ -71,6 +73,7 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest {
 
     protected File mTestDirectory;
     private NotificationManager mNotifManager;
+    private DownloadManager mDownloadManager;
 
     public PublicApiFunctionalTest() {
         super(new FakeSystemFacade());
@@ -82,6 +85,8 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest {
 
         mNotifManager = (NotificationManager) getContext()
                 .getSystemService(Context.NOTIFICATION_SERVICE);
+        mDownloadManager = (DownloadManager) getContext()
+                .getSystemService(Context.DOWNLOAD_SERVICE);
 
         mTestDirectory = new File(Environment.getExternalStorageDirectory() + File.separator
                                   + "download_manager_functional_test");
@@ -552,6 +557,23 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest {
         assertEquals(PACKAGE_NAME, broadcast.getPackage());
     }
 
+    public void testNotificationCancelDownloadClicked() throws Exception {
+        Download download = enqueueRequest(getRequest());
+
+        DownloadReceiver receiver = new DownloadReceiver();
+        receiver.mSystemFacade = mSystemFacade;
+        Intent intent = new Intent(Constants.ACTION_CANCEL);
+        intent.setData(Uri.parse(Downloads.Impl.CONTENT_URI + "/" + download.mId));
+
+        long[] downloadIds = {download.mId};
+        intent.putExtra(DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_IDS, downloadIds);
+        intent.putExtra(DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG, "tag");
+        receiver.onReceive(mContext, intent);
+
+        verify(mNotifManager, times(1)).cancel("tag", 0);
+        verify(mDownloadManager, times(1)).remove(downloadIds);
+    }
+
     public void testBasicConnectivityChanges() throws Exception {
         enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));