Home | History | Annotate | Download | only in downloads
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.providers.downloads;
     18 
     19 import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
     20 import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION;
     21 
     22 import static com.android.providers.downloads.Constants.TAG;
     23 import static com.android.providers.downloads.Helpers.getAsyncHandler;
     24 import static com.android.providers.downloads.Helpers.getDownloadNotifier;
     25 import static com.android.providers.downloads.Helpers.getInt;
     26 import static com.android.providers.downloads.Helpers.getString;
     27 import static com.android.providers.downloads.Helpers.getSystemFacade;
     28 
     29 import android.app.DownloadManager;
     30 import android.app.NotificationManager;
     31 import android.content.BroadcastReceiver;
     32 import android.content.ContentResolver;
     33 import android.content.ContentUris;
     34 import android.content.ContentValues;
     35 import android.content.Context;
     36 import android.content.Intent;
     37 import android.database.Cursor;
     38 import android.net.Uri;
     39 import android.provider.Downloads;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.util.Slog;
     43 import android.widget.Toast;
     44 
     45 /**
     46  * Receives system broadcasts (boot, network connectivity)
     47  */
     48 public class DownloadReceiver extends BroadcastReceiver {
     49     /**
     50      * Intent extra included with {@link Constants#ACTION_CANCEL} intents,
     51      * indicating the IDs (as array of long) of the downloads that were
     52      * canceled.
     53      */
     54     public static final String EXTRA_CANCELED_DOWNLOAD_IDS =
     55             "com.android.providers.downloads.extra.CANCELED_DOWNLOAD_IDS";
     56 
     57     /**
     58      * Intent extra included with {@link Constants#ACTION_CANCEL} intents,
     59      * indicating the tag of the notification corresponding to the download(s)
     60      * that were canceled; this notification must be canceled.
     61      */
     62     public static final String EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG =
     63             "com.android.providers.downloads.extra.CANCELED_DOWNLOAD_NOTIFICATION_TAG";
     64 
     65     @Override
     66     public void onReceive(final Context context, final Intent intent) {
     67         final String action = intent.getAction();
     68         if (Intent.ACTION_BOOT_COMPLETED.equals(action)
     69                 || Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
     70             final PendingResult result = goAsync();
     71             getAsyncHandler().post(new Runnable() {
     72                 @Override
     73                 public void run() {
     74                     handleBootCompleted(context);
     75                     result.finish();
     76                 }
     77             });
     78         } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
     79             final PendingResult result = goAsync();
     80             getAsyncHandler().post(new Runnable() {
     81                 @Override
     82                 public void run() {
     83                     handleUidRemoved(context, intent);
     84                     result.finish();
     85                 }
     86             });
     87 
     88         } else if (Constants.ACTION_OPEN.equals(action)
     89                 || Constants.ACTION_LIST.equals(action)
     90                 || Constants.ACTION_HIDE.equals(action)) {
     91 
     92             final PendingResult result = goAsync();
     93             if (result == null) {
     94                 // TODO: remove this once test is refactored
     95                 handleNotificationBroadcast(context, intent);
     96             } else {
     97                 getAsyncHandler().post(new Runnable() {
     98                     @Override
     99                     public void run() {
    100                         handleNotificationBroadcast(context, intent);
    101                         result.finish();
    102                     }
    103                 });
    104             }
    105         } else if (Constants.ACTION_CANCEL.equals(action)) {
    106             long[] downloadIds = intent.getLongArrayExtra(
    107                     DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_IDS);
    108             DownloadManager manager = (DownloadManager) context.getSystemService(
    109                     Context.DOWNLOAD_SERVICE);
    110             manager.remove(downloadIds);
    111 
    112             String notifTag = intent.getStringExtra(
    113                     DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG);
    114             NotificationManager notifManager = (NotificationManager) context.getSystemService(
    115                     Context.NOTIFICATION_SERVICE);
    116             notifManager.cancel(notifTag, 0);
    117         }
    118     }
    119 
    120     private void handleBootCompleted(Context context) {
    121         // Show any relevant notifications for completed downloads
    122         getDownloadNotifier(context).update();
    123 
    124         // Schedule all downloads that are ready
    125         final ContentResolver resolver = context.getContentResolver();
    126         try (Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, null,
    127                 null, null)) {
    128             final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
    129             final DownloadInfo info = new DownloadInfo(context);
    130             while (cursor.moveToNext()) {
    131                 reader.updateFromDatabase(info);
    132                 Helpers.scheduleJob(context, info);
    133             }
    134         }
    135 
    136         // Schedule idle pass to clean up orphaned files
    137         DownloadIdleService.scheduleIdlePass(context);
    138     }
    139 
    140     private void handleUidRemoved(Context context, Intent intent) {
    141         final ContentResolver resolver = context.getContentResolver();
    142 
    143         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
    144         final int count = resolver.delete(
    145                 Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, Constants.UID + "=" + uid, null);
    146 
    147         if (count > 0) {
    148             Slog.d(TAG, "Deleted " + count + " downloads owned by UID " + uid);
    149         }
    150     }
    151 
    152     /**
    153      * Handle any broadcast related to a system notification.
    154      */
    155     private void handleNotificationBroadcast(Context context, Intent intent) {
    156         final String action = intent.getAction();
    157         if (Constants.ACTION_LIST.equals(action)) {
    158             final long[] ids = intent.getLongArrayExtra(
    159                     DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
    160             sendNotificationClickedIntent(context, ids);
    161 
    162         } else if (Constants.ACTION_OPEN.equals(action)) {
    163             final long id = ContentUris.parseId(intent.getData());
    164             openDownload(context, id);
    165             hideNotification(context, id);
    166 
    167         } else if (Constants.ACTION_HIDE.equals(action)) {
    168             final long id = ContentUris.parseId(intent.getData());
    169             hideNotification(context, id);
    170         }
    171     }
    172 
    173     /**
    174      * Mark the given {@link DownloadManager#COLUMN_ID} as being acknowledged by
    175      * user so it's not renewed later.
    176      */
    177     private void hideNotification(Context context, long id) {
    178         final int status;
    179         final int visibility;
    180 
    181         final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
    182         final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    183         try {
    184             if (cursor.moveToFirst()) {
    185                 status = getInt(cursor, Downloads.Impl.COLUMN_STATUS);
    186                 visibility = getInt(cursor, Downloads.Impl.COLUMN_VISIBILITY);
    187             } else {
    188                 Log.w(TAG, "Missing details for download " + id);
    189                 return;
    190             }
    191         } finally {
    192             cursor.close();
    193         }
    194 
    195         if (Downloads.Impl.isStatusCompleted(status) &&
    196                 (visibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED
    197                 || visibility == VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)) {
    198             final ContentValues values = new ContentValues();
    199             values.put(Downloads.Impl.COLUMN_VISIBILITY,
    200                     Downloads.Impl.VISIBILITY_VISIBLE);
    201             context.getContentResolver().update(uri, values, null, null);
    202         }
    203     }
    204 
    205     /**
    206      * Start activity to display the file represented by the given
    207      * {@link DownloadManager#COLUMN_ID}.
    208      */
    209     private void openDownload(Context context, long id) {
    210         if (!OpenHelper.startViewIntent(context, id, Intent.FLAG_ACTIVITY_NEW_TASK)) {
    211             Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_SHORT)
    212                     .show();
    213         }
    214     }
    215 
    216     /**
    217      * Notify the owner of a running download that its notification was clicked.
    218      */
    219     private void sendNotificationClickedIntent(Context context, long[] ids) {
    220         final String packageName;
    221         final String clazz;
    222         final boolean isPublicApi;
    223 
    224         final Uri uri = ContentUris.withAppendedId(
    225                 Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, ids[0]);
    226         final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    227         try {
    228             if (cursor.moveToFirst()) {
    229                 packageName = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
    230                 clazz = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
    231                 isPublicApi = getInt(cursor, Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0;
    232             } else {
    233                 Log.w(TAG, "Missing details for download " + ids[0]);
    234                 return;
    235             }
    236         } finally {
    237             cursor.close();
    238         }
    239 
    240         if (TextUtils.isEmpty(packageName)) {
    241             Log.w(TAG, "Missing package; skipping broadcast");
    242             return;
    243         }
    244 
    245         Intent appIntent = null;
    246         if (isPublicApi) {
    247             appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED);
    248             appIntent.setPackage(packageName);
    249             appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids);
    250 
    251         } else { // legacy behavior
    252             if (TextUtils.isEmpty(clazz)) {
    253                 Log.w(TAG, "Missing class; skipping broadcast");
    254                 return;
    255             }
    256 
    257             appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED);
    258             appIntent.setClassName(packageName, clazz);
    259             appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids);
    260 
    261             if (ids.length == 1) {
    262                 appIntent.setData(uri);
    263             } else {
    264                 appIntent.setData(Downloads.Impl.CONTENT_URI);
    265             }
    266         }
    267 
    268         getSystemFacade(context).sendBroadcast(appIntent);
    269     }
    270 }
    271