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