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 android.app.DownloadManager; 20 import android.content.ActivityNotFoundException; 21 import android.content.BroadcastReceiver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.database.Cursor; 27 import android.net.ConnectivityManager; 28 import android.net.NetworkInfo; 29 import android.net.Uri; 30 import android.provider.Downloads; 31 import android.util.Log; 32 33 import com.google.common.annotations.VisibleForTesting; 34 35 import java.io.File; 36 37 /** 38 * Receives system broadcasts (boot, network connectivity) 39 */ 40 public class DownloadReceiver extends BroadcastReceiver { 41 @VisibleForTesting 42 SystemFacade mSystemFacade = null; 43 44 @Override onReceive(Context context, Intent intent)45 public void onReceive(Context context, Intent intent) { 46 if (mSystemFacade == null) { 47 mSystemFacade = new RealSystemFacade(context); 48 } 49 50 String action = intent.getAction(); 51 if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { 52 if (Constants.LOGVV) { 53 Log.v(Constants.TAG, "Received broadcast intent for " + 54 Intent.ACTION_BOOT_COMPLETED); 55 } 56 startService(context); 57 } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { 58 if (Constants.LOGVV) { 59 Log.v(Constants.TAG, "Received broadcast intent for " + 60 Intent.ACTION_MEDIA_MOUNTED); 61 } 62 startService(context); 63 } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 64 final ConnectivityManager connManager = (ConnectivityManager) context 65 .getSystemService(Context.CONNECTIVITY_SERVICE); 66 final NetworkInfo info = connManager.getActiveNetworkInfo(); 67 if (info != null && info.isConnected()) { 68 startService(context); 69 } 70 } else if (action.equals(Constants.ACTION_RETRY)) { 71 startService(context); 72 } else if (action.equals(Constants.ACTION_OPEN) 73 || action.equals(Constants.ACTION_LIST) 74 || action.equals(Constants.ACTION_HIDE)) { 75 handleNotificationBroadcast(context, intent); 76 } 77 } 78 79 /** 80 * Handle any broadcast related to a system notification. 81 */ handleNotificationBroadcast(Context context, Intent intent)82 private void handleNotificationBroadcast(Context context, Intent intent) { 83 Uri uri = intent.getData(); 84 String action = intent.getAction(); 85 if (Constants.LOGVV) { 86 if (action.equals(Constants.ACTION_OPEN)) { 87 Log.v(Constants.TAG, "Receiver open for " + uri); 88 } else if (action.equals(Constants.ACTION_LIST)) { 89 Log.v(Constants.TAG, "Receiver list for " + uri); 90 } else { // ACTION_HIDE 91 Log.v(Constants.TAG, "Receiver hide for " + uri); 92 } 93 } 94 95 Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); 96 if (cursor == null) { 97 return; 98 } 99 try { 100 if (!cursor.moveToFirst()) { 101 return; 102 } 103 104 if (action.equals(Constants.ACTION_OPEN)) { 105 openDownload(context, cursor); 106 hideNotification(context, uri, cursor); 107 } else if (action.equals(Constants.ACTION_LIST)) { 108 sendNotificationClickedIntent(intent, cursor); 109 } else { // ACTION_HIDE 110 hideNotification(context, uri, cursor); 111 } 112 } finally { 113 cursor.close(); 114 } 115 } 116 117 /** 118 * Hide a system notification for a download. 119 * @param uri URI to update the download 120 * @param cursor Cursor for reading the download's fields 121 */ hideNotification(Context context, Uri uri, Cursor cursor)122 private void hideNotification(Context context, Uri uri, Cursor cursor) { 123 mSystemFacade.cancelNotification(ContentUris.parseId(uri)); 124 125 int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); 126 int status = cursor.getInt(statusColumn); 127 int visibilityColumn = 128 cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY); 129 int visibility = cursor.getInt(visibilityColumn); 130 if (Downloads.Impl.isStatusCompleted(status) 131 && visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { 132 ContentValues values = new ContentValues(); 133 values.put(Downloads.Impl.COLUMN_VISIBILITY, 134 Downloads.Impl.VISIBILITY_VISIBLE); 135 context.getContentResolver().update(uri, values, null, null); 136 } 137 } 138 139 /** 140 * Open the download that cursor is currently pointing to, since it's completed notification 141 * has been clicked. 142 */ openDownload(Context context, Cursor cursor)143 private void openDownload(Context context, Cursor cursor) { 144 String filename = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA)); 145 String mimetype = 146 cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE)); 147 Uri path = Uri.parse(filename); 148 // If there is no scheme, then it must be a file 149 if (path.getScheme() == null) { 150 path = Uri.fromFile(new File(filename)); 151 } 152 153 Intent activityIntent = new Intent(Intent.ACTION_VIEW); 154 mimetype = DownloadDrmHelper.getOriginalMimeType(context, filename, mimetype); 155 activityIntent.setDataAndType(path, mimetype); 156 activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 157 try { 158 context.startActivity(activityIntent); 159 } catch (ActivityNotFoundException ex) { 160 Log.d(Constants.TAG, "no activity for " + mimetype, ex); 161 } 162 } 163 164 /** 165 * Notify the owner of a running download that its notification was clicked. 166 * @param intent the broadcast intent sent by the notification manager 167 * @param cursor Cursor for reading the download's fields 168 */ sendNotificationClickedIntent(Intent intent, Cursor cursor)169 private void sendNotificationClickedIntent(Intent intent, Cursor cursor) { 170 String pckg = cursor.getString( 171 cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE)); 172 if (pckg == null) { 173 return; 174 } 175 176 String clazz = cursor.getString( 177 cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_NOTIFICATION_CLASS)); 178 boolean isPublicApi = 179 cursor.getInt(cursor.getColumnIndex(Downloads.Impl.COLUMN_IS_PUBLIC_API)) != 0; 180 181 Intent appIntent = null; 182 if (isPublicApi) { 183 appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); 184 appIntent.setPackage(pckg); 185 // send id of the items clicked on. 186 if (intent.getBooleanExtra("multiple", false)) { 187 // broadcast received saying click occurred on a notification with multiple titles. 188 // don't include any ids at all - let the caller query all downloads belonging to it 189 // TODO modify the broadcast to include ids of those multiple notifications. 190 } else { 191 appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, 192 new long[] { 193 cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.Impl._ID))}); 194 } 195 } else { // legacy behavior 196 if (clazz == null) { 197 return; 198 } 199 appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); 200 appIntent.setClassName(pckg, clazz); 201 if (intent.getBooleanExtra("multiple", true)) { 202 appIntent.setData(Downloads.Impl.CONTENT_URI); 203 } else { 204 long downloadId = cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); 205 appIntent.setData( 206 ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, downloadId)); 207 } 208 } 209 210 mSystemFacade.sendBroadcast(appIntent); 211 } 212 startService(Context context)213 private void startService(Context context) { 214 context.startService(new Intent(context, DownloadService.class)); 215 } 216 } 217