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