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