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