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 import static android.provider.Downloads.Impl.COLUMN_DESTINATION; 22 import static android.provider.Downloads.Impl._DATA; 23 24 import static com.android.providers.downloads.Constants.TAG; 25 import static com.android.providers.downloads.Helpers.getAsyncHandler; 26 import static com.android.providers.downloads.Helpers.getDownloadNotifier; 27 import static com.android.providers.downloads.Helpers.getInt; 28 import static com.android.providers.downloads.Helpers.getString; 29 import static com.android.providers.downloads.Helpers.getSystemFacade; 30 31 import android.app.BroadcastOptions; 32 import android.app.DownloadManager; 33 import android.app.NotificationManager; 34 import android.content.BroadcastReceiver; 35 import android.content.ContentResolver; 36 import android.content.ContentUris; 37 import android.content.ContentValues; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.database.Cursor; 41 import android.net.Uri; 42 import android.provider.Downloads; 43 import android.provider.MediaStore; 44 import android.text.TextUtils; 45 import android.util.Log; 46 import android.util.Slog; 47 import android.widget.Toast; 48 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.regex.Pattern; 52 53 /** 54 * Receives system broadcasts (boot, network connectivity) 55 */ 56 public class DownloadReceiver extends BroadcastReceiver { 57 /** 58 * Intent extra included with {@link Constants#ACTION_CANCEL} intents, 59 * indicating the IDs (as array of long) of the downloads that were 60 * canceled. 61 */ 62 public static final String EXTRA_CANCELED_DOWNLOAD_IDS = 63 "com.android.providers.downloads.extra.CANCELED_DOWNLOAD_IDS"; 64 65 /** 66 * Intent extra included with {@link Constants#ACTION_CANCEL} intents, 67 * indicating the tag of the notification corresponding to the download(s) 68 * that were canceled; this notification must be canceled. 69 */ 70 public static final String EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG = 71 "com.android.providers.downloads.extra.CANCELED_DOWNLOAD_NOTIFICATION_TAG"; 72 73 @Override onReceive(final Context context, final Intent intent)74 public void onReceive(final Context context, final Intent intent) { 75 final String action = intent.getAction(); 76 if (Intent.ACTION_BOOT_COMPLETED.equals(action) 77 || Intent.ACTION_MEDIA_MOUNTED.equals(action)) { 78 final PendingResult result = goAsync(); 79 getAsyncHandler().post(new Runnable() { 80 @Override 81 public void run() { 82 handleBootCompleted(context); 83 result.finish(); 84 } 85 }); 86 } else if (Intent.ACTION_UID_REMOVED.equals(action)) { 87 final PendingResult result = goAsync(); 88 getAsyncHandler().post(new Runnable() { 89 @Override 90 public void run() { 91 handleUidRemoved(context, intent); 92 result.finish(); 93 } 94 }); 95 96 } else if (Constants.ACTION_OPEN.equals(action) 97 || Constants.ACTION_LIST.equals(action) 98 || Constants.ACTION_HIDE.equals(action)) { 99 100 final PendingResult result = goAsync(); 101 if (result == null) { 102 // TODO: remove this once test is refactored 103 handleNotificationBroadcast(context, intent); 104 } else { 105 getAsyncHandler().post(new Runnable() { 106 @Override 107 public void run() { 108 handleNotificationBroadcast(context, intent); 109 result.finish(); 110 } 111 }); 112 } 113 } else if (Constants.ACTION_CANCEL.equals(action)) { 114 long[] downloadIds = intent.getLongArrayExtra( 115 DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_IDS); 116 DownloadManager manager = (DownloadManager) context.getSystemService( 117 Context.DOWNLOAD_SERVICE); 118 manager.remove(downloadIds); 119 120 String notifTag = intent.getStringExtra( 121 DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG); 122 NotificationManager notifManager = (NotificationManager) context.getSystemService( 123 Context.NOTIFICATION_SERVICE); 124 notifManager.cancel(notifTag, 0); 125 } 126 } 127 handleBootCompleted(Context context)128 private void handleBootCompleted(Context context) { 129 // Show any relevant notifications for completed downloads 130 getDownloadNotifier(context).update(); 131 132 // Schedule all downloads that are ready 133 final ContentResolver resolver = context.getContentResolver(); 134 try (Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, null, 135 null, null)) { 136 final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor); 137 final DownloadInfo info = new DownloadInfo(context); 138 while (cursor.moveToNext()) { 139 reader.updateFromDatabase(info); 140 Helpers.scheduleJob(context, info); 141 } 142 } 143 144 // Schedule idle pass to clean up orphaned files 145 DownloadIdleService.scheduleIdlePass(context); 146 } 147 handleUidRemoved(Context context, Intent intent)148 private void handleUidRemoved(Context context, Intent intent) { 149 final ContentResolver resolver = context.getContentResolver(); 150 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 151 152 final ArrayList<Long> idsToDelete = new ArrayList<>(); 153 final ArrayList<Long> idsToOrphan = new ArrayList<>(); 154 try (Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, 155 new String[] { Downloads.Impl._ID, Constants.UID, COLUMN_DESTINATION, _DATA }, 156 Constants.UID + "=" + uid, null, null)) { 157 Helpers.handleRemovedUidEntries(context, cursor, idsToDelete, idsToOrphan, null); 158 } 159 160 if (idsToOrphan.size() > 0) { 161 Log.i(Constants.TAG, "Orphaning downloads with ids " 162 + Arrays.toString(idsToOrphan.toArray()) + " as owner package is removed"); 163 final ContentValues values = new ContentValues(); 164 values.putNull(Constants.UID); 165 resolver.update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values, 166 Helpers.buildQueryWithIds(idsToOrphan), null); 167 } 168 if (idsToDelete.size() > 0) { 169 Log.i(Constants.TAG, "Deleting downloads with ids " 170 + Arrays.toString(idsToDelete.toArray()) + " as owner package is removed"); 171 resolver.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, 172 Helpers.buildQueryWithIds(idsToDelete), null); 173 } 174 } 175 176 /** 177 * Handle any broadcast related to a system notification. 178 */ handleNotificationBroadcast(Context context, Intent intent)179 private void handleNotificationBroadcast(Context context, Intent intent) { 180 final String action = intent.getAction(); 181 if (Constants.ACTION_LIST.equals(action)) { 182 final long[] ids = intent.getLongArrayExtra( 183 DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); 184 sendNotificationClickedIntent(context, ids); 185 186 } else if (Constants.ACTION_OPEN.equals(action)) { 187 final long id = ContentUris.parseId(intent.getData()); 188 openDownload(context, id); 189 hideNotification(context, id); 190 191 } else if (Constants.ACTION_HIDE.equals(action)) { 192 final long id = ContentUris.parseId(intent.getData()); 193 hideNotification(context, id); 194 } 195 } 196 197 /** 198 * Mark the given {@link DownloadManager#COLUMN_ID} as being acknowledged by 199 * user so it's not renewed later. 200 */ hideNotification(Context context, long id)201 private void hideNotification(Context context, long id) { 202 final int status; 203 final int visibility; 204 205 final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); 206 final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 207 try { 208 if (cursor.moveToFirst()) { 209 status = getInt(cursor, Downloads.Impl.COLUMN_STATUS); 210 visibility = getInt(cursor, Downloads.Impl.COLUMN_VISIBILITY); 211 } else { 212 Log.w(TAG, "Missing details for download " + id); 213 return; 214 } 215 } finally { 216 cursor.close(); 217 } 218 219 if (Downloads.Impl.isStatusCompleted(status) && 220 (visibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED 221 || visibility == VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)) { 222 final ContentValues values = new ContentValues(); 223 values.put(Downloads.Impl.COLUMN_VISIBILITY, 224 Downloads.Impl.VISIBILITY_VISIBLE); 225 context.getContentResolver().update(uri, values, null, null); 226 } 227 } 228 229 /** 230 * Start activity to display the file represented by the given 231 * {@link DownloadManager#COLUMN_ID}. 232 */ openDownload(Context context, long id)233 private void openDownload(Context context, long id) { 234 if (!OpenHelper.startViewIntent(context, id, Intent.FLAG_ACTIVITY_NEW_TASK)) { 235 Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_SHORT) 236 .show(); 237 } 238 } 239 240 /** 241 * Notify the owner of a running download that its notification was clicked. 242 */ sendNotificationClickedIntent(Context context, long[] ids)243 private void sendNotificationClickedIntent(Context context, long[] ids) { 244 final String packageName; 245 final String clazz; 246 final boolean isPublicApi; 247 248 final Uri uri = ContentUris.withAppendedId( 249 Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, ids[0]); 250 final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 251 try { 252 if (cursor.moveToFirst()) { 253 packageName = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); 254 clazz = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); 255 isPublicApi = getInt(cursor, Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0; 256 } else { 257 Log.w(TAG, "Missing details for download " + ids[0]); 258 return; 259 } 260 } finally { 261 cursor.close(); 262 } 263 264 if (TextUtils.isEmpty(packageName)) { 265 Log.w(TAG, "Missing package; skipping broadcast"); 266 return; 267 } 268 269 Intent appIntent = null; 270 if (isPublicApi) { 271 appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); 272 appIntent.setPackage(packageName); 273 appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids); 274 275 } else { // legacy behavior 276 if (TextUtils.isEmpty(clazz)) { 277 Log.w(TAG, "Missing class; skipping broadcast"); 278 return; 279 } 280 281 appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); 282 appIntent.setClassName(packageName, clazz); 283 appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids); 284 285 if (ids.length == 1) { 286 appIntent.setData(uri); 287 } else { 288 appIntent.setData(Downloads.Impl.CONTENT_URI); 289 } 290 } 291 292 final BroadcastOptions options = BroadcastOptions.makeBasic(); 293 options.setBackgroundActivityStartsAllowed(true); 294 getSystemFacade(context).sendBroadcast(appIntent, null, options.toBundle()); 295 } 296 } 297