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