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