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         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
    143 
    144         // First, disown any downloads that live in shared storage
    145         final ContentValues values = new ContentValues();
    146         values.putNull(Constants.UID);
    147         final int disowned = resolver.update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values,
    148                 Constants.UID + "=" + uid + " AND " + Downloads.Impl.COLUMN_DESTINATION + " IN ("
    149                         + Downloads.Impl.DESTINATION_EXTERNAL + ","
    150                         + Downloads.Impl.DESTINATION_FILE_URI + ")",
    151                 null);
    152 
    153         // Finally, delete any remaining downloads owned by UID
    154         final int deleted = resolver.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
    155                 Constants.UID + "=" + uid, null);
    156 
    157         if ((disowned + deleted) > 0) {
    158             Slog.d(TAG, "Disowned " + disowned + " and deleted " + deleted
    159                     + " downloads owned by UID " + uid);
    160         }
    161     }
    162 
    163     /**
    164      * Handle any broadcast related to a system notification.
    165      */
    166     private void handleNotificationBroadcast(Context context, Intent intent) {
    167         final String action = intent.getAction();
    168         if (Constants.ACTION_LIST.equals(action)) {
    169             final long[] ids = intent.getLongArrayExtra(
    170                     DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
    171             sendNotificationClickedIntent(context, ids);
    172 
    173         } else if (Constants.ACTION_OPEN.equals(action)) {
    174             final long id = ContentUris.parseId(intent.getData());
    175             openDownload(context, id);
    176             hideNotification(context, id);
    177 
    178         } else if (Constants.ACTION_HIDE.equals(action)) {
    179             final long id = ContentUris.parseId(intent.getData());
    180             hideNotification(context, id);
    181         }
    182     }
    183 
    184     /**
    185      * Mark the given {@link DownloadManager#COLUMN_ID} as being acknowledged by
    186      * user so it's not renewed later.
    187      */
    188     private void hideNotification(Context context, long id) {
    189         final int status;
    190         final int visibility;
    191 
    192         final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
    193         final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    194         try {
    195             if (cursor.moveToFirst()) {
    196                 status = getInt(cursor, Downloads.Impl.COLUMN_STATUS);
    197                 visibility = getInt(cursor, Downloads.Impl.COLUMN_VISIBILITY);
    198             } else {
    199                 Log.w(TAG, "Missing details for download " + id);
    200                 return;
    201             }
    202         } finally {
    203             cursor.close();
    204         }
    205 
    206         if (Downloads.Impl.isStatusCompleted(status) &&
    207                 (visibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED
    208                 || visibility == VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)) {
    209             final ContentValues values = new ContentValues();
    210             values.put(Downloads.Impl.COLUMN_VISIBILITY,
    211                     Downloads.Impl.VISIBILITY_VISIBLE);
    212             context.getContentResolver().update(uri, values, null, null);
    213         }
    214     }
    215 
    216     /**
    217      * Start activity to display the file represented by the given
    218      * {@link DownloadManager#COLUMN_ID}.
    219      */
    220     private void openDownload(Context context, long id) {
    221         if (!OpenHelper.startViewIntent(context, id, Intent.FLAG_ACTIVITY_NEW_TASK)) {
    222             Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_SHORT)
    223                     .show();
    224         }
    225     }
    226 
    227     /**
    228      * Notify the owner of a running download that its notification was clicked.
    229      */
    230     private void sendNotificationClickedIntent(Context context, long[] ids) {
    231         final String packageName;
    232         final String clazz;
    233         final boolean isPublicApi;
    234 
    235         final Uri uri = ContentUris.withAppendedId(
    236                 Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, ids[0]);
    237         final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    238         try {
    239             if (cursor.moveToFirst()) {
    240                 packageName = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
    241                 clazz = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
    242                 isPublicApi = getInt(cursor, Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0;
    243             } else {
    244                 Log.w(TAG, "Missing details for download " + ids[0]);
    245                 return;
    246             }
    247         } finally {
    248             cursor.close();
    249         }
    250 
    251         if (TextUtils.isEmpty(packageName)) {
    252             Log.w(TAG, "Missing package; skipping broadcast");
    253             return;
    254         }
    255 
    256         Intent appIntent = null;
    257         if (isPublicApi) {
    258             appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED);
    259             appIntent.setPackage(packageName);
    260             appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids);
    261 
    262         } else { // legacy behavior
    263             if (TextUtils.isEmpty(clazz)) {
    264                 Log.w(TAG, "Missing class; skipping broadcast");
    265                 return;
    266             }
    267 
    268             appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED);
    269             appIntent.setClassName(packageName, clazz);
    270             appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids);
    271 
    272             if (ids.length == 1) {
    273                 appIntent.setData(uri);
    274             } else {
    275                 appIntent.setData(Downloads.Impl.CONTENT_URI);
    276             }
    277         }
    278 
    279         getSystemFacade(context).sendBroadcast(appIntent);
    280     }
    281 }
    282