| Index: chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
|
| index 979b2ba628be529f56c999ee86db0770b2dea8c2..d3da69c8270e8c4783452f99501b4bf034ae8d3e 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
|
| @@ -23,9 +23,11 @@ import android.graphics.Rect;
|
| import android.graphics.drawable.shapes.OvalShape;
|
| import android.os.Binder;
|
| import android.os.Build;
|
| +import android.os.Bundle;
|
| import android.os.IBinder;
|
| import android.service.notification.StatusBarNotification;
|
| import android.text.TextUtils;
|
| +import android.util.Pair;
|
|
|
| import org.chromium.base.ApiCompatibilityUtils;
|
| import org.chromium.base.ApplicationStatus;
|
| @@ -59,7 +61,7 @@ import java.util.concurrent.TimeUnit;
|
| *
|
| * On O and above, this service will receive {@link Service#startForeground(int, Notification)}
|
| * calls when containing active downloads. The foreground notification will be the summary
|
| - * notification generated by {@link DownloadNotificationService#getSummaryNotification(Context)}.
|
| + * notification generated by {@link DownloadNotificationService#buildSummaryNotification(Context)}.
|
| * The service will receive a {@link Service#stopForeground(boolean)} call when all active downloads
|
| * are paused. The summary notification will be hidden when there are no other notifications in the
|
| * {@link NotificationConstants#GROUP_DOWNLOADS} group. This gets checked after every notification
|
| @@ -93,6 +95,8 @@ public class DownloadNotificationService extends Service {
|
|
|
| /** Notification Id starting value, to avoid conflicts from IDs used in prior versions. */
|
|
|
| + private static final String EXTRA_NOTIFICATION_BUNDLE_ICON_ID =
|
| + "Chrome.NotificationBundleIconIdExtra";
|
| private static final int STARTING_NOTIFICATION_ID = 1000000;
|
| private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5;
|
| @VisibleForTesting static final long SECONDS_PER_MINUTE = TimeUnit.MINUTES.toSeconds(1);
|
| @@ -145,7 +149,7 @@ public class DownloadNotificationService extends Service {
|
| intent.setComponent(new ComponentName(context, DownloadNotificationService.class));
|
|
|
| if (BuildInfo.isAtLeastO()) {
|
| - Notification notification = getSummaryNotification(context);
|
| + Notification notification = buildSummaryNotification(context);
|
|
|
| AppHooks.get().startServiceWithNotification(
|
| intent, NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, notification);
|
| @@ -155,16 +159,101 @@ public class DownloadNotificationService extends Service {
|
| }
|
|
|
| /**
|
| - * Builds a summary notification that represents downloads. This is the notification passed to
|
| - * {@link #startForeground(int, Notification)}, which keeps this service in the foreground.
|
| - * @param context The context used to build the notification and pull specific resources.
|
| - * @return The {@link Notification} to show for the summary. Meant to be used by
|
| - * {@link NotificationManager#notify(int, Notification)}.
|
| + * Calculates the suggested icon for the summary notification based on the other notifications
|
| + * currently showing.
|
| + * @param context A context to use to query Android-specific information (NotificationManager).
|
| + * @param removedNotificationId The id of a notification that is currently closing and should be
|
| + * ignored. -1 if no notifications are being closed.
|
| + * @param addedNotification A {@link Pair} of <id, Notification> of a notification that is
|
| + * currently being added and should be used in addition to or in
|
| + * place of the existing icons.
|
| + * @return A {@link Pair} that represents both whether or not the new icon
|
| + * is different from the old one and the icon id itself.
|
| + */
|
| + @TargetApi(Build.VERSION_CODES.M)
|
| + private static Pair<Boolean, Integer> getSummaryIcon(Context context, int removedNotificationId,
|
| + Pair<Integer, Notification> addedNotification) {
|
| + if (!BuildInfo.isAtLeastO()) return new Pair<Boolean, Integer>(false, -1);
|
| + boolean progress = false;
|
| + boolean paused = false;
|
| + boolean pending = false;
|
| + boolean completed = false;
|
| + boolean failed = false;
|
| +
|
| + final int progressIcon = android.R.drawable.stat_sys_download;
|
| + final int pausedIcon = R.drawable.ic_download_pause;
|
| + final int pendingIcon = R.drawable.ic_download_pending;
|
| + final int completedIcon = R.drawable.offline_pin;
|
| + final int failedIcon = android.R.drawable.stat_sys_download_done;
|
| +
|
| + NotificationManager manager =
|
| + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
| + StatusBarNotification[] notifications = manager.getActiveNotifications();
|
| +
|
| + int oldIcon = -1;
|
| + for (StatusBarNotification notification : notifications) {
|
| + boolean isDownloadsGroup = TextUtils.equals(notification.getNotification().getGroup(),
|
| + NotificationConstants.GROUP_DOWNLOADS);
|
| + if (!isDownloadsGroup) continue;
|
| + if (notification.getId() == removedNotificationId) continue;
|
| +
|
| + boolean isSummaryNotification =
|
| + notification.getId() == NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY;
|
| +
|
| + if (addedNotification != null && addedNotification.first == notification.getId())
|
| + continue;
|
| +
|
| + int icon =
|
| + notification.getNotification().extras.getInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID);
|
| + if (isSummaryNotification) {
|
| + oldIcon = icon;
|
| + continue;
|
| + }
|
| +
|
| + progress |= icon == progressIcon;
|
| + paused |= icon == pausedIcon;
|
| + pending |= icon == pendingIcon;
|
| + completed |= icon == completedIcon;
|
| + failed |= icon == failedIcon;
|
| + }
|
| +
|
| + if (addedNotification != null) {
|
| + int icon = addedNotification.second.extras.getInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID);
|
| +
|
| + progress |= icon == progressIcon;
|
| + paused |= icon == pausedIcon;
|
| + pending |= icon == pendingIcon;
|
| + completed |= icon == completedIcon;
|
| + failed |= icon == failedIcon;
|
| + }
|
| +
|
| + int newIcon = android.R.drawable.stat_sys_download_done;
|
| + if (progress) {
|
| + newIcon = android.R.drawable.stat_sys_download;
|
| + } else if (paused) {
|
| + newIcon = R.drawable.ic_download_pause;
|
| + } else if (pending) {
|
| + newIcon = R.drawable.ic_download_pending;
|
| + } else if (completed) {
|
| + newIcon = R.drawable.offline_pin;
|
| + } else if (failed) {
|
| + newIcon = android.R.drawable.stat_sys_download_done;
|
| + }
|
| + return new Pair<Boolean, Integer>(newIcon != oldIcon, newIcon);
|
| + }
|
| +
|
| + /**
|
| + * Builds a summary notification that represents all downloads.
|
| + * {@see #buildSummaryNotification(Context)}.
|
| + * @param context A context used to query Android strings and resources.
|
| + * @param iconId The id of an icon to use for the notification.
|
| + * @return a {@link Notification} that represents the summary icon for all downloads.
|
| */
|
| - private static Notification getSummaryNotification(Context context) {
|
| + private static Notification buildSummaryNotificationWithIcon(Context context, int iconId) {
|
| String title =
|
| context.getResources().getString(R.string.download_notification_summary_title,
|
| DownloadSharedPreferenceHelper.getInstance().getEntries().size());
|
| +
|
| ChromeNotificationBuilder builder =
|
| AppHooks.get()
|
| .createChromeNotificationBuilder(true /* preferCompat */,
|
| @@ -173,10 +262,13 @@ public class DownloadNotificationService extends Service {
|
| NotificationConstants.CATEGORY_GROUP_ID_GENERAL,
|
| context.getString(R.string.notification_category_group_general))
|
| .setContentTitle(title)
|
| - .setSmallIcon(android.R.drawable.stat_sys_download_done)
|
| + .setSmallIcon(iconId)
|
| .setLocalOnly(true)
|
| .setGroup(NotificationConstants.GROUP_DOWNLOADS)
|
| .setGroupSummary(true);
|
| + Bundle extras = new Bundle();
|
| + extras.putInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID, iconId);
|
| + builder.addExtras(extras);
|
|
|
| // This notification should not actually be shown. But if it is, set the click intent to
|
| // open downloads home.
|
| @@ -190,6 +282,18 @@ public class DownloadNotificationService extends Service {
|
| }
|
|
|
| /**
|
| + * Builds a summary notification that represents downloads. This is the notification passed to
|
| + * {@link #startForeground(int, Notification)}, which keeps this service in the foreground.
|
| + * @param context The context used to build the notification and pull specific resources.
|
| + * @return The {@link Notification} to show for the summary. Meant to be used by
|
| + * {@link NotificationManager#notify(int, Notification)}.
|
| + */
|
| + private static Notification buildSummaryNotification(Context context) {
|
| + Pair<Boolean, Integer> icon = getSummaryIcon(context, -1, null);
|
| + return buildSummaryNotificationWithIcon(context, icon.second);
|
| + }
|
| +
|
| + /**
|
| * @return Whether or not there are any current resumable downloads being tracked. These
|
| * tracked downloads may not currently be showing notifications.
|
| */
|
| @@ -305,12 +409,12 @@ public class DownloadNotificationService extends Service {
|
|
|
| /**
|
| * On >= O Android releases, puts this service into a foreground state, binding it to the
|
| - * {@link Notification} generated by {@link #getSummaryNotification(Context)}.
|
| + * {@link Notification} generated by {@link #buildSummaryNotification(Context)}.
|
| */
|
| @VisibleForTesting
|
| void startForegroundInternal() {
|
| if (!BuildInfo.isAtLeastO()) return;
|
| - Notification notification = getSummaryNotification(getApplicationContext());
|
| + Notification notification = buildSummaryNotification(getApplicationContext());
|
| startForeground(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY, notification);
|
| }
|
|
|
| @@ -441,6 +545,30 @@ public class DownloadNotificationService extends Service {
|
| }
|
|
|
| /**
|
| + * Updates the notification summary with a new icon, if necessary.
|
| + * @param removedNotificationId The id of a notification that is currently closing and should be
|
| + * ignored. -1 if no notifications are being closed.
|
| + * @param addedNotification A {@link Pair} of <id, Notification> of a notification that is
|
| + * currently being added and should be used in addition to or in
|
| + * place of the existing icons.
|
| + */
|
| + @VisibleForTesting
|
| + void updateSummaryIcon(
|
| + int removedNotificationId, Pair<Integer, Notification> addedNotification) {
|
| + if (!BuildInfo.isAtLeastO()) return;
|
| +
|
| + Pair<Boolean, Integer> icon =
|
| + getSummaryIcon(mContext, removedNotificationId, addedNotification);
|
| +
|
| + // Avoid rebuilding the summary notification if the icon hasn't changed or we have (or are
|
| + // about to have) no active downloads.
|
| + if (!icon.first || !hasDownloadNotifications(removedNotificationId)) return;
|
| +
|
| + mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_SUMMARY,
|
| + buildSummaryNotificationWithIcon(mContext, icon.second));
|
| + }
|
| +
|
| + /**
|
| * Cancels the existing summary notification. Moved to a helper method for test mocking.
|
| */
|
| @VisibleForTesting
|
| @@ -460,11 +588,11 @@ public class DownloadNotificationService extends Service {
|
| * Or pass {@code null} if no notification fits that criteria.
|
| */
|
| @TargetApi(Build.VERSION_CODES.M)
|
| - void hideSummaryNotificationIfNecessary(Integer notificationIdToIgnore) {
|
| - if (!BuildInfo.isAtLeastO()) return;
|
| - if (mDownloadsInProgress.size() > 0) return;
|
| + boolean hideSummaryNotificationIfNecessary(Integer notificationIdToIgnore) {
|
| + if (!BuildInfo.isAtLeastO()) return false;
|
| + if (mDownloadsInProgress.size() > 0) return false;
|
|
|
| - if (hasDownloadNotifications(notificationIdToIgnore)) return;
|
| + if (hasDownloadNotifications(notificationIdToIgnore)) return false;
|
|
|
| StatusBarNotification notification = getSummaryNotification();
|
| if (notification != null) {
|
| @@ -495,6 +623,7 @@ public class DownloadNotificationService extends Service {
|
| // Stop the service which should start the destruction process. At this point we should be
|
| // (1) a background service and (2) unbound from any clients.
|
| stopSelf();
|
| + return true;
|
| }
|
|
|
| @Override
|
| @@ -654,7 +783,9 @@ public class DownloadNotificationService extends Service {
|
| // down, don't stop foreground if we have no download notifications left to show. Hiding
|
| // the summary will take care of that for us.
|
| stopTrackingInProgressDownload(downloadGuid, hasDownloadNotifications(notificationId));
|
| - hideSummaryNotificationIfNecessary(notificationId);
|
| + if (!hideSummaryNotificationIfNecessary(notificationId)) {
|
| + updateSummaryIcon(notificationId, null);
|
| + }
|
| }
|
|
|
| /**
|
| @@ -834,6 +965,9 @@ public class DownloadNotificationService extends Service {
|
| */
|
| private ChromeNotificationBuilder buildNotification(
|
| int iconId, String title, String contentText) {
|
| + Bundle extras = new Bundle();
|
| + extras.putInt(EXTRA_NOTIFICATION_BUNDLE_ICON_ID, iconId);
|
| +
|
| ChromeNotificationBuilder builder =
|
| AppHooks.get()
|
| .createChromeNotificationBuilder(true /* preferCompat */,
|
| @@ -847,7 +981,8 @@ public class DownloadNotificationService extends Service {
|
| .setLocalOnly(true)
|
| .setAutoCancel(true)
|
| .setContentText(contentText)
|
| - .setGroup(NotificationConstants.GROUP_DOWNLOADS);
|
| + .setGroup(NotificationConstants.GROUP_DOWNLOADS)
|
| + .addExtras(extras);
|
| return builder;
|
| }
|
|
|
| @@ -1061,6 +1196,7 @@ public class DownloadNotificationService extends Service {
|
| } else {
|
| mDownloadSharedPreferenceHelper.removeSharedPreferenceEntry(downloadGuid);
|
| }
|
| + updateSummaryIcon(-1, new Pair<Integer, Notification>(id, notification));
|
| }
|
|
|
| private void trackNotificationUma(boolean isOfflinePage, String downloadGuid) {
|
|
|