| OLD | NEW |
| 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 Loading... |
| 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"; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 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"; |
| 88 | 90 |
| 89 static final String NOTIFICATION_NAMESPACE = "DownloadNotificationService"; | 91 static final String NOTIFICATION_NAMESPACE = "DownloadNotificationService"; |
| 90 private static final String TAG = "DownloadNotification"; | 92 private static final String TAG = "DownloadNotification"; |
| 91 // Limit file name to 25 characters. TODO(qinmin): use different limit for d
ifferent devices? | 93 // 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; | 94 private static final int MAX_FILE_NAME_LENGTH = 25; |
| 93 | 95 |
| 94 /** Notification Id starting value, to avoid conflicts from IDs used in prio
r versions. */ | 96 /** Notification Id starting value, to avoid conflicts from IDs used in prio
r versions. */ |
| 95 | 97 |
| 98 private static final String EXTRA_NOTIFICATION_BUNDLE_ICON_ID = |
| 99 "Chrome.NotificationBundleIconIdExtra"; |
| 96 private static final int STARTING_NOTIFICATION_ID = 1000000; | 100 private static final int STARTING_NOTIFICATION_ID = 1000000; |
| 97 private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5; | 101 private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5; |
| 98 @VisibleForTesting static final long SECONDS_PER_MINUTE = TimeUnit.MINUTES.t
oSeconds(1); | 102 @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); | 103 @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); | 104 @VisibleForTesting static final long SECONDS_PER_DAY = TimeUnit.DAYS.toSecon
ds(1); |
| 101 | 105 |
| 102 private static final String KEY_AUTO_RESUMPTION_ATTEMPT_LEFT = "ResumptionAt
temptLeft"; | 106 private static final String KEY_AUTO_RESUMPTION_ATTEMPT_LEFT = "ResumptionAt
temptLeft"; |
| 103 private static final String KEY_NEXT_DOWNLOAD_NOTIFICATION_ID = "NextDownloa
dNotificationId"; | 107 private static final String KEY_NEXT_DOWNLOAD_NOTIFICATION_ID = "NextDownloa
dNotificationId"; |
| 104 | 108 |
| 105 /** | 109 /** |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 138 * Start this service with a summary {@link Notification}. This will start
the service in the | 142 * Start this service with a summary {@link Notification}. This will start
the service in the |
| 139 * foreground. | 143 * foreground. |
| 140 * @param context The context used to build the notification and to start th
e service. | 144 * @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. | 145 * @param source The {@link Intent} that should be used to build on to start
the service. |
| 142 */ | 146 */ |
| 143 public static void startDownloadNotificationService(Context context, Intent
source) { | 147 public static void startDownloadNotificationService(Context context, Intent
source) { |
| 144 Intent intent = source != null ? new Intent(source) : new Intent(); | 148 Intent intent = source != null ? new Intent(source) : new Intent(); |
| 145 intent.setComponent(new ComponentName(context, DownloadNotificationServi
ce.class)); | 149 intent.setComponent(new ComponentName(context, DownloadNotificationServi
ce.class)); |
| 146 | 150 |
| 147 if (BuildInfo.isAtLeastO()) { | 151 if (BuildInfo.isAtLeastO()) { |
| 148 Notification notification = getSummaryNotification(context); | 152 Notification notification = buildSummaryNotification(context); |
| 149 | 153 |
| 150 AppHooks.get().startServiceWithNotification( | 154 AppHooks.get().startServiceWithNotification( |
| 151 intent, NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMA
RY, notification); | 155 intent, NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMA
RY, notification); |
| 152 } else { | 156 } else { |
| 153 context.startService(intent); | 157 context.startService(intent); |
| 154 } | 158 } |
| 155 } | 159 } |
| 156 | 160 |
| 157 /** | 161 /** |
| 158 * Builds a summary notification that represents downloads. This is the not
ification passed to | 162 * 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. | 163 * currently showing. |
| 160 * @param context The context used to build the notification and pull specif
ic resources. | 164 * @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 | 165 * @param removedNotificationId The id of a notification that is currently c
losing and should be |
| 162 * {@link NotificationManager#notify(int, Notification)}. | 166 * ignored. -1 if no notifications are being c
losed. |
| 167 * @param addedNotification A {@link Pair} of <id, Notification> of a no
tification that is |
| 168 * currently being added and should be used in
addition to or in |
| 169 * place of the existing icons. |
| 170 * @return A {@link Pair} that represents both whether
or not the new icon |
| 171 * is different from the old one and the icon i
d itself. |
| 163 */ | 172 */ |
| 164 private static Notification getSummaryNotification(Context context) { | 173 @TargetApi(Build.VERSION_CODES.M) |
| 174 private static Pair<Boolean, Integer> getSummaryIcon(Context context, int re
movedNotificationId, |
| 175 Pair<Integer, Notification> addedNotification) { |
| 176 if (!BuildInfo.isAtLeastO()) return new Pair<Boolean, Integer>(false, -1
); |
| 177 boolean progress = false; |
| 178 boolean paused = false; |
| 179 boolean pending = false; |
| 180 boolean completed = false; |
| 181 boolean failed = false; |
| 182 |
| 183 final int progressIcon = android.R.drawable.stat_sys_download; |
| 184 final int pausedIcon = R.drawable.ic_download_pause; |
| 185 final int pendingIcon = R.drawable.ic_download_pending; |
| 186 final int completedIcon = R.drawable.offline_pin; |
| 187 final int failedIcon = android.R.drawable.stat_sys_download_done; |
| 188 |
| 189 NotificationManager manager = |
| 190 (NotificationManager) context.getSystemService(Context.NOTIFICAT
ION_SERVICE); |
| 191 StatusBarNotification[] notifications = manager.getActiveNotifications()
; |
| 192 |
| 193 int oldIcon = -1; |
| 194 for (StatusBarNotification notification : notifications) { |
| 195 boolean isDownloadsGroup = TextUtils.equals(notification.getNotifica
tion().getGroup(), |
| 196 NotificationConstants.GROUP_DOWNLOADS); |
| 197 if (!isDownloadsGroup) continue; |
| 198 if (notification.getId() == removedNotificationId) continue; |
| 199 |
| 200 boolean isSummaryNotification = |
| 201 notification.getId() == NotificationConstants.NOTIFICATION_I
D_DOWNLOAD_SUMMARY; |
| 202 |
| 203 if (addedNotification != null && addedNotification.first == notifica
tion.getId()) |
| 204 continue; |
| 205 |
| 206 int icon = |
| 207 notification.getNotification().extras.getInt(EXTRA_NOTIFICAT
ION_BUNDLE_ICON_ID); |
| 208 if (isSummaryNotification) { |
| 209 oldIcon = icon; |
| 210 continue; |
| 211 } |
| 212 |
| 213 progress |= icon == progressIcon; |
| 214 paused |= icon == pausedIcon; |
| 215 pending |= icon == pendingIcon; |
| 216 completed |= icon == completedIcon; |
| 217 failed |= icon == failedIcon; |
| 218 } |
| 219 |
| 220 if (addedNotification != null) { |
| 221 int icon = addedNotification.second.extras.getInt(EXTRA_NOTIFICATION
_BUNDLE_ICON_ID); |
| 222 |
| 223 progress |= icon == progressIcon; |
| 224 paused |= icon == pausedIcon; |
| 225 pending |= icon == pendingIcon; |
| 226 completed |= icon == completedIcon; |
| 227 failed |= icon == failedIcon; |
| 228 } |
| 229 |
| 230 int newIcon = android.R.drawable.stat_sys_download_done; |
| 231 if (progress) { |
| 232 newIcon = android.R.drawable.stat_sys_download; |
| 233 } else if (paused) { |
| 234 newIcon = R.drawable.ic_download_pause; |
| 235 } else if (pending) { |
| 236 newIcon = R.drawable.ic_download_pending; |
| 237 } else if (completed) { |
| 238 newIcon = R.drawable.offline_pin; |
| 239 } else if (failed) { |
| 240 newIcon = android.R.drawable.stat_sys_download_done; |
| 241 } |
| 242 return new Pair<Boolean, Integer>(newIcon != oldIcon, newIcon); |
| 243 } |
| 244 |
| 245 /** |
| 246 * Builds a summary notification that represents all downloads. |
| 247 * {@see #buildSummaryNotification(Context)}. |
| 248 * @param context A context used to query Android strings and resources. |
| 249 * @param iconId The id of an icon to use for the notification. |
| 250 * @return a {@link Notification} that represents the summary icon fo
r all downloads. |
| 251 */ |
| 252 private static Notification buildSummaryNotificationWithIcon(Context context
, int iconId) { |
| 165 String title = | 253 String title = |
| 166 context.getResources().getString(R.string.download_notification_
summary_title, | 254 context.getResources().getString(R.string.download_notification_
summary_title, |
| 167 DownloadSharedPreferenceHelper.getInstance().getEntries(
).size()); | 255 DownloadSharedPreferenceHelper.getInstance().getEntries(
).size()); |
| 256 |
| 168 ChromeNotificationBuilder builder = | 257 ChromeNotificationBuilder builder = |
| 169 AppHooks.get() | 258 AppHooks.get() |
| 170 .createChromeNotificationBuilder(true /* preferCompat */
, | 259 .createChromeNotificationBuilder(true /* preferCompat */
, |
| 171 NotificationConstants.CATEGORY_ID_BROWSER, | 260 NotificationConstants.CATEGORY_ID_BROWSER, |
| 172 context.getString(R.string.notification_category
_browser), | 261 context.getString(R.string.notification_category
_browser), |
| 173 NotificationConstants.CATEGORY_GROUP_ID_GENERAL, | 262 NotificationConstants.CATEGORY_GROUP_ID_GENERAL, |
| 174 context.getString(R.string.notification_category
_group_general)) | 263 context.getString(R.string.notification_category
_group_general)) |
| 175 .setContentTitle(title) | 264 .setContentTitle(title) |
| 176 .setSmallIcon(android.R.drawable.stat_sys_download_done) | 265 .setSmallIcon(iconId) |
| 177 .setLocalOnly(true) | 266 .setLocalOnly(true) |
| 178 .setGroup(NotificationConstants.GROUP_DOWNLOADS) | 267 .setGroup(NotificationConstants.GROUP_DOWNLOADS) |
| 179 .setGroupSummary(true); | 268 .setGroupSummary(true); |
| 269 Bundle extras = new Bundle(); |
| 270 extras.putInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID, iconId); |
| 271 builder.addExtras(extras); |
| 180 | 272 |
| 181 // This notification should not actually be shown. But if it is, set th
e click intent to | 273 // This notification should not actually be shown. But if it is, set th
e click intent to |
| 182 // open downloads home. | 274 // open downloads home. |
| 183 Intent downloadHomeIntent = buildActionIntent( | 275 Intent downloadHomeIntent = buildActionIntent( |
| 184 context, DownloadManager.ACTION_NOTIFICATION_CLICKED, null, fals
e, false); | 276 context, DownloadManager.ACTION_NOTIFICATION_CLICKED, null, fals
e, false); |
| 185 builder.setContentIntent(PendingIntent.getBroadcast(context, | 277 builder.setContentIntent(PendingIntent.getBroadcast(context, |
| 186 NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, download
HomeIntent, | 278 NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, download
HomeIntent, |
| 187 PendingIntent.FLAG_UPDATE_CURRENT)); | 279 PendingIntent.FLAG_UPDATE_CURRENT)); |
| 188 | 280 |
| 189 return builder.build(); | 281 return builder.build(); |
| 190 } | 282 } |
| 191 | 283 |
| 192 /** | 284 /** |
| 285 * Builds a summary notification that represents downloads. This is the not
ification passed to |
| 286 * {@link #startForeground(int, Notification)}, which keeps this service in
the foreground. |
| 287 * @param context The context used to build the notification and pull specif
ic resources. |
| 288 * @return The {@link Notification} to show for the summary. Meant to be us
ed by |
| 289 * {@link NotificationManager#notify(int, Notification)}. |
| 290 */ |
| 291 private static Notification buildSummaryNotification(Context context) { |
| 292 Pair<Boolean, Integer> icon = getSummaryIcon(context, -1, null); |
| 293 return buildSummaryNotificationWithIcon(context, icon.second); |
| 294 } |
| 295 |
| 296 /** |
| 193 * @return Whether or not there are any current resumable downloads being tr
acked. These | 297 * @return Whether or not there are any current resumable downloads being tr
acked. These |
| 194 * tracked downloads may not currently be showing notifications. | 298 * tracked downloads may not currently be showing notifications. |
| 195 */ | 299 */ |
| 196 public static boolean isTrackingResumableDownloads(Context context) { | 300 public static boolean isTrackingResumableDownloads(Context context) { |
| 197 List<DownloadSharedPreferenceEntry> entries = | 301 List<DownloadSharedPreferenceEntry> entries = |
| 198 DownloadSharedPreferenceHelper.getInstance().getEntries(); | 302 DownloadSharedPreferenceHelper.getInstance().getEntries(); |
| 199 for (DownloadSharedPreferenceEntry entry : entries) { | 303 for (DownloadSharedPreferenceEntry entry : entries) { |
| 200 if (canResumeDownload(context, entry)) return true; | 304 if (canResumeDownload(context, entry)) return true; |
| 201 } | 305 } |
| 202 return false; | 306 return false; |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 298 */ | 402 */ |
| 299 @VisibleForTesting | 403 @VisibleForTesting |
| 300 @TargetApi(Build.VERSION_CODES.N) | 404 @TargetApi(Build.VERSION_CODES.N) |
| 301 void stopForegroundInternal(boolean killNotification) { | 405 void stopForegroundInternal(boolean killNotification) { |
| 302 if (!BuildInfo.isAtLeastO()) return; | 406 if (!BuildInfo.isAtLeastO()) return; |
| 303 stopForeground(killNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROU
ND_DETACH); | 407 stopForeground(killNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROU
ND_DETACH); |
| 304 } | 408 } |
| 305 | 409 |
| 306 /** | 410 /** |
| 307 * On >= O Android releases, puts this service into a foreground state, bind
ing it to the | 411 * On >= O Android releases, puts this service into a foreground state, bind
ing it to the |
| 308 * {@link Notification} generated by {@link #getSummaryNotification(Context)
}. | 412 * {@link Notification} generated by {@link #buildSummaryNotification(Contex
t)}. |
| 309 */ | 413 */ |
| 310 @VisibleForTesting | 414 @VisibleForTesting |
| 311 void startForegroundInternal() { | 415 void startForegroundInternal() { |
| 312 if (!BuildInfo.isAtLeastO()) return; | 416 if (!BuildInfo.isAtLeastO()) return; |
| 313 Notification notification = getSummaryNotification(getApplicationContext
()); | 417 Notification notification = buildSummaryNotification(getApplicationConte
xt()); |
| 314 startForeground(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY,
notification); | 418 startForeground(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY,
notification); |
| 315 } | 419 } |
| 316 | 420 |
| 317 private void rescheduleDownloads() { | 421 private void rescheduleDownloads() { |
| 318 List<DownloadSharedPreferenceEntry> entries = mDownloadSharedPreferenceH
elper.getEntries(); | 422 List<DownloadSharedPreferenceEntry> entries = mDownloadSharedPreferenceH
elper.getEntries(); |
| 319 if (entries.isEmpty()) return; | 423 if (entries.isEmpty()) return; |
| 320 | 424 |
| 321 boolean scheduleAutoResumption = false; | 425 boolean scheduleAutoResumption = false; |
| 322 boolean allowMeteredConnection = false; | 426 boolean allowMeteredConnection = false; |
| 323 for (int i = 0; i < entries.size(); ++i) { | 427 for (int i = 0; i < entries.size(); ++i) { |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 434 NotificationConstants.GROUP_DOWNLOADS); | 538 NotificationConstants.GROUP_DOWNLOADS); |
| 435 boolean isSummaryNotification = | 539 boolean isSummaryNotification = |
| 436 notification.getId() == NotificationConstants.NOTIFICATION_I
D_DOWNLOAD_SUMMARY; | 540 notification.getId() == NotificationConstants.NOTIFICATION_I
D_DOWNLOAD_SUMMARY; |
| 437 if (isDownloadsGroup && isSummaryNotification) return notification; | 541 if (isDownloadsGroup && isSummaryNotification) return notification; |
| 438 } | 542 } |
| 439 | 543 |
| 440 return null; | 544 return null; |
| 441 } | 545 } |
| 442 | 546 |
| 443 /** | 547 /** |
| 548 * Updates the notification summary with a new icon, if necessary. |
| 549 * @param removedNotificationId The id of a notification that is currently c
losing and should be |
| 550 * ignored. -1 if no notifications are being c
losed. |
| 551 * @param addedNotification A {@link Pair} of <id, Notification> of a no
tification that is |
| 552 * currently being added and should be used in
addition to or in |
| 553 * place of the existing icons. |
| 554 */ |
| 555 @VisibleForTesting |
| 556 void updateSummaryIcon( |
| 557 int removedNotificationId, Pair<Integer, Notification> addedNotifica
tion) { |
| 558 if (!BuildInfo.isAtLeastO()) return; |
| 559 |
| 560 Pair<Boolean, Integer> icon = |
| 561 getSummaryIcon(mContext, removedNotificationId, addedNotificatio
n); |
| 562 |
| 563 // Avoid rebuilding the summary notification if the icon hasn't changed
or we have (or are |
| 564 // about to have) no active downloads. |
| 565 if (!icon.first || !hasDownloadNotifications(removedNotificationId)) ret
urn; |
| 566 |
| 567 mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLO
AD_SUMMARY, |
| 568 buildSummaryNotificationWithIcon(mContext, icon.second)); |
| 569 } |
| 570 |
| 571 /** |
| 444 * Cancels the existing summary notification. Moved to a helper method for
test mocking. | 572 * Cancels the existing summary notification. Moved to a helper method for
test mocking. |
| 445 */ | 573 */ |
| 446 @VisibleForTesting | 574 @VisibleForTesting |
| 447 void cancelSummaryNotification() { | 575 void cancelSummaryNotification() { |
| 448 mNotificationManager.cancel(NotificationConstants.NOTIFICATION_ID_DOWNLO
AD_SUMMARY); | 576 mNotificationManager.cancel(NotificationConstants.NOTIFICATION_ID_DOWNLO
AD_SUMMARY); |
| 449 } | 577 } |
| 450 | 578 |
| 451 /** | 579 /** |
| 452 * Check all current notifications and hide the summary notification if we h
ave no downloads | 580 * 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 | 581 * 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 | 582 * 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 | 583 * {@link NotificationManager#cancel(int)}, the summary will remain, so we n
eed to check and |
| 456 * manually remove it ourselves. | 584 * manually remove it ourselves. |
| 457 * @param notificationIdToIgnore Canceling a notification and querying for t
he current list of | 585 * @param notificationIdToIgnore Canceling a notification and querying for t
he current list of |
| 458 * active notifications isn't synchronous. Pa
ss a notification id | 586 * active notifications isn't synchronous. Pa
ss a notification id |
| 459 * here if there is a notification that should
be assumed gone. | 587 * here if there is a notification that should
be assumed gone. |
| 460 * Or pass {@code null} if no notification fit
s that criteria. | 588 * Or pass {@code null} if no notification fit
s that criteria. |
| 461 */ | 589 */ |
| 462 @TargetApi(Build.VERSION_CODES.M) | 590 @TargetApi(Build.VERSION_CODES.M) |
| 463 void hideSummaryNotificationIfNecessary(Integer notificationIdToIgnore) { | 591 boolean hideSummaryNotificationIfNecessary(Integer notificationIdToIgnore) { |
| 464 if (!BuildInfo.isAtLeastO()) return; | 592 if (!BuildInfo.isAtLeastO()) return false; |
| 465 if (mDownloadsInProgress.size() > 0) return; | 593 if (mDownloadsInProgress.size() > 0) return false; |
| 466 | 594 |
| 467 if (hasDownloadNotifications(notificationIdToIgnore)) return; | 595 if (hasDownloadNotifications(notificationIdToIgnore)) return false; |
| 468 | 596 |
| 469 StatusBarNotification notification = getSummaryNotification(); | 597 StatusBarNotification notification = getSummaryNotification(); |
| 470 if (notification != null) { | 598 if (notification != null) { |
| 471 // We have a valid summary notification, but how we dismiss it depen
ds on whether or not | 599 // 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(...). | 600 // it is currently bound to this service via startForeground(...). |
| 473 if ((notification.getNotification().flags & Notification.FLAG_FOREGR
OUND_SERVICE) | 601 if ((notification.getNotification().flags & Notification.FLAG_FOREGR
OUND_SERVICE) |
| 474 != 0) { | 602 != 0) { |
| 475 // If we are a foreground service and we are hiding the notifica
tion, we have no | 603 // 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 | 604 // other downloads notifications showing, so we need to remove t
he notification and |
| 477 // unregister it from this service at the same time. | 605 // unregister it from this service at the same time. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 488 stopForegroundInternal(false); | 616 stopForegroundInternal(false); |
| 489 } | 617 } |
| 490 | 618 |
| 491 // Notify all observers that we are requesting a chance to shut down. T
his will let any | 619 // Notify all observers that we are requesting a chance to shut down. T
his will let any |
| 492 // observers unbind from us if necessary. | 620 // observers unbind from us if necessary. |
| 493 for (Observer observer : mObservers) observer.onServiceShutdownRequested
(); | 621 for (Observer observer : mObservers) observer.onServiceShutdownRequested
(); |
| 494 | 622 |
| 495 // Stop the service which should start the destruction process. At this
point we should be | 623 // 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. | 624 // (1) a background service and (2) unbound from any clients. |
| 497 stopSelf(); | 625 stopSelf(); |
| 626 return true; |
| 498 } | 627 } |
| 499 | 628 |
| 500 @Override | 629 @Override |
| 501 public IBinder onBind(Intent intent) { | 630 public IBinder onBind(Intent intent) { |
| 502 return mBinder; | 631 return mBinder; |
| 503 } | 632 } |
| 504 | 633 |
| 505 /** | 634 /** |
| 506 * Helper method to update the remaining number of background resumption att
empts left. | 635 * Helper method to update the remaining number of background resumption att
empts left. |
| 507 */ | 636 */ |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 647 */ | 776 */ |
| 648 @VisibleForTesting | 777 @VisibleForTesting |
| 649 void cancelNotification(int notificationId, String downloadGuid) { | 778 void cancelNotification(int notificationId, String downloadGuid) { |
| 650 mNotificationManager.cancel(NOTIFICATION_NAMESPACE, notificationId); | 779 mNotificationManager.cancel(NOTIFICATION_NAMESPACE, notificationId); |
| 651 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(downloadGuid
); | 780 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(downloadGuid
); |
| 652 | 781 |
| 653 // Since we are about to go through the process of validating whether or
not we can shut | 782 // 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 | 783 // 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. | 784 // the summary will take care of that for us. |
| 656 stopTrackingInProgressDownload(downloadGuid, hasDownloadNotifications(no
tificationId)); | 785 stopTrackingInProgressDownload(downloadGuid, hasDownloadNotifications(no
tificationId)); |
| 657 hideSummaryNotificationIfNecessary(notificationId); | 786 if (!hideSummaryNotificationIfNecessary(notificationId)) { |
| 787 updateSummaryIcon(notificationId, null); |
| 788 } |
| 658 } | 789 } |
| 659 | 790 |
| 660 /** | 791 /** |
| 661 * Called when a download is canceled. | 792 * Called when a download is canceled. |
| 662 * @param downloadGuid GUID of the download. | 793 * @param downloadGuid GUID of the download. |
| 663 */ | 794 */ |
| 664 @VisibleForTesting | 795 @VisibleForTesting |
| 665 public void notifyDownloadCanceled(String downloadGuid) { | 796 public void notifyDownloadCanceled(String downloadGuid) { |
| 666 DownloadSharedPreferenceEntry entry = | 797 DownloadSharedPreferenceEntry entry = |
| 667 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry
(downloadGuid); | 798 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry
(downloadGuid); |
| (...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 827 | 958 |
| 828 /** | 959 /** |
| 829 * Builds a notification to be displayed. | 960 * Builds a notification to be displayed. |
| 830 * @param iconId Id of the notification icon. | 961 * @param iconId Id of the notification icon. |
| 831 * @param title Title of the notification. | 962 * @param title Title of the notification. |
| 832 * @param contentText Notification content text to be displayed. | 963 * @param contentText Notification content text to be displayed. |
| 833 * @return notification builder that builds the notification to be displayed | 964 * @return notification builder that builds the notification to be displayed |
| 834 */ | 965 */ |
| 835 private ChromeNotificationBuilder buildNotification( | 966 private ChromeNotificationBuilder buildNotification( |
| 836 int iconId, String title, String contentText) { | 967 int iconId, String title, String contentText) { |
| 968 Bundle extras = new Bundle(); |
| 969 extras.putInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID, iconId); |
| 970 |
| 837 ChromeNotificationBuilder builder = | 971 ChromeNotificationBuilder builder = |
| 838 AppHooks.get() | 972 AppHooks.get() |
| 839 .createChromeNotificationBuilder(true /* preferCompat */
, | 973 .createChromeNotificationBuilder(true /* preferCompat */
, |
| 840 NotificationConstants.CATEGORY_ID_BROWSER, | 974 NotificationConstants.CATEGORY_ID_BROWSER, |
| 841 mContext.getString(R.string.notification_categor
y_browser), | 975 mContext.getString(R.string.notification_categor
y_browser), |
| 842 NotificationConstants.CATEGORY_GROUP_ID_GENERAL, | 976 NotificationConstants.CATEGORY_GROUP_ID_GENERAL, |
| 843 mContext.getString(R.string.notification_categor
y_group_general)) | 977 mContext.getString(R.string.notification_categor
y_group_general)) |
| 844 .setContentTitle( | 978 .setContentTitle( |
| 845 DownloadUtils.getAbbreviatedFileName(title, MAX_
FILE_NAME_LENGTH)) | 979 DownloadUtils.getAbbreviatedFileName(title, MAX_
FILE_NAME_LENGTH)) |
| 846 .setSmallIcon(iconId) | 980 .setSmallIcon(iconId) |
| 847 .setLocalOnly(true) | 981 .setLocalOnly(true) |
| 848 .setAutoCancel(true) | 982 .setAutoCancel(true) |
| 849 .setContentText(contentText) | 983 .setContentText(contentText) |
| 850 .setGroup(NotificationConstants.GROUP_DOWNLOADS); | 984 .setGroup(NotificationConstants.GROUP_DOWNLOADS) |
| 985 .addExtras(extras); |
| 851 return builder; | 986 return builder; |
| 852 } | 987 } |
| 853 | 988 |
| 854 private Bitmap getLargeNotificationIcon(Bitmap bitmap) { | 989 private Bitmap getLargeNotificationIcon(Bitmap bitmap) { |
| 855 Resources resources = mContext.getResources(); | 990 Resources resources = mContext.getResources(); |
| 856 int height = (int) resources.getDimension(android.R.dimen.notification_l
arge_icon_height); | 991 int height = (int) resources.getDimension(android.R.dimen.notification_l
arge_icon_height); |
| 857 int width = (int) resources.getDimension(android.R.dimen.notification_la
rge_icon_width); | 992 int width = (int) resources.getDimension(android.R.dimen.notification_la
rge_icon_width); |
| 858 final OvalShape circle = new OvalShape(); | 993 final OvalShape circle = new OvalShape(); |
| 859 circle.resize(width, height); | 994 circle.resize(width, height); |
| 860 final Paint paint = new Paint(); | 995 final Paint paint = new Paint(); |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1054 private void updateNotification(int id, Notification notification, String do
wnloadGuid, | 1189 private void updateNotification(int id, Notification notification, String do
wnloadGuid, |
| 1055 boolean isOfflinePage, DownloadSharedPreferenceEntry entry) { | 1190 boolean isOfflinePage, DownloadSharedPreferenceEntry entry) { |
| 1056 updateNotification(id, notification); | 1191 updateNotification(id, notification); |
| 1057 trackNotificationUma(isOfflinePage, downloadGuid); | 1192 trackNotificationUma(isOfflinePage, downloadGuid); |
| 1058 | 1193 |
| 1059 if (entry != null) { | 1194 if (entry != null) { |
| 1060 mDownloadSharedPreferenceHelper.addOrReplaceSharedPreferenceEntry(en
try); | 1195 mDownloadSharedPreferenceHelper.addOrReplaceSharedPreferenceEntry(en
try); |
| 1061 } else { | 1196 } else { |
| 1062 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(download
Guid); | 1197 mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(download
Guid); |
| 1063 } | 1198 } |
| 1199 updateSummaryIcon(-1, new Pair<Integer, Notification>(id, notification))
; |
| 1064 } | 1200 } |
| 1065 | 1201 |
| 1066 private void trackNotificationUma(boolean isOfflinePage, String downloadGuid
) { | 1202 private void trackNotificationUma(boolean isOfflinePage, String downloadGuid
) { |
| 1067 // Check if we already have an entry in the DownloadSharedPreferenceHelp
er. This is a | 1203 // 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 | 1204 // 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. | 1205 // we had built one for this download before. |
| 1070 if (mDownloadSharedPreferenceHelper.hasEntry(downloadGuid)) return; | 1206 if (mDownloadSharedPreferenceHelper.hasEntry(downloadGuid)) return; |
| 1071 NotificationUmaTracker.getInstance().onNotificationShown(isOfflinePage | 1207 NotificationUmaTracker.getInstance().onNotificationShown(isOfflinePage |
| 1072 ? NotificationUmaTracker.DOWNLOAD_PAGES | 1208 ? NotificationUmaTracker.DOWNLOAD_PAGES |
| 1073 : NotificationUmaTracker.DOWNLOAD_FILES); | 1209 : NotificationUmaTracker.DOWNLOAD_FILES); |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1183 return context.getString(R.string.remaining_duration_minutes, minute
s); | 1319 return context.getString(R.string.remaining_duration_minutes, minute
s); |
| 1184 } else if (minutes > 0) { | 1320 } else if (minutes > 0) { |
| 1185 return context.getString(R.string.remaining_duration_one_minute); | 1321 return context.getString(R.string.remaining_duration_one_minute); |
| 1186 } else if (seconds == 1) { | 1322 } else if (seconds == 1) { |
| 1187 return context.getString(R.string.remaining_duration_one_second); | 1323 return context.getString(R.string.remaining_duration_one_second); |
| 1188 } else { | 1324 } else { |
| 1189 return context.getString(R.string.remaining_duration_seconds, second
s); | 1325 return context.getString(R.string.remaining_duration_seconds, second
s); |
| 1190 } | 1326 } |
| 1191 } | 1327 } |
| 1192 } | 1328 } |
| OLD | NEW |