| 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; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 49 import org.chromium.chrome.browser.notifications.ChannelDefinitions; | 49 import org.chromium.chrome.browser.notifications.ChannelDefinitions; |
| 50 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder; | 50 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder; |
| 51 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory; | 51 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory; |
| 52 import org.chromium.chrome.browser.notifications.NotificationConstants; | 52 import org.chromium.chrome.browser.notifications.NotificationConstants; |
| 53 import org.chromium.chrome.browser.notifications.NotificationUmaTracker; | 53 import org.chromium.chrome.browser.notifications.NotificationUmaTracker; |
| 54 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBri
dge; | 54 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBri
dge; |
| 55 import org.chromium.chrome.browser.profiles.Profile; | 55 import org.chromium.chrome.browser.profiles.Profile; |
| 56 import org.chromium.chrome.browser.util.IntentUtils; | 56 import org.chromium.chrome.browser.util.IntentUtils; |
| 57 import org.chromium.components.offline_items_collection.ContentId; | 57 import org.chromium.components.offline_items_collection.ContentId; |
| 58 import org.chromium.components.offline_items_collection.LegacyHelpers; | 58 import org.chromium.components.offline_items_collection.LegacyHelpers; |
| 59 import org.chromium.components.offline_items_collection.OfflineItem.Progress; |
| 59 import org.chromium.content.browser.BrowserStartupController; | 60 import org.chromium.content.browser.BrowserStartupController; |
| 60 | 61 |
| 61 import java.util.ArrayList; | 62 import java.util.ArrayList; |
| 62 import java.util.List; | 63 import java.util.List; |
| 63 import java.util.concurrent.TimeUnit; | |
| 64 | 64 |
| 65 /** | 65 /** |
| 66 * Service responsible for creating and updating download notifications even aft
er | 66 * Service responsible for creating and updating download notifications even aft
er |
| 67 * Chrome gets killed. | 67 * Chrome gets killed. |
| 68 * | 68 * |
| 69 * On O and above, this service will receive {@link Service#startForeground(int,
Notification)} | 69 * On O and above, this service will receive {@link Service#startForeground(int,
Notification)} |
| 70 * calls when containing active downloads. The foreground notification will be
the summary | 70 * calls when containing active downloads. The foreground notification will be
the summary |
| 71 * notification generated by {@link DownloadNotificationService#buildSummaryNoti
fication(Context)}. | 71 * notification generated by {@link DownloadNotificationService#buildSummaryNoti
fication(Context)}. |
| 72 * The service will receive a {@link Service#stopForeground(boolean)} call when
all active downloads | 72 * The service will receive a {@link Service#stopForeground(boolean)} call when
all active downloads |
| 73 * are paused. The summary notification will be hidden when there are no other
notifications in the | 73 * are paused. The summary notification will be hidden when there are no other
notifications in the |
| (...skipping 30 matching lines...) Expand all Loading... |
| 104 private static final String TAG = "DownloadNotification"; | 104 private static final String TAG = "DownloadNotification"; |
| 105 // Limit file name to 25 characters. TODO(qinmin): use different limit for d
ifferent devices? | 105 // Limit file name to 25 characters. TODO(qinmin): use different limit for d
ifferent devices? |
| 106 private static final int MAX_FILE_NAME_LENGTH = 25; | 106 private static final int MAX_FILE_NAME_LENGTH = 25; |
| 107 | 107 |
| 108 /** Notification Id starting value, to avoid conflicts from IDs used in prio
r versions. */ | 108 /** Notification Id starting value, to avoid conflicts from IDs used in prio
r versions. */ |
| 109 | 109 |
| 110 private static final String EXTRA_NOTIFICATION_BUNDLE_ICON_ID = | 110 private static final String EXTRA_NOTIFICATION_BUNDLE_ICON_ID = |
| 111 "Chrome.NotificationBundleIconIdExtra"; | 111 "Chrome.NotificationBundleIconIdExtra"; |
| 112 private static final int STARTING_NOTIFICATION_ID = 1000000; | 112 private static final int STARTING_NOTIFICATION_ID = 1000000; |
| 113 private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5; | 113 private static final int MAX_RESUMPTION_ATTEMPT_LEFT = 5; |
| 114 @VisibleForTesting static final long SECONDS_PER_MINUTE = TimeUnit.MINUTES.t
oSeconds(1); | |
| 115 @VisibleForTesting static final long SECONDS_PER_HOUR = TimeUnit.HOURS.toSec
onds(1); | |
| 116 @VisibleForTesting static final long SECONDS_PER_DAY = TimeUnit.DAYS.toSecon
ds(1); | |
| 117 | 114 |
| 118 private static final String KEY_AUTO_RESUMPTION_ATTEMPT_LEFT = "ResumptionAt
temptLeft"; | 115 private static final String KEY_AUTO_RESUMPTION_ATTEMPT_LEFT = "ResumptionAt
temptLeft"; |
| 119 private static final String KEY_NEXT_DOWNLOAD_NOTIFICATION_ID = "NextDownloa
dNotificationId"; | 116 private static final String KEY_NEXT_DOWNLOAD_NOTIFICATION_ID = "NextDownloa
dNotificationId"; |
| 120 | 117 |
| 121 /** | 118 /** |
| 122 * An Observer interface that allows other classes to know when this class i
s canceling | 119 * An Observer interface that allows other classes to know when this class i
s canceling |
| 123 * downloads. | 120 * downloads. |
| 124 */ | 121 */ |
| 125 public interface Observer { | 122 public interface Observer { |
| 126 /** | 123 /** |
| (...skipping 604 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 731 SharedPreferences SharedPrefs = ContextUtils.getAppSharedPreferences(); | 728 SharedPreferences SharedPrefs = ContextUtils.getAppSharedPreferences(); |
| 732 SharedPreferences.Editor editor = SharedPrefs.edit(); | 729 SharedPreferences.Editor editor = SharedPrefs.edit(); |
| 733 editor.remove(KEY_AUTO_RESUMPTION_ATTEMPT_LEFT); | 730 editor.remove(KEY_AUTO_RESUMPTION_ATTEMPT_LEFT); |
| 734 editor.apply(); | 731 editor.apply(); |
| 735 } | 732 } |
| 736 | 733 |
| 737 /** | 734 /** |
| 738 * Adds or updates an in-progress download notification. | 735 * Adds or updates an in-progress download notification. |
| 739 * @param id The {@link ContentId} of the download. | 736 * @param id The {@link ContentId} of the download. |
| 740 * @param fileName File name of the download. | 737 * @param fileName File name of the download. |
| 741 * @param percentage Percentage completed. Value should be betw
een 0 to 100 if the | 738 * @param progress The current download progress. |
| 742 * percentage can be determined, or -1 if it
is unknown. | |
| 743 * @param bytesReceived Total number of bytes received. | 739 * @param bytesReceived Total number of bytes received. |
| 744 * @param timeRemainingInMillis Remaining download time in milliseconds. | 740 * @param timeRemainingInMillis Remaining download time in milliseconds. |
| 745 * @param startTime Time when download started. | 741 * @param startTime Time when download started. |
| 746 * @param isOffTheRecord Whether the download is off the record. | 742 * @param isOffTheRecord Whether the download is off the record. |
| 747 * @param canDownloadWhileMetered Whether the download can happen in metered
network. | 743 * @param canDownloadWhileMetered Whether the download can happen in metered
network. |
| 748 * @param isTransient Whether or not clicking on the download sh
ould launch | 744 * @param isTransient Whether or not clicking on the download sh
ould launch |
| 749 * downloads home. | 745 * downloads home. |
| 750 * @param icon A {@link Bitmap} to be used as the large i
con for display. | 746 * @param icon A {@link Bitmap} to be used as the large i
con for display. |
| 751 */ | 747 */ |
| 752 @VisibleForTesting | 748 @VisibleForTesting |
| 753 public void notifyDownloadProgress(ContentId id, String fileName, int percen
tage, | 749 public void notifyDownloadProgress(ContentId id, String fileName, Progress p
rogress, |
| 754 long bytesReceived, long timeRemainingInMillis, long startTime, bool
ean isOffTheRecord, | 750 long bytesReceived, long timeRemainingInMillis, long startTime, bool
ean isOffTheRecord, |
| 755 boolean canDownloadWhileMetered, boolean isTransient, Bitmap icon) { | 751 boolean canDownloadWhileMetered, boolean isTransient, Bitmap icon) { |
| 756 updateActiveDownloadNotification(id, fileName, percentage, bytesReceived
, | 752 updateActiveDownloadNotification(id, fileName, progress, bytesReceived, |
| 757 timeRemainingInMillis, startTime, isOffTheRecord, canDownloadWhi
leMetered, false, | 753 timeRemainingInMillis, startTime, isOffTheRecord, canDownloadWhi
leMetered, false, |
| 758 isTransient, icon); | 754 isTransient, icon); |
| 759 } | 755 } |
| 760 | 756 |
| 761 /** | 757 /** |
| 762 * Adds or updates a pending download notification. | 758 * Adds or updates a pending download notification. |
| 763 * @param id The {@link ContentId} of the download. | 759 * @param id The {@link ContentId} of the download. |
| 764 * @param fileName File name of the download. | 760 * @param fileName File name of the download. |
| 765 * @param isOffTheRecord Whether the download is off the record. | 761 * @param isOffTheRecord Whether the download is off the record. |
| 766 * @param canDownloadWhileMetered Whether the download can happen in metered
network. | 762 * @param canDownloadWhileMetered Whether the download can happen in metered
network. |
| 767 * @param isTransient Whether or not clicking on the download sh
ould launch | 763 * @param isTransient Whether or not clicking on the download sh
ould launch |
| 768 * downloads home. | 764 * downloads home. |
| 769 * @param icon A {@link Bitmap} to be used as the large i
con for display. | 765 * @param icon A {@link Bitmap} to be used as the large i
con for display. |
| 770 */ | 766 */ |
| 771 private void notifyDownloadPending(ContentId id, String fileName, boolean is
OffTheRecord, | 767 private void notifyDownloadPending(ContentId id, String fileName, boolean is
OffTheRecord, |
| 772 boolean canDownloadWhileMetered, boolean isTransient, Bitmap icon) { | 768 boolean canDownloadWhileMetered, boolean isTransient, Bitmap icon) { |
| 773 updateActiveDownloadNotification(id, fileName, | 769 updateActiveDownloadNotification(id, fileName, Progress.createIndetermin
ateProgress(), 0, 0, |
| 774 DownloadItem.INDETERMINATE_DOWNLOAD_PERCENTAGE, 0, 0, 0, isOffTh
eRecord, | 770 0, isOffTheRecord, canDownloadWhileMetered, true, isTransient, i
con); |
| 775 canDownloadWhileMetered, true, isTransient, icon); | |
| 776 } | 771 } |
| 777 | 772 |
| 778 /** | 773 /** |
| 779 * Helper method to update the notification for an active download, the down
load is either in | 774 * Helper method to update the notification for an active download, the down
load is either in |
| 780 * progress or pending. | 775 * progress or pending. |
| 781 * @param id The {@link ContentId} of the download. | 776 * @param id The {@link ContentId} of the download. |
| 782 * @param fileName File name of the download. | 777 * @param fileName File name of the download. |
| 783 * @param percentage Percentage completed. Value should be betw
een 0 to 100 if the | 778 * @param progress The current download progress. |
| 784 * percentage can be determined, or -1 if it
is unknown. | |
| 785 * @param bytesReceived Total number of bytes received. | 779 * @param bytesReceived Total number of bytes received. |
| 786 * @param timeRemainingInMillis Remaining download time in milliseconds or
-1 if it is | 780 * @param timeRemainingInMillis Remaining download time in milliseconds or
-1 if it is |
| 787 * unknown. | 781 * unknown. |
| 788 * @param startTime Time when download started. | 782 * @param startTime Time when download started. |
| 789 * @param isOffTheRecord Whether the download is off the record. | 783 * @param isOffTheRecord Whether the download is off the record. |
| 790 * @param canDownloadWhileMetered Whether the download can happen in metered
network. | 784 * @param canDownloadWhileMetered Whether the download can happen in metered
network. |
| 791 * @param isDownloadPending Whether the download is pending. | 785 * @param isDownloadPending Whether the download is pending. |
| 792 * @param isTransient Whether or not clicking on the download sh
ould launch | 786 * @param isTransient Whether or not clicking on the download sh
ould launch |
| 793 * downloads home. | 787 * downloads home. |
| 794 * @param icon A {@link Bitmap} to be used as the large i
con for display. | 788 * @param icon A {@link Bitmap} to be used as the large i
con for display. |
| 795 */ | 789 */ |
| 796 private void updateActiveDownloadNotification(ContentId id, String fileName,
int percentage, | 790 private void updateActiveDownloadNotification(ContentId id, String fileName,
Progress progress, |
| 797 long bytesReceived, long timeRemainingInMillis, long startTime, bool
ean isOffTheRecord, | 791 long bytesReceived, long timeRemainingInMillis, long startTime, bool
ean isOffTheRecord, |
| 798 boolean canDownloadWhileMetered, boolean isDownloadPending, boolean
isTransient, | 792 boolean canDownloadWhileMetered, boolean isDownloadPending, boolean
isTransient, |
| 799 Bitmap icon) { | 793 Bitmap icon) { |
| 800 boolean indeterminate = | 794 boolean indeterminate = (progress.isIndeterminate() || isDownloadPending
); |
| 801 (percentage == DownloadItem.INDETERMINATE_DOWNLOAD_PERCENTAGE) |
| isDownloadPending; | |
| 802 String contentText = null; | 795 String contentText = null; |
| 803 if (isDownloadPending) { | 796 if (isDownloadPending) { |
| 804 contentText = mContext.getResources().getString(R.string.download_no
tification_pending); | 797 contentText = mContext.getResources().getString(R.string.download_no
tification_pending); |
| 805 } else if (indeterminate) { | 798 } else if (indeterminate || timeRemainingInMillis < 0) { |
| 806 // TODO(dimich): Enable the byte count back in M59. See bug 704049 f
or more info and | 799 // TODO(dimich): Enable the byte count back in M59. See bug 704049 f
or more info and |
| 807 // details of what was temporarily reverted (for M58). | 800 // details of what was temporarily reverted (for M58). |
| 808 contentText = mContext.getResources().getString(R.string.download_st
arted); | 801 contentText = mContext.getResources().getString(R.string.download_st
arted); |
| 809 } else { | 802 } else { |
| 810 contentText = timeRemainingInMillis < 0 | 803 contentText = DownloadUtils.getTimeOrFilesLeftString( |
| 811 ? mContext.getResources().getString(R.string.download_starte
d) | 804 mContext, progress, timeRemainingInMillis); |
| 812 : formatRemainingTime(mContext, timeRemainingInMillis); | |
| 813 } | 805 } |
| 814 int resId = isDownloadPending ? R.drawable.ic_download_pending | 806 int resId = isDownloadPending ? R.drawable.ic_download_pending |
| 815 : android.R.drawable.stat_sys_download; | 807 : android.R.drawable.stat_sys_download; |
| 816 ChromeNotificationBuilder builder = buildNotification(resId, fileName, c
ontentText); | 808 ChromeNotificationBuilder builder = buildNotification(resId, fileName, c
ontentText); |
| 817 builder.setOngoing(true); | 809 builder.setOngoing(true); |
| 818 builder.setPriority(Notification.PRIORITY_HIGH); | 810 builder.setPriority(Notification.PRIORITY_HIGH); |
| 819 | 811 |
| 820 // Avoid animations while the download isn't progressing. | 812 // Avoid animations while the download isn't progressing. |
| 821 if (!isDownloadPending) { | 813 if (!isDownloadPending) { |
| 822 builder.setProgress(100, percentage, indeterminate); | 814 builder.setProgress(100, indeterminate ? -1 : progress.getPercentage
(), indeterminate); |
| 823 } | 815 } |
| 824 | 816 |
| 825 if (!indeterminate && !LegacyHelpers.isLegacyOfflinePage(id)) { | 817 if (!indeterminate && !LegacyHelpers.isLegacyOfflinePage(id)) { |
| 826 String percentText = DownloadUtils.getPercentageString(percentage); | 818 String percentText = DownloadUtils.getPercentageString(progress.getP
ercentage()); |
| 827 if (Build.VERSION.CODENAME.equals("N") | 819 if (Build.VERSION.CODENAME.equals("N") |
| 828 || Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { | 820 || Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { |
| 829 builder.setSubText(percentText); | 821 builder.setSubText(percentText); |
| 830 } else { | 822 } else { |
| 831 builder.setContentInfo(percentText); | 823 builder.setContentInfo(percentText); |
| 832 } | 824 } |
| 833 } | 825 } |
| 834 int notificationId = getNotificationId(id); | 826 int notificationId = getNotificationId(id); |
| 835 if (startTime > 0) builder.setWhen(startTime); | 827 if (startTime > 0) builder.setWhen(startTime); |
| 836 | 828 |
| (...skipping 579 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1416 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry
(id); | 1408 mDownloadSharedPreferenceHelper.getDownloadSharedPreferenceEntry
(id); |
| 1417 if (entry != null) return entry.notificationId; | 1409 if (entry != null) return entry.notificationId; |
| 1418 int notificationId = mNextNotificationId; | 1410 int notificationId = mNextNotificationId; |
| 1419 mNextNotificationId = mNextNotificationId == Integer.MAX_VALUE | 1411 mNextNotificationId = mNextNotificationId == Integer.MAX_VALUE |
| 1420 ? STARTING_NOTIFICATION_ID : mNextNotificationId + 1; | 1412 ? STARTING_NOTIFICATION_ID : mNextNotificationId + 1; |
| 1421 SharedPreferences.Editor editor = mSharedPrefs.edit(); | 1413 SharedPreferences.Editor editor = mSharedPrefs.edit(); |
| 1422 editor.putInt(KEY_NEXT_DOWNLOAD_NOTIFICATION_ID, mNextNotificationId); | 1414 editor.putInt(KEY_NEXT_DOWNLOAD_NOTIFICATION_ID, mNextNotificationId); |
| 1423 editor.apply(); | 1415 editor.apply(); |
| 1424 return notificationId; | 1416 return notificationId; |
| 1425 } | 1417 } |
| 1426 | |
| 1427 /** | |
| 1428 * Format remaining time for the given millis, in the following format: | |
| 1429 * 5 hours; will include 1 unit, can go down to seconds precision. | |
| 1430 * This is similar to what android.java.text.Formatter.formatShortElapsedTim
e() does. Don't use | |
| 1431 * ui::TimeFormat::Simple() as it is very expensive. | |
| 1432 * | |
| 1433 * @param context the application context. | |
| 1434 * @param millis the remaining time in milli seconds. | |
| 1435 * @return the formatted remaining time. | |
| 1436 */ | |
| 1437 public static String formatRemainingTime(Context context, long millis) { | |
| 1438 long secondsLong = millis / 1000; | |
| 1439 | |
| 1440 int days = 0; | |
| 1441 int hours = 0; | |
| 1442 int minutes = 0; | |
| 1443 if (secondsLong >= SECONDS_PER_DAY) { | |
| 1444 days = (int) (secondsLong / SECONDS_PER_DAY); | |
| 1445 secondsLong -= days * SECONDS_PER_DAY; | |
| 1446 } | |
| 1447 if (secondsLong >= SECONDS_PER_HOUR) { | |
| 1448 hours = (int) (secondsLong / SECONDS_PER_HOUR); | |
| 1449 secondsLong -= hours * SECONDS_PER_HOUR; | |
| 1450 } | |
| 1451 if (secondsLong >= SECONDS_PER_MINUTE) { | |
| 1452 minutes = (int) (secondsLong / SECONDS_PER_MINUTE); | |
| 1453 secondsLong -= minutes * SECONDS_PER_MINUTE; | |
| 1454 } | |
| 1455 int seconds = (int) secondsLong; | |
| 1456 | |
| 1457 if (days >= 2) { | |
| 1458 days += (hours + 12) / 24; | |
| 1459 return context.getString(R.string.remaining_duration_days, days); | |
| 1460 } else if (days > 0) { | |
| 1461 return context.getString(R.string.remaining_duration_one_day); | |
| 1462 } else if (hours >= 2) { | |
| 1463 hours += (minutes + 30) / 60; | |
| 1464 return context.getString(R.string.remaining_duration_hours, hours); | |
| 1465 } else if (hours > 0) { | |
| 1466 return context.getString(R.string.remaining_duration_one_hour); | |
| 1467 } else if (minutes >= 2) { | |
| 1468 minutes += (seconds + 30) / 60; | |
| 1469 return context.getString(R.string.remaining_duration_minutes, minute
s); | |
| 1470 } else if (minutes > 0) { | |
| 1471 return context.getString(R.string.remaining_duration_one_minute); | |
| 1472 } else if (seconds == 1) { | |
| 1473 return context.getString(R.string.remaining_duration_one_second); | |
| 1474 } else { | |
| 1475 return context.getString(R.string.remaining_duration_seconds, second
s); | |
| 1476 } | |
| 1477 } | |
| 1478 } | 1418 } |
| OLD | NEW |