4002a97a867ca4caf7d8de3e103afd34e8465d72
[android/platform/packages/providers/DownloadProvider.git] / src / com / android / providers / downloads / DownloadNotification.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 android.app.Notification;
20 import android.app.PendingIntent;
21 import android.content.ContentUris;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.provider.Downloads;
26 import android.view.View;
27 import android.widget.RemoteViews;
28
29 import java.util.Collection;
30 import java.util.HashMap;
31
32 /**
33  * This class handles the updating of the Notification Manager for the
34  * cases where there is an ongoing download. Once the download is complete
35  * (be it successful or unsuccessful) it is no longer the responsibility
36  * of this component to show the download in the notification manager.
37  *
38  */
39 class DownloadNotification {
40
41     Context mContext;
42     HashMap <String, NotificationItem> mNotifications;
43     private SystemFacade mSystemFacade;
44
45     static final String LOGTAG = "DownloadNotification";
46     static final String WHERE_RUNNING =
47         "(" + Downloads.Impl.COLUMN_STATUS + " >= '100') AND (" +
48         Downloads.Impl.COLUMN_STATUS + " <= '199') AND (" +
49         Downloads.Impl.COLUMN_VISIBILITY + " IS NULL OR " +
50         Downloads.Impl.COLUMN_VISIBILITY + " == '" + Downloads.Impl.VISIBILITY_VISIBLE + "' OR " +
51         Downloads.Impl.COLUMN_VISIBILITY +
52             " == '" + Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + "')";
53     static final String WHERE_COMPLETED =
54         Downloads.Impl.COLUMN_STATUS + " >= '200' AND " +
55         Downloads.Impl.COLUMN_VISIBILITY +
56             " == '" + Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + "'";
57
58
59     /**
60      * This inner class is used to collate downloads that are owned by
61      * the same application. This is so that only one notification line
62      * item is used for all downloads of a given application.
63      *
64      */
65     static class NotificationItem {
66         int mId;  // This first db _id for the download for the app
67         long mTotalCurrent = 0;
68         long mTotalTotal = 0;
69         int mTitleCount = 0;
70         String mPackageName;  // App package name
71         String mDescription;
72         String[] mTitles = new String[2]; // download titles.
73         String mPausedText = null;
74
75         /*
76          * Add a second download to this notification item.
77          */
78         void addItem(String title, long currentBytes, long totalBytes) {
79             mTotalCurrent += currentBytes;
80             if (totalBytes <= 0 || mTotalTotal == -1) {
81                 mTotalTotal = -1;
82             } else {
83                 mTotalTotal += totalBytes;
84             }
85             if (mTitleCount < 2) {
86                 mTitles[mTitleCount] = title;
87             }
88             mTitleCount++;
89         }
90     }
91
92
93     /**
94      * Constructor
95      * @param ctx The context to use to obtain access to the
96      *            Notification Service
97      */
98     DownloadNotification(Context ctx, SystemFacade systemFacade) {
99         mContext = ctx;
100         mSystemFacade = systemFacade;
101         mNotifications = new HashMap<String, NotificationItem>();
102     }
103
104     /*
105      * Update the notification ui.
106      */
107     public void updateNotification(Collection<DownloadInfo> downloads) {
108         updateActiveNotification(downloads);
109         updateCompletedNotification(downloads);
110     }
111
112     private void updateActiveNotification(Collection<DownloadInfo> downloads) {
113         // Collate the notifications
114         mNotifications.clear();
115         for (DownloadInfo download : downloads) {
116             if (!isActiveAndVisible(download)) {
117                 continue;
118             }
119             String packageName = download.mPackage;
120             long max = download.mTotalBytes;
121             long progress = download.mCurrentBytes;
122             long id = download.mId;
123             String title = download.mTitle;
124             if (title == null || title.length() == 0) {
125                 title = mContext.getResources().getString(
126                         R.string.download_unknown_title);
127             }
128
129             NotificationItem item;
130             if (mNotifications.containsKey(packageName)) {
131                 item = mNotifications.get(packageName);
132                 item.addItem(title, progress, max);
133             } else {
134                 item = new NotificationItem();
135                 item.mId = (int) id;
136                 item.mPackageName = packageName;
137                 item.mDescription = download.mDescription;
138                 item.addItem(title, progress, max);
139                 mNotifications.put(packageName, item);
140             }
141             if (download.mStatus == Downloads.Impl.STATUS_QUEUED_FOR_WIFI
142                     && item.mPausedText == null) {
143                 item.mPausedText = mContext.getResources().getString(
144                         R.string.notification_need_wifi_for_size);
145             }
146         }
147
148         // Add the notifications
149         for (NotificationItem item : mNotifications.values()) {
150             // Build the notification object
151             Notification n = new Notification();
152
153             boolean hasPausedText = (item.mPausedText != null);
154             int iconResource = android.R.drawable.stat_sys_download;
155             if (hasPausedText) {
156                 iconResource = android.R.drawable.stat_sys_warning;
157             }
158             n.icon = iconResource;
159
160             n.flags |= Notification.FLAG_ONGOING_EVENT;
161
162             // Build the RemoteView object
163             RemoteViews expandedView = new RemoteViews(
164                     "com.android.providers.downloads",
165                     R.layout.status_bar_ongoing_event_progress_bar);
166             StringBuilder title = new StringBuilder(item.mTitles[0]);
167             if (item.mTitleCount > 1) {
168                 title.append(mContext.getString(R.string.notification_filename_separator));
169                 title.append(item.mTitles[1]);
170                 n.number = item.mTitleCount;
171                 if (item.mTitleCount > 2) {
172                     title.append(mContext.getString(R.string.notification_filename_extras,
173                             new Object[] { Integer.valueOf(item.mTitleCount - 2) }));
174                 }
175             } else {
176                 expandedView.setTextViewText(R.id.description,
177                         item.mDescription);
178             }
179             expandedView.setTextViewText(R.id.title, title);
180
181             if (hasPausedText) {
182                 expandedView.setViewVisibility(R.id.progress_bar, View.GONE);
183                 expandedView.setTextViewText(R.id.paused_text, item.mPausedText);
184             } else {
185                 expandedView.setViewVisibility(R.id.paused_text, View.GONE);
186                 expandedView.setProgressBar(R.id.progress_bar,
187                         (int) item.mTotalTotal,
188                         (int) item.mTotalCurrent,
189                         item.mTotalTotal == -1);
190             }
191             expandedView.setTextViewText(R.id.progress_text,
192                     getDownloadingText(item.mTotalTotal, item.mTotalCurrent));
193             expandedView.setImageViewResource(R.id.appIcon, iconResource);
194             n.contentView = expandedView;
195
196             Intent intent = new Intent(Constants.ACTION_LIST);
197             intent.setClassName("com.android.providers.downloads",
198                     DownloadReceiver.class.getName());
199             intent.setData(
200                     ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, item.mId));
201             intent.putExtra("multiple", item.mTitleCount > 1);
202
203             n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
204
205             mSystemFacade.postNotification(item.mId, n);
206
207         }
208     }
209
210     private void updateCompletedNotification(Collection<DownloadInfo> downloads) {
211         for (DownloadInfo download : downloads) {
212             if (!isCompleteAndVisible(download)) {
213                 continue;
214             }
215             // Add the notifications
216             Notification n = new Notification();
217             n.icon = android.R.drawable.stat_sys_download_done;
218
219             long id = download.mId;
220             String title = download.mTitle;
221             if (title == null || title.length() == 0) {
222                 title = mContext.getResources().getString(
223                         R.string.download_unknown_title);
224             }
225             Uri contentUri =
226                 ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
227             String caption;
228             Intent intent;
229             if (Downloads.Impl.isStatusError(download.mStatus)) {
230                 caption = mContext.getResources()
231                         .getString(R.string.notification_download_failed);
232                 intent = new Intent(Constants.ACTION_LIST);
233             } else {
234                 caption = mContext.getResources()
235                         .getString(R.string.notification_download_complete);
236                 if (download.mDestination == Downloads.Impl.DESTINATION_EXTERNAL) {
237                     intent = new Intent(Constants.ACTION_OPEN);
238                 } else {
239                     intent = new Intent(Constants.ACTION_LIST);
240                 }
241             }
242             intent.setClassName("com.android.providers.downloads",
243                     DownloadReceiver.class.getName());
244             intent.setData(contentUri);
245
246             n.when = download.mLastMod;
247             n.setLatestEventInfo(mContext, title, caption,
248                     PendingIntent.getBroadcast(mContext, 0, intent, 0));
249
250             intent = new Intent(Constants.ACTION_HIDE);
251             intent.setClassName("com.android.providers.downloads",
252                     DownloadReceiver.class.getName());
253             intent.setData(contentUri);
254             n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
255
256             mSystemFacade.postNotification(download.mId, n);
257         }
258     }
259
260     private boolean isActiveAndVisible(DownloadInfo download) {
261         return 100 <= download.mStatus && download.mStatus < 200
262                 && download.mVisibility != Downloads.Impl.VISIBILITY_HIDDEN;
263     }
264
265     private boolean isCompleteAndVisible(DownloadInfo download) {
266         return download.mStatus >= 200
267                 && download.mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
268     }
269
270     /*
271      * Helper function to build the downloading text.
272      */
273     private String getDownloadingText(long totalBytes, long currentBytes) {
274         if (totalBytes <= 0) {
275             return "";
276         }
277         long progress = currentBytes * 100 / totalBytes;
278         StringBuilder sb = new StringBuilder();
279         sb.append(progress);
280         sb.append('%');
281         return sb.toString();
282     }
283
284 }