| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.providers.downloads; |
| |
| import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED; |
| import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION; |
| |
| import android.app.DownloadManager; |
| import android.content.ActivityNotFoundException; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.net.ConnectivityManager; |
| import android.net.NetworkInfo; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.provider.Downloads; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| |
| /** |
| * Receives system broadcasts (boot, network connectivity) |
| */ |
| public class DownloadReceiver extends BroadcastReceiver { |
| private static final String TAG = "DownloadReceiver"; |
| |
| private static Handler sAsyncHandler; |
| |
| static { |
| final HandlerThread thread = new HandlerThread(TAG); |
| thread.start(); |
| sAsyncHandler = new Handler(thread.getLooper()); |
| } |
| |
| @VisibleForTesting |
| SystemFacade mSystemFacade = null; |
| |
| @Override |
| public void onReceive(final Context context, final Intent intent) { |
| if (mSystemFacade == null) { |
| mSystemFacade = new RealSystemFacade(context); |
| } |
| |
| String action = intent.getAction(); |
| if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { |
| if (Constants.LOGVV) { |
| Log.v(Constants.TAG, "Received broadcast intent for " + |
| Intent.ACTION_BOOT_COMPLETED); |
| } |
| startService(context); |
| } else if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { |
| if (Constants.LOGVV) { |
| Log.v(Constants.TAG, "Received broadcast intent for " + |
| Intent.ACTION_MEDIA_MOUNTED); |
| } |
| startService(context); |
| } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { |
| final ConnectivityManager connManager = (ConnectivityManager) context |
| .getSystemService(Context.CONNECTIVITY_SERVICE); |
| final NetworkInfo info = connManager.getActiveNetworkInfo(); |
| if (info != null && info.isConnected()) { |
| startService(context); |
| } |
| } else if (action.equals(Constants.ACTION_RETRY)) { |
| startService(context); |
| } else if (action.equals(Constants.ACTION_OPEN) |
| || action.equals(Constants.ACTION_LIST) |
| || action.equals(Constants.ACTION_HIDE)) { |
| |
| final PendingResult result = goAsync(); |
| if (result == null) { |
| // TODO: remove this once test is refactored |
| handleNotificationBroadcast(context, intent); |
| } else { |
| sAsyncHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| handleNotificationBroadcast(context, intent); |
| result.finish(); |
| } |
| }); |
| } |
| } |
| } |
| |
| /** |
| * Handle any broadcast related to a system notification. |
| */ |
| private void handleNotificationBroadcast(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| if (Constants.ACTION_LIST.equals(action)) { |
| final long[] ids = intent.getLongArrayExtra( |
| DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); |
| sendNotificationClickedIntent(context, ids); |
| |
| } else if (Constants.ACTION_OPEN.equals(action)) { |
| final long id = ContentUris.parseId(intent.getData()); |
| openDownload(context, id); |
| hideNotification(context, id); |
| |
| } else if (Constants.ACTION_HIDE.equals(action)) { |
| final long id = ContentUris.parseId(intent.getData()); |
| hideNotification(context, id); |
| } |
| } |
| |
| /** |
| * Mark the given {@link DownloadManager#COLUMN_ID} as being acknowledged by |
| * user so it's not renewed later. |
| */ |
| private void hideNotification(Context context, long id) { |
| final int status; |
| final int visibility; |
| |
| final Uri uri = ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); |
| final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); |
| try { |
| if (cursor.moveToFirst()) { |
| status = getInt(cursor, Downloads.Impl.COLUMN_STATUS); |
| visibility = getInt(cursor, Downloads.Impl.COLUMN_VISIBILITY); |
| } else { |
| Log.w(TAG, "Missing details for download " + id); |
| return; |
| } |
| } finally { |
| cursor.close(); |
| } |
| |
| if (Downloads.Impl.isStatusCompleted(status) && |
| (visibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED |
| || visibility == VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)) { |
| final ContentValues values = new ContentValues(); |
| values.put(Downloads.Impl.COLUMN_VISIBILITY, |
| Downloads.Impl.VISIBILITY_VISIBLE); |
| context.getContentResolver().update(uri, values, null, null); |
| } |
| } |
| |
| /** |
| * Start activity to display the file represented by the given |
| * {@link DownloadManager#COLUMN_ID}. |
| */ |
| private void openDownload(Context context, long id) { |
| final Intent intent = OpenHelper.buildViewIntent(context, id); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| try { |
| context.startActivity(intent); |
| } catch (ActivityNotFoundException ex) { |
| Log.d(Constants.TAG, "no activity for " + intent, ex); |
| Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_LONG) |
| .show(); |
| } |
| } |
| |
| /** |
| * Notify the owner of a running download that its notification was clicked. |
| */ |
| private void sendNotificationClickedIntent(Context context, long[] ids) { |
| final String packageName; |
| final String clazz; |
| final boolean isPublicApi; |
| |
| final Uri uri = ContentUris.withAppendedId( |
| Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, ids[0]); |
| final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); |
| try { |
| if (cursor.moveToFirst()) { |
| packageName = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); |
| clazz = getString(cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); |
| isPublicApi = getInt(cursor, Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0; |
| } else { |
| Log.w(TAG, "Missing details for download " + ids[0]); |
| return; |
| } |
| } finally { |
| cursor.close(); |
| } |
| |
| if (TextUtils.isEmpty(packageName)) { |
| Log.w(TAG, "Missing package; skipping broadcast"); |
| return; |
| } |
| |
| Intent appIntent = null; |
| if (isPublicApi) { |
| appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); |
| appIntent.setPackage(packageName); |
| appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids); |
| |
| } else { // legacy behavior |
| if (TextUtils.isEmpty(clazz)) { |
| Log.w(TAG, "Missing class; skipping broadcast"); |
| return; |
| } |
| |
| appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); |
| appIntent.setClassName(packageName, clazz); |
| appIntent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, ids); |
| |
| if (ids.length == 1) { |
| appIntent.setData(uri); |
| } else { |
| appIntent.setData(Downloads.Impl.CONTENT_URI); |
| } |
| } |
| |
| mSystemFacade.sendBroadcast(appIntent); |
| } |
| |
| private static String getString(Cursor cursor, String col) { |
| return cursor.getString(cursor.getColumnIndexOrThrow(col)); |
| } |
| |
| private static int getInt(Cursor cursor, String col) { |
| return cursor.getInt(cursor.getColumnIndexOrThrow(col)); |
| } |
| |
| private void startService(Context context) { |
| context.startService(new Intent(context, DownloadService.class)); |
| } |
| } |