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 onReceive(final Context context, final Intent intent)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 handleBootCompleted(Context context)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 handleUidRemoved(Context context, Intent intent)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 */ handleNotificationBroadcast(Context context, Intent intent)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 */ hideNotification(Context context, long id)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 */ openDownload(Context context, long id)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 */ sendNotificationClickedIntent(Context context, long[] ids)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