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 com.android.providers.downloads.Constants.TAG; 22 23 import android.app.DownloadManager; 24 import android.content.BroadcastReceiver; 25 import android.content.ContentResolver; 26 import android.content.ContentUris; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.database.Cursor; 31 import android.net.ConnectivityManager; 32 import android.net.NetworkInfo; 33 import android.net.Uri; 34 import android.os.Handler; 35 import android.os.HandlerThread; 36 import android.provider.Downloads; 37 import android.text.TextUtils; 38 import android.util.Log; 39 import android.util.Slog; 40 import android.widget.Toast; 41 42 import com.google.common.annotations.VisibleForTesting; 43 44 /** 45 * Receives system broadcasts (boot, network connectivity) 46 */ 47 public class DownloadReceiver extends BroadcastReceiver { 48 private static Handler sAsyncHandler; 49 50 static { 51 final HandlerThread thread = new HandlerThread("DownloadReceiver"); thread.start()52 thread.start(); 53 sAsyncHandler = new Handler(thread.getLooper()); 54 } 55 56 @VisibleForTesting 57 SystemFacade mSystemFacade = null; 58 59 @Override onReceive(final Context context, final Intent intent)60 public void onReceive(final Context context, final Intent intent) { 61 if (mSystemFacade == null) { 62 mSystemFacade = new RealSystemFacade(context); 63 } 64 65 final String action = intent.getAction(); 66 if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { 67 startService(context); 68 69 } else if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) { 70 startService(context); 71 72 } else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { 73 final ConnectivityManager connManager = (ConnectivityManager) context 74 .getSystemService(Context.CONNECTIVITY_SERVICE); 75 final NetworkInfo info = connManager.getActiveNetworkInfo(); 76 if (info != null && info.isConnected()) { 77 startService(context); 78 } 79 80 } else if (Intent.ACTION_UID_REMOVED.equals(action)) { 81 final PendingResult result = goAsync(); 82 sAsyncHandler.post(new Runnable() { 83 @Override 84 public void run() { 85 handleUidRemoved(context, intent); 86 result.finish(); 87 } 88 }); 89 90 } else if (Constants.ACTION_RETRY.equals(action)) { 91 startService(context); 92 93 } else if (Constants.ACTION_OPEN.equals(action) 94 || Constants.ACTION_LIST.equals(action) 95 || Constants.ACTION_HIDE.equals(action)) { 96 97 final PendingResult result = goAsync(); 98 if (result == null) { 99 // TODO: remove this once test is refactored 100 handleNotificationBroadcast(context, intent); 101 } else { 102 sAsyncHandler.post(new Runnable() { 103 @Override 104 public void run() { 105 handleNotificationBroadcast(context, intent); 106 result.finish(); 107 } 108 }); 109 } 110 } 111 } 112 handleUidRemoved(Context context, Intent intent)113 private void handleUidRemoved(Context context, Intent intent) { 114 final ContentResolver resolver = context.getContentResolver(); 115 116 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 117 final int count = resolver.delete( 118 Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, Constants.UID + "=" + uid, null); 119 120 if (count > 0) { 121 Slog.d(TAG, "Deleted " + count + " downloads owned by UID " + uid); 122 } 123 } 124 125 /** 126 * Handle any broadcast related to a system notification. 127 */ handleNotificationBroadcast(Context context, Intent intent)128 private void handleNotificationBroadcast(Context context, Intent intent) { 129 final String action = intent.getAction(); 130 if (Constants.ACTION_LIST.equals(action)) { 131 final long[] ids = intent.getLongArrayExtra( 132 DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); 133 sendNotificationClickedIntent(context, ids); 134 135 } else if (Constants.ACTION_OPEN.equals(action)) { 136 final long id = ContentUris.parseId(intent.getData()); 137 openDownload(context, id); 138 hideNotification(context, id); 139 140 } else if (Constants.ACTION_HIDE.equals(action)) { 141 final long id = ContentUris.parseId(intent.getData()); 142 hideNotification(context, id); 143 } 144 } 145 146 /** 147 * Mark the given {@link DownloadManager#COLUMN_ID} as being acknowledged by 148 * user so it's not renewed later. 149 */ hideNotification(Context context, long id)150 private void hideNotification(Context context, long id) { 151 final int status; 152 final int visibility; 153 154 final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); 155 final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 156 try { 157 if (cursor.moveToFirst()) { 158 status = getInt(cursor, Downloads.Impl.COLUMN_STATUS); 159 visibility = getInt(cursor, Downloads.Impl.COLUMN_VISIBILITY); 160 } else { 161 Log.w(TAG, "Missing details for download " + id); 162 return; 163 } 164 } finally { 165 cursor.close(); 166 } 167 168 if (Downloads.Impl.isStatusCompleted(status) && 169 (visibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED 170 || visibility == VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)) { 171 final ContentValues values = new ContentValues(); 172 values.put(Downloads.Impl.COLUMN_VISIBILITY, 173 Downloads.Impl.VISIBILITY_VISIBLE); 174 context.getContentResolver().update(uri, values, null, null); 175 } 176 } 177 178 /** 179 * Start activity to display the file represented by the given 180 * {@link DownloadManager#COLUMN_ID}. 181 */ openDownload(Context context, long id)182 private void openDownload(Context context, long id) { 183 if (!OpenHelper.startViewIntent(context, id, Intent.FLAG_ACTIVITY_NEW_TASK)) { 184 Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_SHORT) 185 .show(); 186 } 187 } 188 189 /** 190 * Notify the owner of a running download that its notification was clicked. 191 */ sendNotificationClickedIntent(Context context, long[] ids)192 private void sendNotificationClickedIntent(Context context, long[] ids) { 193 final String packageName; 194 final String clazz; 195 final boolean isPublicApi; 196 197 final Uri uri = ContentUris.withAppendedId( 198 Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, ids[0]); 199 final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 200 try { 201 if (cursor.moveToFirst()) { 202 packageName = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); 203 clazz = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); 204 isPublicApi = getInt(cursor, Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0; 205 } else { 206 Log.w(TAG, "Missing details for download " + ids[0]); 207 return; 208 } 209 } finally { 210 cursor.close(); 211 } 212 213 if (TextUtils.isEmpty(packageName)) { 214 Log.w(TAG, "Missing package; skipping broadcast"); 215 return; 216 } 217 218 Intent appIntent = null; 219 if (isPublicApi) { 220 appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); 221 appIntent.setPackage(packageName); 222 appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids); 223 224 } else { // legacy behavior 225 if (TextUtils.isEmpty(clazz)) { 226 Log.w(TAG, "Missing class; skipping broadcast"); 227 return; 228 } 229 230 appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); 231 appIntent.setClassName(packageName, clazz); 232 appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids); 233 234 if (ids.length == 1) { 235 appIntent.setData(uri); 236 } else { 237 appIntent.setData(Downloads.Impl.CONTENT_URI); 238 } 239 } 240 241 mSystemFacade.sendBroadcast(appIntent); 242 } 243 getString(Cursor cursor, String col)244 private static String getString(Cursor cursor, String col) { 245 return cursor.getString(cursor.getColumnIndexOrThrow(col)); 246 } 247 getInt(Cursor cursor, String col)248 private static int getInt(Cursor cursor, String col) { 249 return cursor.getInt(cursor.getColumnIndexOrThrow(col)); 250 } 251 startService(Context context)252 private void startService(Context context) { 253 context.startService(new Intent(context, DownloadService.class)); 254 } 255 } 256