1a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey/* 2a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Copyright (C) 2012 The Android Open Source Project 3a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * 4a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License"); 5a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * you may not use this file except in compliance with the License. 6a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * You may obtain a copy of the License at 7a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * 8a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * http://www.apache.org/licenses/LICENSE-2.0 9a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * 10a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Unless required by applicable law or agreed to in writing, software 11a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS, 12a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * See the License for the specific language governing permissions and 14a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * limitations under the License. 15a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey */ 16a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 17a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeypackage com.android.providers.downloads; 18a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 19a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport static android.app.DownloadManager.Request.VISIBILITY_VISIBLE; 20a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED; 21a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION; 221ad10ce731d1b54692d7d5ee32601e965f503fa4Jeff Sharkeyimport static android.provider.Downloads.Impl.STATUS_RUNNING; 23703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkeyimport static com.android.providers.downloads.Constants.TAG; 24a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 25a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.app.DownloadManager; 26a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.app.Notification; 27a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.app.NotificationManager; 28a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.app.PendingIntent; 29a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.content.ContentUris; 30a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.content.Context; 31a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.content.Intent; 32a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.content.res.Resources; 33a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.net.Uri; 34703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkeyimport android.os.SystemClock; 35a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.provider.Downloads; 36a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport android.text.TextUtils; 3752b703c5d0c4cff72bafdec0e2229368d3cc20d0Jeff Sharkeyimport android.text.format.DateUtils; 38703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkeyimport android.util.Log; 3938648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkeyimport android.util.LongSparseLongArray; 40a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 41a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport com.google.common.collect.ArrayListMultimap; 42a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport com.google.common.collect.Maps; 43a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport com.google.common.collect.Multimap; 44a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 45250a1ebc4cf52edf4e55a594f05f4a351ee8e126Elliott Hughesimport java.text.NumberFormat; 46a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport java.util.Collection; 47a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport java.util.HashMap; 48a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport java.util.Iterator; 49a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 50a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeyimport javax.annotation.concurrent.GuardedBy; 51a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 52a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey/** 53a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Update {@link NotificationManager} to reflect current {@link DownloadInfo} 54a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * states. Collapses similar downloads into a single notification, and builds 55a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * {@link PendingIntent} that launch towards {@link DownloadReceiver}. 56a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey */ 57a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkeypublic class DownloadNotifier { 58a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 59a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static final int TYPE_ACTIVE = 1; 60a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static final int TYPE_WAITING = 2; 61a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static final int TYPE_COMPLETE = 3; 62a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 63a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private final Context mContext; 64a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private final NotificationManager mNotifManager; 65a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 66a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey /** 67a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Currently active notifications, mapped from clustering tag to timestamp 68a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * when first shown. 69a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * 70a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * @see #buildNotificationTag(DownloadInfo) 71a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey */ 72a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey @GuardedBy("mActiveNotifs") 73a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private final HashMap<String, Long> mActiveNotifs = Maps.newHashMap(); 74a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 7538648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey /** 7638648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey * Current speed of active downloads, mapped from {@link DownloadInfo#mId} 7738648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey * to speed in bytes per second. 7838648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey */ 7938648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey @GuardedBy("mDownloadSpeed") 8038648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey private final LongSparseLongArray mDownloadSpeed = new LongSparseLongArray(); 8138648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey 82703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey /** 83703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey * Last time speed was reproted, mapped from {@link DownloadInfo#mId} to 84703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey * {@link SystemClock#elapsedRealtime()}. 85703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey */ 86703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey @GuardedBy("mDownloadSpeed") 87703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey private final LongSparseLongArray mDownloadTouch = new LongSparseLongArray(); 88703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey 89a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey public DownloadNotifier(Context context) { 90a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey mContext = context; 91a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey mNotifManager = (NotificationManager) context.getSystemService( 92a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey Context.NOTIFICATION_SERVICE); 93a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 94a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 95b0bb182a1b52b31ccf1ec5e0be82308ebb4857e2Jeff Sharkey public void cancelAll() { 96b0bb182a1b52b31ccf1ec5e0be82308ebb4857e2Jeff Sharkey mNotifManager.cancelAll(); 97b0bb182a1b52b31ccf1ec5e0be82308ebb4857e2Jeff Sharkey } 98b0bb182a1b52b31ccf1ec5e0be82308ebb4857e2Jeff Sharkey 99a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey /** 100703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey * Notify the current speed of an active download, used for calculating 10138648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey * estimated remaining time. 10238648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey */ 10338648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey public void notifyDownloadSpeed(long id, long bytesPerSecond) { 10438648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey synchronized (mDownloadSpeed) { 10538648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey if (bytesPerSecond != 0) { 10638648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey mDownloadSpeed.put(id, bytesPerSecond); 107703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey mDownloadTouch.put(id, SystemClock.elapsedRealtime()); 10838648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey } else { 10938648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey mDownloadSpeed.delete(id); 110703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey mDownloadTouch.delete(id); 11138648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey } 11238648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey } 11338648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey } 11438648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey 11538648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey /** 116a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Update {@link NotificationManager} to reflect the given set of 117a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * {@link DownloadInfo}, adding, collapsing, and removing as needed. 118a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey */ 119a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey public void updateWith(Collection<DownloadInfo> downloads) { 120a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey synchronized (mActiveNotifs) { 121a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey updateWithLocked(downloads); 122a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 123a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 124a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 125a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private void updateWithLocked(Collection<DownloadInfo> downloads) { 126a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Resources res = mContext.getResources(); 127a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 128a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Cluster downloads together 129a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Multimap<String, DownloadInfo> clustered = ArrayListMultimap.create(); 130a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey for (DownloadInfo info : downloads) { 131a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final String tag = buildNotificationTag(info); 132a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (tag != null) { 133a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey clustered.put(tag, info); 134a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 135a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 136a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 137a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Build notification for each cluster 138a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey for (String tag : clustered.keySet()) { 139a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final int type = getNotificationTagType(tag); 140a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Collection<DownloadInfo> cluster = clustered.get(tag); 141a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 142a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Notification.Builder builder = new Notification.Builder(mContext); 14352d35ed20edec2b5086459f1d5d64309c421f3b2Selim Cinek builder.setColor(res.getColor( 14452d35ed20edec2b5086459f1d5d64309c421f3b2Selim Cinek com.android.internal.R.color.system_notification_accent_color)); 145a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 146a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Use time when cluster was first shown to avoid shuffling 147a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final long firstShown; 148a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (mActiveNotifs.containsKey(tag)) { 149a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey firstShown = mActiveNotifs.get(tag); 150a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 151a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey firstShown = System.currentTimeMillis(); 152a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey mActiveNotifs.put(tag, firstShown); 153a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 154a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setWhen(firstShown); 155a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 156a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Show relevant icon 157a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (type == TYPE_ACTIVE) { 158a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setSmallIcon(android.R.drawable.stat_sys_download); 159a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (type == TYPE_WAITING) { 160a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setSmallIcon(android.R.drawable.stat_sys_warning); 161a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (type == TYPE_COMPLETE) { 162a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setSmallIcon(android.R.drawable.stat_sys_download_done); 163a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 164a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 165a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Build action intents 166a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (type == TYPE_ACTIVE || type == TYPE_WAITING) { 167af49093b1554fdc55977e9281505e05865f33813Danny Baumann // build a synthetic uri for intent identification purposes 168af49093b1554fdc55977e9281505e05865f33813Danny Baumann final Uri uri = new Uri.Builder().scheme("active-dl").appendPath(tag).build(); 169a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Intent intent = new Intent(Constants.ACTION_LIST, 170af49093b1554fdc55977e9281505e05865f33813Danny Baumann uri, mContext, DownloadReceiver.class); 171a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, 172a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey getDownloadIds(cluster)); 173af49093b1554fdc55977e9281505e05865f33813Danny Baumann builder.setContentIntent(PendingIntent.getBroadcast(mContext, 174af49093b1554fdc55977e9281505e05865f33813Danny Baumann 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); 175a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setOngoing(true); 176a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 177a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (type == TYPE_COMPLETE) { 178a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final DownloadInfo info = cluster.iterator().next(); 179a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Uri uri = ContentUris.withAppendedId( 180a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, info.mId); 18164a9e6cb3628db724e04492cf207757348960eb7Jeff Sharkey builder.setAutoCancel(true); 182a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 183a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final String action; 184a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (Downloads.Impl.isStatusError(info.mStatus)) { 185a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey action = Constants.ACTION_LIST; 186a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 187a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (info.mDestination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { 188a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey action = Constants.ACTION_OPEN; 189a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 190a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey action = Constants.ACTION_LIST; 191a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 192a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 193a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 194a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Intent intent = new Intent(action, uri, mContext, DownloadReceiver.class); 195a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, 196a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey getDownloadIds(cluster)); 197af49093b1554fdc55977e9281505e05865f33813Danny Baumann builder.setContentIntent(PendingIntent.getBroadcast(mContext, 198af49093b1554fdc55977e9281505e05865f33813Danny Baumann 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); 199a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 200a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Intent hideIntent = new Intent(Constants.ACTION_HIDE, 201a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey uri, mContext, DownloadReceiver.class); 202a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent, 0)); 203a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 204a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 205a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Calculate and show progress 206a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey String remainingText = null; 207a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey String percentText = null; 208a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (type == TYPE_ACTIVE) { 209a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey long current = 0; 210a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey long total = 0; 211fec5f50a85e1bfc7bb4fa12d04ffa7526c79fad7Jeff Sharkey long speed = 0; 21238648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey synchronized (mDownloadSpeed) { 21338648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey for (DownloadInfo info : cluster) { 21438648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey if (info.mTotalBytes != -1) { 21538648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey current += info.mCurrentBytes; 21638648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey total += info.mTotalBytes; 21738648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey speed += mDownloadSpeed.get(info.mId); 21838648831a92295e9a11831e19e5a9dab4cbd939eJeff Sharkey } 219a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 220a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 221a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 222a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (total > 0) { 223250a1ebc4cf52edf4e55a594f05f4a351ee8e126Elliott Hughes percentText = 224250a1ebc4cf52edf4e55a594f05f4a351ee8e126Elliott Hughes NumberFormat.getPercentInstance().format((double) current / total); 225fec5f50a85e1bfc7bb4fa12d04ffa7526c79fad7Jeff Sharkey 226fec5f50a85e1bfc7bb4fa12d04ffa7526c79fad7Jeff Sharkey if (speed > 0) { 227fec5f50a85e1bfc7bb4fa12d04ffa7526c79fad7Jeff Sharkey final long remainingMillis = ((total - current) * 1000) / speed; 22852b703c5d0c4cff72bafdec0e2229368d3cc20d0Jeff Sharkey remainingText = res.getString(R.string.download_remaining, 22952b703c5d0c4cff72bafdec0e2229368d3cc20d0Jeff Sharkey DateUtils.formatDuration(remainingMillis)); 23052b703c5d0c4cff72bafdec0e2229368d3cc20d0Jeff Sharkey } 231a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 232250a1ebc4cf52edf4e55a594f05f4a351ee8e126Elliott Hughes final int percent = (int) ((current * 100) / total); 233a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setProgress(100, percent, false); 234a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 235a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setProgress(100, 0, true); 236a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 237a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 238a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 239a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Build titles and description 240a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Notification notif; 241a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (cluster.size() == 1) { 242a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final DownloadInfo info = cluster.iterator().next(); 243a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 244a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentTitle(getDownloadTitle(res, info)); 245a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 246a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (type == TYPE_ACTIVE) { 247a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (!TextUtils.isEmpty(info.mDescription)) { 248a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText(info.mDescription); 249a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 250a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText(remainingText); 251a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 252a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentInfo(percentText); 253a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 254a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (type == TYPE_WAITING) { 255a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText( 256a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey res.getString(R.string.notification_need_wifi_for_size)); 257a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 258a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (type == TYPE_COMPLETE) { 259a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (Downloads.Impl.isStatusError(info.mStatus)) { 260a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText(res.getText(R.string.notification_download_failed)); 261a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (Downloads.Impl.isStatusSuccess(info.mStatus)) { 262a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText( 263a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey res.getText(R.string.notification_download_complete)); 264a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 265a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 266a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 267a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey notif = builder.build(); 268a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 269a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 270a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Notification.InboxStyle inboxStyle = new Notification.InboxStyle(builder); 271a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 272a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey for (DownloadInfo info : cluster) { 273a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey inboxStyle.addLine(getDownloadTitle(res, info)); 274a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 275a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 276a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (type == TYPE_ACTIVE) { 277a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentTitle(res.getQuantityString( 278a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey R.plurals.notif_summary_active, cluster.size(), cluster.size())); 279a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText(remainingText); 280a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentInfo(percentText); 281a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey inboxStyle.setSummaryText(remainingText); 282a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 283a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (type == TYPE_WAITING) { 284a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentTitle(res.getQuantityString( 285a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey R.plurals.notif_summary_waiting, cluster.size(), cluster.size())); 286a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey builder.setContentText( 287a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey res.getString(R.string.notification_need_wifi_for_size)); 288a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey inboxStyle.setSummaryText( 289a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey res.getString(R.string.notification_need_wifi_for_size)); 290a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 291a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 292a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey notif = inboxStyle.build(); 293a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 294a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 295a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey mNotifManager.notify(tag, 0, notif); 296a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 297a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 298a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Remove stale tags that weren't renewed 299a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final Iterator<String> it = mActiveNotifs.keySet().iterator(); 300a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey while (it.hasNext()) { 301a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final String tag = it.next(); 302a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (!clustered.containsKey(tag)) { 303a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey mNotifManager.cancel(tag, 0); 304a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey it.remove(); 305a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 306a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 307a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 308a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 309a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static CharSequence getDownloadTitle(Resources res, DownloadInfo info) { 310a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (!TextUtils.isEmpty(info.mTitle)) { 311a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return info.mTitle; 312a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 313a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return res.getString(R.string.download_unknown_title); 314a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 315a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 316a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 317a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private long[] getDownloadIds(Collection<DownloadInfo> infos) { 318a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey final long[] ids = new long[infos.size()]; 319a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey int i = 0; 320a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey for (DownloadInfo info : infos) { 321a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey ids[i++] = info.mId; 322a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 323a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return ids; 324a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 325a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 326703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey public void dumpSpeeds() { 327703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey synchronized (mDownloadSpeed) { 328703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey for (int i = 0; i < mDownloadSpeed.size(); i++) { 329703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey final long id = mDownloadSpeed.keyAt(i); 330703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey final long delta = SystemClock.elapsedRealtime() - mDownloadTouch.get(id); 331703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey Log.d(TAG, "Download " + id + " speed " + mDownloadSpeed.valueAt(i) + "bps, " 332703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey + delta + "ms ago"); 333703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey } 334703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey } 335703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey } 336703bc3a83056a878a83e263b992fb5331b84535fJeff Sharkey 337a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey /** 338a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Build tag used for collapsing several {@link DownloadInfo} into a single 339a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * {@link Notification}. 340a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey */ 341a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static String buildNotificationTag(DownloadInfo info) { 342a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey if (info.mStatus == Downloads.Impl.STATUS_QUEUED_FOR_WIFI) { 343a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return TYPE_WAITING + ":" + info.mPackage; 344a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (isActiveAndVisible(info)) { 345a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return TYPE_ACTIVE + ":" + info.mPackage; 346a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else if (isCompleteAndVisible(info)) { 347a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey // Complete downloads always have unique notifs 348a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return TYPE_COMPLETE + ":" + info.mId; 349a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } else { 350a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return null; 351a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 352a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 353a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 354a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey /** 355a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * Return the cluster type of the given tag, as created by 356a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey * {@link #buildNotificationTag(DownloadInfo)}. 357a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey */ 358a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static int getNotificationTagType(String tag) { 359a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return Integer.parseInt(tag.substring(0, tag.indexOf(':'))); 360a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 361a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 362a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static boolean isActiveAndVisible(DownloadInfo download) { 3631ad10ce731d1b54692d7d5ee32601e965f503fa4Jeff Sharkey return download.mStatus == STATUS_RUNNING && 364a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey (download.mVisibility == VISIBILITY_VISIBLE 365a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey || download.mVisibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 366a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 367a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey 368a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey private static boolean isCompleteAndVisible(DownloadInfo download) { 369a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey return Downloads.Impl.isStatusCompleted(download.mStatus) && 370a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey (download.mVisibility == VISIBILITY_VISIBLE_NOTIFY_COMPLETED 371a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey || download.mVisibility == VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION); 372a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey } 373a40a349c0107660bfb4004467550725a3ca3ec97Jeff Sharkey} 374