Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(127)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java

Issue 2730243002: Update the download summary icon (Closed)
Patch Set: Fix case where the summary was not going away Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.download; 5 package org.chromium.chrome.browser.download;
6 6
7 import android.annotation.TargetApi; 7 import android.annotation.TargetApi;
8 import android.app.DownloadManager; 8 import android.app.DownloadManager;
9 import android.app.Notification; 9 import android.app.Notification;
10 import android.app.NotificationManager; 10 import android.app.NotificationManager;
11 import android.app.PendingIntent; 11 import android.app.PendingIntent;
12 import android.app.Service; 12 import android.app.Service;
13 import android.content.ComponentName; 13 import android.content.ComponentName;
14 import android.content.Context; 14 import android.content.Context;
15 import android.content.Intent; 15 import android.content.Intent;
16 import android.content.SharedPreferences; 16 import android.content.SharedPreferences;
17 import android.content.res.Resources; 17 import android.content.res.Resources;
18 import android.graphics.Bitmap; 18 import android.graphics.Bitmap;
19 import android.graphics.BitmapFactory; 19 import android.graphics.BitmapFactory;
20 import android.graphics.Canvas; 20 import android.graphics.Canvas;
21 import android.graphics.Paint; 21 import android.graphics.Paint;
22 import android.graphics.Rect; 22 import android.graphics.Rect;
23 import android.graphics.drawable.shapes.OvalShape; 23 import android.graphics.drawable.shapes.OvalShape;
24 import android.os.Binder; 24 import android.os.Binder;
25 import android.os.Build; 25 import android.os.Build;
26 import android.os.Bundle;
26 import android.os.IBinder; 27 import android.os.IBinder;
27 import android.service.notification.StatusBarNotification; 28 import android.service.notification.StatusBarNotification;
28 import android.text.TextUtils; 29 import android.text.TextUtils;
30 import android.util.Pair;
29 31
30 import org.chromium.base.ApiCompatibilityUtils; 32 import org.chromium.base.ApiCompatibilityUtils;
31 import org.chromium.base.ApplicationStatus; 33 import org.chromium.base.ApplicationStatus;
32 import org.chromium.base.BuildInfo; 34 import org.chromium.base.BuildInfo;
33 import org.chromium.base.ContextUtils; 35 import org.chromium.base.ContextUtils;
34 import org.chromium.base.Log; 36 import org.chromium.base.Log;
35 import org.chromium.base.ObserverList; 37 import org.chromium.base.ObserverList;
36 import org.chromium.base.VisibleForTesting; 38 import org.chromium.base.VisibleForTesting;
37 import org.chromium.base.library_loader.LibraryLoader; 39 import org.chromium.base.library_loader.LibraryLoader;
38 import org.chromium.base.library_loader.ProcessInitException; 40 import org.chromium.base.library_loader.ProcessInitException;
(...skipping 13 matching lines...) Expand all
52 import java.util.ArrayList; 54 import java.util.ArrayList;
53 import java.util.List; 55 import java.util.List;
54 import java.util.concurrent.TimeUnit; 56 import java.util.concurrent.TimeUnit;
55 57
56 /** 58 /**
57 * Service responsible for creating and updating download notifications even aft er 59 * Service responsible for creating and updating download notifications even aft er
58 * Chrome gets killed. 60 * Chrome gets killed.
59 * 61 *
60 * On O and above, this service will receive {@link Service#startForeground(int, Notification)} 62 * On O and above, this service will receive {@link Service#startForeground(int, Notification)}
61 * calls when containing active downloads. The foreground notification will be the summary 63 * calls when containing active downloads. The foreground notification will be the summary
62 * notification generated by {@link DownloadNotificationService#getSummaryNotifi cation(Context)}. 64 * notification generated by {@link DownloadNotificationService#buildSummaryNoti fication(Context)}.
63 * The service will receive a {@link Service#stopForeground(boolean)} call when all active downloads 65 * The service will receive a {@link Service#stopForeground(boolean)} call when all active downloads
64 * are paused. The summary notification will be hidden when there are no other notifications in the 66 * are paused. The summary notification will be hidden when there are no other notifications in the
65 * {@link NotificationConstants#GROUP_DOWNLOADS} group. This gets checked after every notification 67 * {@link NotificationConstants#GROUP_DOWNLOADS} group. This gets checked after every notification
66 * gets removed from the {@link NotificationManager}. 68 * gets removed from the {@link NotificationManager}.
67 */ 69 */
68 public class DownloadNotificationService extends Service { 70 public class DownloadNotificationService extends Service {
69 static final String EXTRA_DOWNLOAD_GUID = "DownloadGuid"; 71 static final String EXTRA_DOWNLOAD_GUID = "DownloadGuid";
70 static final String EXTRA_DOWNLOAD_FILE_PATH = "DownloadFilePath"; 72 static final String EXTRA_DOWNLOAD_FILE_PATH = "DownloadFilePath";
71 static final String EXTRA_NOTIFICATION_DISMISSED = "NotificationDismissed"; 73 static final String EXTRA_NOTIFICATION_DISMISSED = "NotificationDismissed";
72 static final String EXTRA_IS_SUPPORTED_MIME_TYPE = "IsSupportedMimeType"; 74 static final String EXTRA_IS_SUPPORTED_MIME_TYPE = "IsSupportedMimeType";
73 static final String EXTRA_IS_OFF_THE_RECORD = 75 static final String EXTRA_IS_OFF_THE_RECORD =
74 "org.chromium.chrome.browser.download.IS_OFF_THE_RECORD"; 76 "org.chromium.chrome.browser.download.IS_OFF_THE_RECORD";
75 static final String EXTRA_IS_OFFLINE_PAGE = 77 static final String EXTRA_IS_OFFLINE_PAGE =
76 "org.chromium.chrome.browser.download.IS_OFFLINE_PAGE"; 78 "org.chromium.chrome.browser.download.IS_OFFLINE_PAGE";
77 79
78 public static final String ACTION_DOWNLOAD_CANCEL = 80 public static final String ACTION_DOWNLOAD_CANCEL =
79 "org.chromium.chrome.browser.download.DOWNLOAD_CANCEL"; 81 "org.chromium.chrome.browser.download.DOWNLOAD_CANCEL";
80 public static final String ACTION_DOWNLOAD_PAUSE = 82 public static final String ACTION_DOWNLOAD_PAUSE =
81 "org.chromium.chrome.browser.download.DOWNLOAD_PAUSE"; 83 "org.chromium.chrome.browser.download.DOWNLOAD_PAUSE";
82 public static final String ACTION_DOWNLOAD_RESUME = 84 public static final String ACTION_DOWNLOAD_RESUME =
83 "org.chromium.chrome.browser.download.DOWNLOAD_RESUME"; 85 "org.chromium.chrome.browser.download.DOWNLOAD_RESUME";
84 static final String ACTION_DOWNLOAD_RESUME_ALL = 86 static final String ACTION_DOWNLOAD_RESUME_ALL =
85 "org.chromium.chrome.browser.download.DOWNLOAD_RESUME_ALL"; 87 "org.chromium.chrome.browser.download.DOWNLOAD_RESUME_ALL";
86 public static final String ACTION_DOWNLOAD_OPEN = 88 public static final String ACTION_DOWNLOAD_OPEN =
87 "org.chromium.chrome.browser.download.DOWNLOAD_OPEN"; 89 "org.chromium.chrome.browser.download.DOWNLOAD_OPEN";
90 public static final String ACTION_UPDATE_SUMMARY_ICON =
91 "org.chromium.chrome.browser.download.UPDATE_SUMMARY_ICON";
88 92
89 static final String NOTIFICATION_NAMESPACE = "DownloadNotificationService"; 93 static final String NOTIFICATION_NAMESPACE = "DownloadNotificationService";
90 private static final String TAG = "DownloadNotification"; 94 private static final String TAG = "DownloadNotification";
91 // Limit file name to 25 characters. TODO(qinmin): use different limit for d ifferent devices? 95 // Limit file name to 25 characters. TODO(qinmin): use different limit for d ifferent devices?
92 private static final int MAX_FILE_NAME_LENGTH = 25; 96 private static final int MAX_FILE_NAME_LENGTH = 25;
93 97
94 /** Notification Id starting value, to avoid conflicts from IDs used in prio r versions. */ 98 /** Notification Id starting value, to avoid conflicts from IDs used in prio r versions. */
95 99
100 private static final String EXTRA_NOTIFICATION_BUNDLE_ICON_ID =
101 "Chrome.NotificationBundleIconIdExtra";
96 private static final int STARTING_NOTIFICATION_ID = 1000000; 102 private static final int STARTING_NOTIFICATION_ID = 1000000;
97 private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5; 103 private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5;
98 @VisibleForTesting static final long SECONDS_PER_MINUTE = TimeUnit.MINUTES.t oSeconds(1); 104 @VisibleForTesting static final long SECONDS_PER_MINUTE = TimeUnit.MINUTES.t oSeconds(1);
99 @VisibleForTesting static final long SECONDS_PER_HOUR = TimeUnit.HOURS.toSec onds(1); 105 @VisibleForTesting static final long SECONDS_PER_HOUR = TimeUnit.HOURS.toSec onds(1);
100 @VisibleForTesting static final long SECONDS_PER_DAY = TimeUnit.DAYS.toSecon ds(1); 106 @VisibleForTesting static final long SECONDS_PER_DAY = TimeUnit.DAYS.toSecon ds(1);
101 107
102 private static final String KEY_AUTO_RESUMPTION_ATTEMPT_LEFT = "ResumptionAt temptLeft"; 108 private static final String KEY_AUTO_RESUMPTION_ATTEMPT_LEFT = "ResumptionAt temptLeft";
103 private static final String KEY_NEXT_DOWNLOAD_NOTIFICATION_ID = "NextDownloa dNotificationId"; 109 private static final String KEY_NEXT_DOWNLOAD_NOTIFICATION_ID = "NextDownloa dNotificationId";
104 110
105 /** 111 /**
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
138 * Start this service with a summary {@link Notification}. This will start the service in the 144 * Start this service with a summary {@link Notification}. This will start the service in the
139 * foreground. 145 * foreground.
140 * @param context The context used to build the notification and to start th e service. 146 * @param context The context used to build the notification and to start th e service.
141 * @param source The {@link Intent} that should be used to build on to start the service. 147 * @param source The {@link Intent} that should be used to build on to start the service.
142 */ 148 */
143 public static void startDownloadNotificationService(Context context, Intent source) { 149 public static void startDownloadNotificationService(Context context, Intent source) {
144 Intent intent = source != null ? new Intent(source) : new Intent(); 150 Intent intent = source != null ? new Intent(source) : new Intent();
145 intent.setComponent(new ComponentName(context, DownloadNotificationServi ce.class)); 151 intent.setComponent(new ComponentName(context, DownloadNotificationServi ce.class));
146 152
147 if (BuildInfo.isAtLeastO()) { 153 if (BuildInfo.isAtLeastO()) {
148 Notification notification = getSummaryNotification(context); 154 Notification notification = buildSummaryNotification(context);
149 155
150 AppHooks.get().startServiceWithNotification( 156 AppHooks.get().startServiceWithNotification(
151 intent, NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMA RY, notification); 157 intent, NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMA RY, notification);
152 } else { 158 } else {
153 context.startService(intent); 159 context.startService(intent);
154 } 160 }
155 } 161 }
156 162
157 /** 163 /**
158 * Builds a summary notification that represents downloads. This is the not ification passed to 164 * Calculates the suggested icon for the summary notification based on the o ther notifications
159 * {@link #startForeground(int, Notification)}, which keeps this service in the foreground. 165 * currently showing.
160 * @param context The context used to build the notification and pull specif ic resources. 166 * @param context A context to use to query Android-specific information (No tificationManager).
161 * @return The {@link Notification} to show for the summary. Meant to be us ed by 167 * @param removedNotificationId The id of a notification that is currently c losing and should be
162 * {@link NotificationManager#notify(int, Notification)}. 168 * ignored. -1 if no notifications are being c losed.
169 * @param addedNotification A {@link Pair} of <id, Notification> of a no tification that is
170 * currently being added and should be used in addition to or in
171 * place of the existing icons.
172 * @return A {@link Pair} that represents both whether or not the new icon
173 * is different from the old one and the icon i d itself.
163 */ 174 */
164 private static Notification getSummaryNotification(Context context) { 175 @TargetApi(Build.VERSION_CODES.M)
176 private static Pair<Boolean, Integer> getSummaryIcon(Context context, int re movedNotificationId,
177 Pair<Integer, Notification> addedNotification) {
178 if (!BuildInfo.isAtLeastO()) return new Pair<Boolean, Integer>(false, -1 );
179 boolean progress = false;
180 boolean paused = false;
181 boolean pending = false;
182 boolean completed = false;
183 boolean failed = false;
184
185 final int progressIcon = android.R.drawable.stat_sys_download;
186 final int pausedIcon = R.drawable.ic_download_pause;
187 final int pendingIcon = R.drawable.ic_download_pending;
188 final int completedIcon = R.drawable.offline_pin;
189 final int failedIcon = android.R.drawable.stat_sys_download_done;
190
191 NotificationManager manager =
192 (NotificationManager) context.getSystemService(Context.NOTIFICAT ION_SERVICE);
193 StatusBarNotification[] notifications = manager.getActiveNotifications() ;
194
195 int oldIcon = -1;
196 for (StatusBarNotification notification : notifications) {
197 boolean isDownloadsGroup = TextUtils.equals(notification.getNotifica tion().getGroup(),
198 NotificationConstants.GROUP_DOWNLOADS);
199 if (!isDownloadsGroup) continue;
200 if (notification.getId() == removedNotificationId) continue;
201
202 boolean isSummaryNotification =
203 notification.getId() == NotificationConstants.NOTIFICATION_I D_DOWNLOAD_SUMMARY;
204
205 if (addedNotification != null && addedNotification.first == notifica tion.getId())
qinmin 2017/03/05 06:48:26 if addedNotification is not null, this for loop sk
David Trainor- moved to gerrit 2017/03/06 16:29:54 The added notification might not be shown yet, so
qinmin 2017/03/06 21:43:43 i see, in that case, can we just initialize those
206 continue;
207
208 int icon =
209 notification.getNotification().extras.getInt(EXTRA_NOTIFICAT ION_BUNDLE_ICON_ID);
210 if (isSummaryNotification) {
211 oldIcon = icon;
212 continue;
213 }
214
215 progress |= icon == progressIcon;
216 paused |= icon == pausedIcon;
217 pending |= icon == pendingIcon;
218 completed |= icon == completedIcon;
219 failed |= icon == failedIcon;
220 }
221
222 if (addedNotification != null) {
223 int icon = addedNotification.second.extras.getInt(EXTRA_NOTIFICATION _BUNDLE_ICON_ID);
224
225 progress |= icon == progressIcon;
226 paused |= icon == pausedIcon;
227 pending |= icon == pendingIcon;
228 completed |= icon == completedIcon;
229 failed |= icon == failedIcon;
230 }
231
232 int newIcon = android.R.drawable.stat_sys_download_done;
233 if (progress) {
234 newIcon = android.R.drawable.stat_sys_download;
235 } else if (paused) {
236 newIcon = R.drawable.ic_download_pause;
237 } else if (pending) {
238 newIcon = R.drawable.ic_download_pending;
239 } else if (completed) {
240 newIcon = R.drawable.offline_pin;
241 } else if (failed) {
242 newIcon = android.R.drawable.stat_sys_download_done;
243 }
244 return new Pair<Boolean, Integer>(newIcon != oldIcon, newIcon);
245 }
246
247 /**
248 * Builds a summary notification that represents all downloads.
249 * {@see #buildSummaryNotification(Context)}.
250 * @param context A context used to query Android strings and resources.
251 * @param iconId The id of an icon to use for the notification.
252 * @return a {@link Notification} that represents the summary icon fo r all downloads.
253 */
254 private static Notification buildSummaryNotificationWithIcon(Context context , int iconId) {
165 String title = 255 String title =
166 context.getResources().getString(R.string.download_notification_ summary_title, 256 context.getResources().getString(R.string.download_notification_ summary_title,
167 DownloadSharedPreferenceHelper.getInstance().getEntries( ).size()); 257 DownloadSharedPreferenceHelper.getInstance().getEntries( ).size());
258
168 ChromeNotificationBuilder builder = 259 ChromeNotificationBuilder builder =
169 AppHooks.get() 260 AppHooks.get()
170 .createChromeNotificationBuilder(true /* preferCompat */ , 261 .createChromeNotificationBuilder(true /* preferCompat */ ,
171 NotificationConstants.CATEGORY_ID_BROWSER, 262 NotificationConstants.CATEGORY_ID_BROWSER,
172 context.getString(R.string.notification_category _browser), 263 context.getString(R.string.notification_category _browser),
173 NotificationConstants.CATEGORY_GROUP_ID_GENERAL, 264 NotificationConstants.CATEGORY_GROUP_ID_GENERAL,
174 context.getString(R.string.notification_category _group_general)) 265 context.getString(R.string.notification_category _group_general))
175 .setContentTitle(title) 266 .setContentTitle(title)
176 .setSmallIcon(android.R.drawable.stat_sys_download_done) 267 .setSmallIcon(iconId)
177 .setLocalOnly(true) 268 .setLocalOnly(true)
178 .setGroup(NotificationConstants.GROUP_DOWNLOADS) 269 .setGroup(NotificationConstants.GROUP_DOWNLOADS)
179 .setGroupSummary(true); 270 .setGroupSummary(true);
271 Bundle extras = new Bundle();
272 extras.putInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID, iconId);
273 builder.addExtras(extras);
180 274
181 // This notification should not actually be shown. But if it is, set th e click intent to 275 // This notification should not actually be shown. But if it is, set th e click intent to
182 // open downloads home. 276 // open downloads home.
183 Intent downloadHomeIntent = buildActionIntent( 277 Intent downloadHomeIntent = buildActionIntent(
184 context, DownloadManager.ACTION_NOTIFICATION_CLICKED, null, fals e, false); 278 context, DownloadManager.ACTION_NOTIFICATION_CLICKED, null, fals e, false);
185 builder.setContentIntent(PendingIntent.getBroadcast(context, 279 builder.setContentIntent(PendingIntent.getBroadcast(context,
186 NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, download HomeIntent, 280 NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, download HomeIntent,
187 PendingIntent.FLAG_UPDATE_CURRENT)); 281 PendingIntent.FLAG_UPDATE_CURRENT));
188 282
189 return builder.build(); 283 return builder.build();
190 } 284 }
191 285
192 /** 286 /**
287 * Builds a summary notification that represents downloads. This is the not ification passed to
288 * {@link #startForeground(int, Notification)}, which keeps this service in the foreground.
289 * @param context The context used to build the notification and pull specif ic resources.
290 * @return The {@link Notification} to show for the summary. Meant to be us ed by
291 * {@link NotificationManager#notify(int, Notification)}.
292 */
293 private static Notification buildSummaryNotification(Context context) {
294 Pair<Boolean, Integer> icon = getSummaryIcon(context, -1, null);
295 return buildSummaryNotificationWithIcon(context, icon.second);
296 }
297
298 /**
193 * @return Whether or not there are any current resumable downloads being tr acked. These 299 * @return Whether or not there are any current resumable downloads being tr acked. These
194 * tracked downloads may not currently be showing notifications. 300 * tracked downloads may not currently be showing notifications.
195 */ 301 */
196 public static boolean isTrackingResumableDownloads(Context context) { 302 public static boolean isTrackingResumableDownloads(Context context) {
197 List<DownloadSharedPreferenceEntry> entries = 303 List<DownloadSharedPreferenceEntry> entries =
198 DownloadSharedPreferenceHelper.getInstance().getEntries(); 304 DownloadSharedPreferenceHelper.getInstance().getEntries();
199 for (DownloadSharedPreferenceEntry entry : entries) { 305 for (DownloadSharedPreferenceEntry entry : entries) {
200 if (canResumeDownload(context, entry)) return true; 306 if (canResumeDownload(context, entry)) return true;
201 } 307 }
202 return false; 308 return false;
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
261 if (ACTION_DOWNLOAD_RESUME_ALL.equals(intent.getAction())) { 367 if (ACTION_DOWNLOAD_RESUME_ALL.equals(intent.getAction())) {
262 if (mNumAutoResumptionAttemptLeft > 0) { 368 if (mNumAutoResumptionAttemptLeft > 0) {
263 mNumAutoResumptionAttemptLeft--; 369 mNumAutoResumptionAttemptLeft--;
264 updateResumptionAttemptLeft(); 370 updateResumptionAttemptLeft();
265 } 371 }
266 } else { 372 } else {
267 // Reset number of attempts left if the action is triggered by u ser. 373 // Reset number of attempts left if the action is triggered by u ser.
268 mNumAutoResumptionAttemptLeft = MAX_RESUMPTION_ATTEMPT_LEFT; 374 mNumAutoResumptionAttemptLeft = MAX_RESUMPTION_ATTEMPT_LEFT;
269 clearResumptionAttemptLeft(); 375 clearResumptionAttemptLeft();
270 } 376 }
377 } else if (ACTION_UPDATE_SUMMARY_ICON.equals(intent.getAction())) {
378 updateSummaryIcon(-1, null);
379 hideSummaryNotificationIfNecessary(null);
271 } 380 }
272 // This should restart the service after Chrome gets killed. However, th is 381 // This should restart the service after Chrome gets killed. However, th is
273 // doesn't work on Android 4.4.2. 382 // doesn't work on Android 4.4.2.
274 return START_STICKY; 383 return START_STICKY;
275 } 384 }
276 385
277 /** 386 /**
278 * Adds an {@link Observer}, which will be notified when this service attemp ts to 387 * Adds an {@link Observer}, which will be notified when this service attemp ts to
279 * start stopping itself. 388 * start stopping itself.
280 */ 389 */
(...skipping 17 matching lines...) Expand all
298 */ 407 */
299 @VisibleForTesting 408 @VisibleForTesting
300 @TargetApi(Build.VERSION_CODES.N) 409 @TargetApi(Build.VERSION_CODES.N)
301 void stopForegroundInteral(boolean killNotification) { 410 void stopForegroundInteral(boolean killNotification) {
302 if (!BuildInfo.isAtLeastO()) return; 411 if (!BuildInfo.isAtLeastO()) return;
303 stopForeground(killNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROU ND_DETACH); 412 stopForeground(killNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROU ND_DETACH);
304 } 413 }
305 414
306 /** 415 /**
307 * On >= O Android releases, puts this service into a foreground state, bind ing it to the 416 * On >= O Android releases, puts this service into a foreground state, bind ing it to the
308 * {@link Notification} generated by {@link #getSummaryNotification(Context) }. 417 * {@link Notification} generated by {@link #buildSummaryNotification(Contex t)}.
309 */ 418 */
310 @VisibleForTesting 419 @VisibleForTesting
311 void startForegroundInternal() { 420 void startForegroundInternal() {
312 if (!BuildInfo.isAtLeastO()) return; 421 if (!BuildInfo.isAtLeastO()) return;
313 Notification notification = getSummaryNotification(getApplicationContext ()); 422 Notification notification = buildSummaryNotification(getApplicationConte xt());
314 startForeground(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, notification); 423 startForeground(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, notification);
315 } 424 }
316 425
317 private void rescheduleDownloads() { 426 private void rescheduleDownloads() {
318 List<DownloadSharedPreferenceEntry> entries = mDownloadSharedPreferenceH elper.getEntries(); 427 List<DownloadSharedPreferenceEntry> entries = mDownloadSharedPreferenceH elper.getEntries();
319 if (entries.isEmpty()) return; 428 if (entries.isEmpty()) return;
320 429
321 boolean scheduleAutoResumption = false; 430 boolean scheduleAutoResumption = false;
322 boolean allowMeteredConnection = false; 431 boolean allowMeteredConnection = false;
323 for (int i = 0; i < entries.size(); ++i) { 432 for (int i = 0; i < entries.size(); ++i) {
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
434 NotificationConstants.GROUP_DOWNLOADS); 543 NotificationConstants.GROUP_DOWNLOADS);
435 boolean isSummaryNotification = 544 boolean isSummaryNotification =
436 notification.getId() == NotificationConstants.NOTIFICATION_I D_DOWNLOAD_SUMMARY; 545 notification.getId() == NotificationConstants.NOTIFICATION_I D_DOWNLOAD_SUMMARY;
437 if (isDownloadsGroup && isSummaryNotification) return notification; 546 if (isDownloadsGroup && isSummaryNotification) return notification;
438 } 547 }
439 548
440 return null; 549 return null;
441 } 550 }
442 551
443 /** 552 /**
553 * Updates the notification summary with a new icon, if necessary.
554 * @param removedNotificationId The id of a notification that is currently c losing and should be
555 * ignored. -1 if no notifications are being c losed.
556 * @param addedNotification A {@link Pair} of <id, Notification> of a no tification that is
557 * currently being added and should be used in addition to or in
558 * place of the existing icons.
559 */
560 @VisibleForTesting
561 void updateSummaryIcon(
562 int removedNotificationId, Pair<Integer, Notification> addedNotifica tion) {
563 if (!BuildInfo.isAtLeastO()) return;
564
565 Pair<Boolean, Integer> icon =
566 getSummaryIcon(mContext, removedNotificationId, addedNotificatio n);
567
568 // Avoid rebuilding the summary notification if the icon hasn't changed or we have (or are
569 // about to have) no active downloads.
570 if (!icon.first || !hasDownloadNotifications(removedNotificationId)) {
571 return;
572 }
573
574 mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLO AD_SUMMARY,
575 buildSummaryNotificationWithIcon(mContext, icon.second));
576 }
577
578 /**
444 * Cancels the existing summary notification. Moved to a helper method for test mocking. 579 * Cancels the existing summary notification. Moved to a helper method for test mocking.
445 */ 580 */
446 @VisibleForTesting 581 @VisibleForTesting
447 void cancelSummaryNotification() { 582 void cancelSummaryNotification() {
448 mNotificationManager.cancel(NotificationConstants.NOTIFICATION_ID_DOWNLO AD_SUMMARY); 583 mNotificationManager.cancel(NotificationConstants.NOTIFICATION_ID_DOWNLO AD_SUMMARY);
449 } 584 }
450 585
451 /** 586 /**
452 * Check all current notifications and hide the summary notification if we h ave no downloads 587 * Check all current notifications and hide the summary notification if we h ave no downloads
453 * notifications left. On Android if the user swipes away the last download notification the 588 * notifications left. On Android if the user swipes away the last download notification the
454 * summary will be dismissed. But if the last downloads notification is dis missed via 589 * summary will be dismissed. But if the last downloads notification is dis missed via
455 * {@link NotificationManager#cancel(int)}, the summary will remain, so we n eed to check and 590 * {@link NotificationManager#cancel(int)}, the summary will remain, so we n eed to check and
456 * manually remove it ourselves. 591 * manually remove it ourselves.
457 * @param notificationIdToIgnore Canceling a notification and querying for t he current list of 592 * @param notificationIdToIgnore Canceling a notification and querying for t he current list of
458 * active notifications isn't synchronous. Pa ss a notification id 593 * active notifications isn't synchronous. Pa ss a notification id
459 * here if there is a notification that should be assumed gone. 594 * here if there is a notification that should be assumed gone.
460 * Or pass {@code null} if no notification fit s that criteria. 595 * Or pass {@code null} if no notification fit s that criteria.
461 */ 596 */
462 @TargetApi(Build.VERSION_CODES.M) 597 @TargetApi(Build.VERSION_CODES.M)
463 void hideSummaryNotificationIfNecessary(Integer notificationIdToIgnore) { 598 boolean hideSummaryNotificationIfNecessary(Integer notificationIdToIgnore) {
464 if (!BuildInfo.isAtLeastO()) return; 599 if (!BuildInfo.isAtLeastO()) return false;
465 if (mDownloadsInProgress.size() > 0) return; 600 if (mDownloadsInProgress.size() > 0) return false;
466 601
467 if (hasDownloadNotifications(notificationIdToIgnore)) return; 602 if (hasDownloadNotifications(notificationIdToIgnore)) return false;
468 603
469 StatusBarNotification notification = getSummaryNotification(); 604 StatusBarNotification notification = getSummaryNotification();
470 if (notification != null) { 605 if (notification != null) {
471 // We have a valid summary notification, but how we dismiss it depen ds on whether or not 606 // We have a valid summary notification, but how we dismiss it depen ds on whether or not
472 // it is currently bound to this service via startForeground(...). 607 // it is currently bound to this service via startForeground(...).
473 if ((notification.getNotification().flags & Notification.FLAG_FOREGR OUND_SERVICE) 608 if ((notification.getNotification().flags & Notification.FLAG_FOREGR OUND_SERVICE)
474 != 0) { 609 != 0) {
475 // If we are a foreground service and we are hiding the notifica tion, we have no 610 // If we are a foreground service and we are hiding the notifica tion, we have no
476 // other downloads notifications showing, so we need to remove t he notification and 611 // other downloads notifications showing, so we need to remove t he notification and
477 // unregister it from this service at the same time. 612 // unregister it from this service at the same time.
(...skipping 10 matching lines...) Expand all
488 stopForegroundInteral(false); 623 stopForegroundInteral(false);
489 } 624 }
490 625
491 // Notify all observers that we are requesting a chance to shut down. T his will let any 626 // Notify all observers that we are requesting a chance to shut down. T his will let any
492 // observers unbind from us if necessary. 627 // observers unbind from us if necessary.
493 for (Observer observer : mObservers) observer.onServiceShutdownRequested (); 628 for (Observer observer : mObservers) observer.onServiceShutdownRequested ();
494 629
495 // Stop the service which should start the destruction process. At this point we should be 630 // Stop the service which should start the destruction process. At this point we should be
496 // (1) a background service and (2) unbound from any clients. 631 // (1) a background service and (2) unbound from any clients.
497 stopSelf(); 632 stopSelf();
633 return true;
498 } 634 }
499 635
500 @Override 636 @Override
501 public IBinder onBind(Intent intent) { 637 public IBinder onBind(Intent intent) {
502 return mBinder; 638 return mBinder;
503 } 639 }
504 640
505 /** 641 /**
506 * Helper method to update the remaining number of background resumption att empts left. 642 * Helper method to update the remaining number of background resumption att empts left.
507 */ 643 */
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
647 */ 783 */
648 @VisibleForTesting 784 @VisibleForTesting
649 void cancelNotification(int notificationId, String downloadGuid) { 785 void cancelNotification(int notificationId, String downloadGuid) {
650 mNotificationManager.cancel(NOTIFICATION_NAMESPACE, notificationId); 786 mNotificationManager.cancel(NOTIFICATION_NAMESPACE, notificationId);
651 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(downloadGuid ); 787 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(downloadGuid );
652 788
653 // Since we are about to go through the process of validating whether or not we can shut 789 // Since we are about to go through the process of validating whether or not we can shut
654 // down, don't stop foreground if we have no download notifications left to show. Hiding 790 // down, don't stop foreground if we have no download notifications left to show. Hiding
655 // the summary will take care of that for us. 791 // the summary will take care of that for us.
656 stopTrackingInProgressDownload(downloadGuid, hasDownloadNotifications(no tificationId)); 792 stopTrackingInProgressDownload(downloadGuid, hasDownloadNotifications(no tificationId));
657 hideSummaryNotificationIfNecessary(notificationId); 793 if (!hideSummaryNotificationIfNecessary(notificationId)) {
794 updateSummaryIcon(notificationId, null);
795 }
658 } 796 }
659 797
660 /** 798 /**
661 * Called when a download is canceled. 799 * Called when a download is canceled.
662 * @param downloadGuid GUID of the download. 800 * @param downloadGuid GUID of the download.
663 */ 801 */
664 @VisibleForTesting 802 @VisibleForTesting
665 public void notifyDownloadCanceled(String downloadGuid) { 803 public void notifyDownloadCanceled(String downloadGuid) {
666 DownloadSharedPreferenceEntry entry = 804 DownloadSharedPreferenceEntry entry =
667 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry (downloadGuid); 805 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry (downloadGuid);
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
762 } 900 }
763 intent.setComponent(component); 901 intent.setComponent(component);
764 builder.setContentIntent(PendingIntent.getBroadcast( 902 builder.setContentIntent(PendingIntent.getBroadcast(
765 mContext, notificationId, intent, PendingIntent.FLAG_UPDATE_CURR ENT)); 903 mContext, notificationId, intent, PendingIntent.FLAG_UPDATE_CURR ENT));
766 if (mDownloadSuccessLargeIcon == null) { 904 if (mDownloadSuccessLargeIcon == null) {
767 Bitmap bitmap = BitmapFactory.decodeResource( 905 Bitmap bitmap = BitmapFactory.decodeResource(
768 mContext.getResources(), R.drawable.offline_pin); 906 mContext.getResources(), R.drawable.offline_pin);
769 mDownloadSuccessLargeIcon = getLargeNotificationIcon(bitmap); 907 mDownloadSuccessLargeIcon = getLargeNotificationIcon(bitmap);
770 } 908 }
771 builder.setLargeIcon(mDownloadSuccessLargeIcon); 909 builder.setLargeIcon(mDownloadSuccessLargeIcon);
910 builder.setDeleteIntent(buildIconUpdateIntent(notificationId));
772 updateNotification(notificationId, builder.build(), downloadGuid, isOffl inePage, null); 911 updateNotification(notificationId, builder.build(), downloadGuid, isOffl inePage, null);
773 stopTrackingInProgressDownload(downloadGuid, true); 912 stopTrackingInProgressDownload(downloadGuid, true);
774 return notificationId; 913 return notificationId;
775 } 914 }
776 915
777 /** 916 /**
778 * Add a download failed notification. 917 * Add a download failed notification.
779 * @param isOfflinePage Whether or not the download was for an offline page. 918 * @param isOfflinePage Whether or not the download was for an offline page.
780 * @param downloadGuid GUID of the download. 919 * @param downloadGuid GUID of the download.
781 * @param fileName GUID of the download. 920 * @param fileName GUID of the download.
782 */ 921 */
783 @VisibleForTesting 922 @VisibleForTesting
784 public void notifyDownloadFailed(boolean isOfflinePage, String downloadGuid, String fileName) { 923 public void notifyDownloadFailed(boolean isOfflinePage, String downloadGuid, String fileName) {
785 // If the download is not in history db, fileName could be empty. Get it from 924 // If the download is not in history db, fileName could be empty. Get it from
786 // SharedPreferences. 925 // SharedPreferences.
787 if (TextUtils.isEmpty(fileName)) { 926 if (TextUtils.isEmpty(fileName)) {
788 DownloadSharedPreferenceEntry entry = 927 DownloadSharedPreferenceEntry entry =
789 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceE ntry(downloadGuid); 928 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceE ntry(downloadGuid);
790 if (entry == null) return; 929 if (entry == null) return;
791 fileName = entry.fileName; 930 fileName = entry.fileName;
792 } 931 }
793 932
794 int notificationId = getNotificationId(downloadGuid); 933 int notificationId = getNotificationId(downloadGuid);
795 ChromeNotificationBuilder builder = 934 ChromeNotificationBuilder builder =
796 buildNotification(android.R.drawable.stat_sys_download_done, fil eName, 935 buildNotification(android.R.drawable.stat_sys_download_done, fil eName,
797 mContext.getResources().getString(R.string.download_noti fication_failed)); 936 mContext.getResources().getString(R.string.download_noti fication_failed));
937 builder.setDeleteIntent(buildIconUpdateIntent(notificationId));
798 updateNotification(notificationId, builder.build(), downloadGuid, isOffl inePage, null); 938 updateNotification(notificationId, builder.build(), downloadGuid, isOffl inePage, null);
799 stopTrackingInProgressDownload(downloadGuid, true); 939 stopTrackingInProgressDownload(downloadGuid, true);
800 } 940 }
801 941
802 /** 942 /**
803 * Helper method to build a PendingIntent from the provided intent. 943 * Helper method to build a PendingIntent from the provided intent.
804 * @param intent Intent to broadcast. 944 * @param intent Intent to broadcast.
805 * @param notificationId ID of the notification. 945 * @param notificationId ID of the notification.
806 */ 946 */
807 private PendingIntent buildPendingIntent(Intent intent, int notificationId) { 947 private PendingIntent buildPendingIntent(Intent intent, int notificationId) {
808 return PendingIntent.getBroadcast( 948 return PendingIntent.getBroadcast(
809 mContext, notificationId, intent, PendingIntent.FLAG_UPDATE_CURR ENT); 949 mContext, notificationId, intent, PendingIntent.FLAG_UPDATE_CURR ENT);
810 } 950 }
811 951
812 /** 952 /**
953 * Helper method to build a {@link PendingIntent} that will wrap an {@link I ntent} that will
954 * update the summary notification icon.
955 * @param notificationId ID of the notification.
956 */
957 // ---- BEFORE LANDING NOTE ----
958 // TODO(dtrainor): This is not firing for the complete notification. Test o r remove this if we
959 // don't need it.
960 // ---- BEFORE LANDING NOTE ----
961 private PendingIntent buildIconUpdateIntent(int notificationId) {
962 Intent intent = new Intent(mContext, DownloadBroadcastReceiver.class);
963 intent.setAction(ACTION_UPDATE_SUMMARY_ICON);
964 return buildPendingIntent(intent, notificationId);
965 }
966
967 /**
813 * Helper method to build an download action Intent from the provided inform ation. 968 * Helper method to build an download action Intent from the provided inform ation.
814 * @param context {@link Context} to pull resources from. 969 * @param context {@link Context} to pull resources from.
815 * @param action Download action to perform. 970 * @param action Download action to perform.
816 * @param downloadGuid GUID of the download. 971 * @param downloadGuid GUID of the download.
817 * @param isOffTheRecord Whether the download is incognito. 972 * @param isOffTheRecord Whether the download is incognito.
818 * @param isOfflinePage Whether the download represents an Offline Page. 973 * @param isOfflinePage Whether the download represents an Offline Page.
819 */ 974 */
820 static Intent buildActionIntent(Context context, String action, String downl oadGuid, 975 static Intent buildActionIntent(Context context, String action, String downl oadGuid,
821 boolean isOffTheRecord, boolean isOfflinePage) { 976 boolean isOffTheRecord, boolean isOfflinePage) {
822 ComponentName component = new ComponentName( 977 ComponentName component = new ComponentName(
823 context.getPackageName(), DownloadBroadcastReceiver.class.getNam e()); 978 context.getPackageName(), DownloadBroadcastReceiver.class.getNam e());
824 Intent intent = new Intent(action); 979 Intent intent = new Intent(action);
825 intent.setComponent(component); 980 intent.setComponent(component);
826 intent.putExtra(EXTRA_DOWNLOAD_GUID, downloadGuid); 981 intent.putExtra(EXTRA_DOWNLOAD_GUID, downloadGuid);
827 intent.putExtra(EXTRA_IS_OFF_THE_RECORD, isOffTheRecord); 982 intent.putExtra(EXTRA_IS_OFF_THE_RECORD, isOffTheRecord);
828 intent.putExtra(EXTRA_IS_OFFLINE_PAGE, isOfflinePage); 983 intent.putExtra(EXTRA_IS_OFFLINE_PAGE, isOfflinePage);
829 return intent; 984 return intent;
830 } 985 }
831 986
832 /** 987 /**
833 * Builds a notification to be displayed. 988 * Builds a notification to be displayed.
834 * @param iconId Id of the notification icon. 989 * @param iconId Id of the notification icon.
835 * @param title Title of the notification. 990 * @param title Title of the notification.
836 * @param contentText Notification content text to be displayed. 991 * @param contentText Notification content text to be displayed.
837 * @return notification builder that builds the notification to be displayed 992 * @return notification builder that builds the notification to be displayed
838 */ 993 */
839 private ChromeNotificationBuilder buildNotification( 994 private ChromeNotificationBuilder buildNotification(
840 int iconId, String title, String contentText) { 995 int iconId, String title, String contentText) {
996 Bundle extras = new Bundle();
997 extras.putInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID, iconId);
998
841 ChromeNotificationBuilder builder = 999 ChromeNotificationBuilder builder =
842 AppHooks.get() 1000 AppHooks.get()
843 .createChromeNotificationBuilder(true /* preferCompat */ , 1001 .createChromeNotificationBuilder(true /* preferCompat */ ,
844 NotificationConstants.CATEGORY_ID_BROWSER, 1002 NotificationConstants.CATEGORY_ID_BROWSER,
845 mContext.getString(R.string.notification_categor y_browser), 1003 mContext.getString(R.string.notification_categor y_browser),
846 NotificationConstants.CATEGORY_GROUP_ID_GENERAL, 1004 NotificationConstants.CATEGORY_GROUP_ID_GENERAL,
847 mContext.getString(R.string.notification_categor y_group_general)) 1005 mContext.getString(R.string.notification_categor y_group_general))
848 .setContentTitle( 1006 .setContentTitle(
849 DownloadUtils.getAbbreviatedFileName(title, MAX_ FILE_NAME_LENGTH)) 1007 DownloadUtils.getAbbreviatedFileName(title, MAX_ FILE_NAME_LENGTH))
850 .setSmallIcon(iconId) 1008 .setSmallIcon(iconId)
851 .setLocalOnly(true) 1009 .setLocalOnly(true)
852 .setAutoCancel(true) 1010 .setAutoCancel(true)
853 .setContentText(contentText) 1011 .setContentText(contentText)
854 .setGroup(NotificationConstants.GROUP_DOWNLOADS); 1012 .setGroup(NotificationConstants.GROUP_DOWNLOADS)
1013 .addExtras(extras);
855 return builder; 1014 return builder;
856 } 1015 }
857 1016
858 private Bitmap getLargeNotificationIcon(Bitmap bitmap) { 1017 private Bitmap getLargeNotificationIcon(Bitmap bitmap) {
859 Resources resources = mContext.getResources(); 1018 Resources resources = mContext.getResources();
860 int height = (int) resources.getDimension(android.R.dimen.notification_l arge_icon_height); 1019 int height = (int) resources.getDimension(android.R.dimen.notification_l arge_icon_height);
861 int width = (int) resources.getDimension(android.R.dimen.notification_la rge_icon_width); 1020 int width = (int) resources.getDimension(android.R.dimen.notification_la rge_icon_width);
862 final OvalShape circle = new OvalShape(); 1021 final OvalShape circle = new OvalShape();
863 circle.resize(width, height); 1022 circle.resize(width, height);
864 final Paint paint = new Paint(); 1023 final Paint paint = new Paint();
(...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after
1054 private void updateNotification(int id, Notification notification, String do wnloadGuid, 1213 private void updateNotification(int id, Notification notification, String do wnloadGuid,
1055 boolean isOfflinePage, DownloadSharedPreferenceEntry entry) { 1214 boolean isOfflinePage, DownloadSharedPreferenceEntry entry) {
1056 updateNotification(id, notification); 1215 updateNotification(id, notification);
1057 trackNotificationUma(isOfflinePage, downloadGuid); 1216 trackNotificationUma(isOfflinePage, downloadGuid);
1058 1217
1059 if (entry != null) { 1218 if (entry != null) {
1060 mDownloadSharedPreferenceHelper.addOrReplaceSharedPreferenceEntry(en try); 1219 mDownloadSharedPreferenceHelper.addOrReplaceSharedPreferenceEntry(en try);
1061 } else { 1220 } else {
1062 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(download Guid); 1221 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(download Guid);
1063 } 1222 }
1223 updateSummaryIcon(-1, new Pair<Integer, Notification>(id, notification)) ;
1064 } 1224 }
1065 1225
1066 private void trackNotificationUma(boolean isOfflinePage, String downloadGuid ) { 1226 private void trackNotificationUma(boolean isOfflinePage, String downloadGuid ) {
1067 // Check if we already have an entry in the DownloadSharedPreferenceHelp er. This is a 1227 // Check if we already have an entry in the DownloadSharedPreferenceHelp er. This is a
1068 // reasonable indicator for whether or not a notification is already sho wing (or at least if 1228 // reasonable indicator for whether or not a notification is already sho wing (or at least if
1069 // we had built one for this download before. 1229 // we had built one for this download before.
1070 if (mDownloadSharedPreferenceHelper.hasEntry(downloadGuid)) return; 1230 if (mDownloadSharedPreferenceHelper.hasEntry(downloadGuid)) return;
1071 NotificationUmaTracker.getInstance().onNotificationShown(isOfflinePage 1231 NotificationUmaTracker.getInstance().onNotificationShown(isOfflinePage
1072 ? NotificationUmaTracker.DOWNLOAD_PAGES 1232 ? NotificationUmaTracker.DOWNLOAD_PAGES
1073 : NotificationUmaTracker.DOWNLOAD_FILES); 1233 : NotificationUmaTracker.DOWNLOAD_FILES);
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
1183 return context.getString(R.string.remaining_duration_minutes, minute s); 1343 return context.getString(R.string.remaining_duration_minutes, minute s);
1184 } else if (minutes > 0) { 1344 } else if (minutes > 0) {
1185 return context.getString(R.string.remaining_duration_one_minute); 1345 return context.getString(R.string.remaining_duration_one_minute);
1186 } else if (seconds == 1) { 1346 } else if (seconds == 1) {
1187 return context.getString(R.string.remaining_duration_one_second); 1347 return context.getString(R.string.remaining_duration_one_second);
1188 } else { 1348 } else {
1189 return context.getString(R.string.remaining_duration_seconds, second s); 1349 return context.getString(R.string.remaining_duration_seconds, second s);
1190 } 1350 }
1191 } 1351 }
1192 } 1352 }
OLDNEW
« no previous file with comments | « no previous file | chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698