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
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.download;
6 
7 import android.annotation.SuppressLint;
8 import android.app.DownloadManager;
9 import android.content.ActivityNotFoundException;
10 import android.content.Context;
11 import android.content.Intent;
12 import android.content.SharedPreferences;
13 import android.database.Cursor;
14 import android.net.ConnectivityManager;
15 import android.net.NetworkInfo;
16 import android.net.Uri;
17 import android.os.Handler;
18 import android.provider.MediaStore.MediaColumns;
19 import android.text.TextUtils;
20 import android.util.Pair;
21 
22 import androidx.annotation.Nullable;
23 import androidx.annotation.VisibleForTesting;
24 
25 import org.chromium.base.Callback;
26 import org.chromium.base.ContentUriUtils;
27 import org.chromium.base.ContextUtils;
28 import org.chromium.base.Log;
29 import org.chromium.base.ObserverList;
30 import org.chromium.base.ThreadUtils;
31 import org.chromium.base.annotations.CalledByNative;
32 import org.chromium.base.annotations.NativeMethods;
33 import org.chromium.base.metrics.RecordHistogram;
34 import org.chromium.base.task.AsyncTask;
35 import org.chromium.chrome.R;
36 import org.chromium.chrome.browser.download.DownloadManagerBridge.DownloadEnqueueRequest;
37 import org.chromium.chrome.browser.download.DownloadManagerBridge.DownloadEnqueueResponse;
38 import org.chromium.chrome.browser.download.DownloadNotificationUmaHelper.UmaDownloadResumption;
39 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
40 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
41 import org.chromium.chrome.browser.flags.ChromeFeatureList;
42 import org.chromium.chrome.browser.media.MediaViewerUtils;
43 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
44 import org.chromium.chrome.browser.preferences.Pref;
45 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
46 import org.chromium.chrome.browser.profiles.Profile;
47 import org.chromium.chrome.browser.profiles.ProfileKey;
48 import org.chromium.chrome.browser.profiles.ProfileManager;
49 import org.chromium.components.browser_ui.util.ConversionUtils;
50 import org.chromium.components.download.DownloadCollectionBridge;
51 import org.chromium.components.download.DownloadState;
52 import org.chromium.components.external_intents.ExternalNavigationHandler;
53 import org.chromium.components.feature_engagement.EventConstants;
54 import org.chromium.components.feature_engagement.Tracker;
55 import org.chromium.components.offline_items_collection.ContentId;
56 import org.chromium.components.offline_items_collection.FailState;
57 import org.chromium.components.offline_items_collection.LegacyHelpers;
58 import org.chromium.components.offline_items_collection.OfflineItem;
59 import org.chromium.components.offline_items_collection.OfflineItemSchedule;
60 import org.chromium.components.offline_items_collection.PendingState;
61 import org.chromium.components.prefs.PrefService;
62 import org.chromium.components.user_prefs.UserPrefs;
63 import org.chromium.content_public.browser.BrowserStartupController;
64 import org.chromium.net.ConnectionType;
65 import org.chromium.net.NetworkChangeNotifierAutoDetect;
66 import org.chromium.net.RegistrationPolicyAlwaysRegister;
67 import org.chromium.ui.widget.Toast;
68 
69 import java.io.File;
70 import java.util.ArrayList;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Set;
76 import java.util.concurrent.RejectedExecutionException;
77 
78 /**
79  * Chrome implementation of the {@link DownloadController.Observer} interface.
80  * This class is responsible for keeping track of which downloads are in progress. It generates
81  * updates for progress of downloads and handles cleaning up of interrupted progress notifications.
82  * TODO(qinmin): move BroadcastReceiver inheritance into DownloadManagerBridge, as it
83  * handles all Android DownloadManager interactions. And DownloadManagerService should not know
84  * download Id issued by Android DownloadManager.
85  */
86 public class DownloadManagerService implements DownloadController.Observer,
87                                                NetworkChangeNotifierAutoDetect.Observer,
88                                                DownloadServiceDelegate, ProfileManager.Observer {
89     private static final String TAG = "DownloadService";
90     private static final String DOWNLOAD_RETRY_COUNT_FILE_NAME = "DownloadRetryCount";
91     private static final String DOWNLOAD_MANUAL_RETRY_SUFFIX = ".Manual";
92     private static final String DOWNLOAD_TOTAL_RETRY_SUFFIX = ".Total";
93     private static final long UPDATE_DELAY_MILLIS = 1000;
94     // Wait 10 seconds to resume all downloads, so that we won't impact tab loading.
95     private static final long RESUME_DELAY_MILLIS = 10000;
96     public static final long UNKNOWN_BYTES_RECEIVED = -1;
97 
98     private static final Set<String> sFirstSeenDownloadIds = new HashSet<String>();
99 
100     private static DownloadManagerService sDownloadManagerService;
101     private static boolean sIsNetworkListenerDisabled;
102     private static boolean sIsNetworkMetered;
103 
104     private final SharedPreferencesManager mSharedPrefs;
105     private final HashMap<String, DownloadProgress> mDownloadProgressMap =
106             new HashMap<String, DownloadProgress>(4, 0.75f);
107 
108     private final DownloadNotifier mDownloadNotifier;
109     // Delay between UI updates.
110     private final long mUpdateDelayInMillis;
111 
112     private final Handler mHandler;
113 
114     // Deprecated after new download backend.
115     /** Generic interface for notifying external UI components about downloads and their states. */
116     public interface DownloadObserver extends DownloadSharedPreferenceHelper.Observer {
117         /** Called in response to {@link DownloadManagerService#getAllDownloads(boolean)}. */
onAllDownloadsRetrieved(final List<DownloadItem> list, boolean isOffTheRecord)118         void onAllDownloadsRetrieved(final List<DownloadItem> list, boolean isOffTheRecord);
119 
120         /** Called when a download is created. */
onDownloadItemCreated(DownloadItem item)121         void onDownloadItemCreated(DownloadItem item);
122 
123         /** Called when a download is updated. */
onDownloadItemUpdated(DownloadItem item)124         void onDownloadItemUpdated(DownloadItem item);
125 
126         /** Called when a download has been removed. */
onDownloadItemRemoved(String guid, boolean isOffTheRecord)127         void onDownloadItemRemoved(String guid, boolean isOffTheRecord);
128 
129         /** Only for testing */
broadcastDownloadSuccessfulForTesting(DownloadInfo downloadInfo)130         default void broadcastDownloadSuccessfulForTesting(DownloadInfo downloadInfo) {}
131     }
132 
133     @VisibleForTesting protected final List<String> mAutoResumableDownloadIds =
134             new ArrayList<String>();
135     private final ObserverList<DownloadObserver> mDownloadObservers = new ObserverList<>();
136 
137     private OMADownloadHandler mOMADownloadHandler;
138     private DownloadSnackbarController mDownloadSnackbarController;
139     private DownloadInfoBarController mInfoBarController;
140     private DownloadInfoBarController mIncognitoInfoBarController;
141     private long mNativeDownloadManagerService;
142     private NetworkChangeNotifierAutoDetect mNetworkChangeNotifier;
143     // Flag to track if we need to post a task to update download notifications.
144     private boolean mIsUIUpdateScheduled;
145     private int mAutoResumptionLimit = -1;
146     private DownloadManagerRequestInterceptor mDownloadManagerRequestInterceptor;
147 
148     // Whether any ChromeActivity is launched.
149     private boolean mActivityLaunched;
150 
151     // Disabling call to DownloadManager.addCompletedDownload() for test.
152     private boolean mDisableAddCompletedDownloadForTesting;
153 
154     /**
155      * Interface to intercept download request to Android DownloadManager. This is implemented by
156      * tests so that we don't need to actually enqueue a download into the Android DownloadManager.
157      */
158     interface DownloadManagerRequestInterceptor {
interceptDownloadRequest(DownloadItem item, boolean notifyComplete)159         void interceptDownloadRequest(DownloadItem item, boolean notifyComplete);
160     }
161 
162     // Deprecated after new download backend.
163     /**
164      * Class representing progress of a download.
165      */
166     private static class DownloadProgress {
167         final long mStartTimeInMillis;
168         boolean mCanDownloadWhileMetered;
169         DownloadItem mDownloadItem;
170         @DownloadStatus
171         int mDownloadStatus;
172         boolean mIsAutoResumable;
173         boolean mIsUpdated;
174         boolean mIsSupportedMimeType;
175 
DownloadProgress(long startTimeInMillis, boolean canDownloadWhileMetered, DownloadItem downloadItem, @DownloadStatus int downloadStatus)176         DownloadProgress(long startTimeInMillis, boolean canDownloadWhileMetered,
177                 DownloadItem downloadItem, @DownloadStatus int downloadStatus) {
178             mStartTimeInMillis = startTimeInMillis;
179             mCanDownloadWhileMetered = canDownloadWhileMetered;
180             mDownloadItem = downloadItem;
181             mDownloadStatus = downloadStatus;
182             mIsAutoResumable = false;
183             mIsUpdated = true;
184         }
185 
DownloadProgress(DownloadProgress progress)186         DownloadProgress(DownloadProgress progress) {
187             mStartTimeInMillis = progress.mStartTimeInMillis;
188             mCanDownloadWhileMetered = progress.mCanDownloadWhileMetered;
189             mDownloadItem = progress.mDownloadItem;
190             mDownloadStatus = progress.mDownloadStatus;
191             mIsAutoResumable = progress.mIsAutoResumable;
192             mIsUpdated = progress.mIsUpdated;
193             mIsSupportedMimeType = progress.mIsSupportedMimeType;
194         }
195     }
196 
197     /**
198      * Creates DownloadManagerService.
199      */
getDownloadManagerService()200     public static DownloadManagerService getDownloadManagerService() {
201         ThreadUtils.assertOnUiThread();
202         if (sDownloadManagerService == null) {
203             DownloadNotifier downloadNotifier = new SystemDownloadNotifier();
204             sDownloadManagerService = new DownloadManagerService(
205                     downloadNotifier, new Handler(), UPDATE_DELAY_MILLIS);
206         }
207         return sDownloadManagerService;
208     }
209 
hasDownloadManagerService()210     public static boolean hasDownloadManagerService() {
211         ThreadUtils.assertOnUiThread();
212         return sDownloadManagerService != null;
213     }
214 
215     /**
216      * For tests only: sets the DownloadManagerService.
217      * @param service An instance of DownloadManagerService.
218      * @return Null or a currently set instance of DownloadManagerService.
219      */
220     @VisibleForTesting
setDownloadManagerService(DownloadManagerService service)221     public static DownloadManagerService setDownloadManagerService(DownloadManagerService service) {
222         ThreadUtils.assertOnUiThread();
223         DownloadManagerService prev = sDownloadManagerService;
224         sDownloadManagerService = service;
225         return prev;
226     }
227 
228     @VisibleForTesting
setDownloadManagerRequestInterceptor(DownloadManagerRequestInterceptor interceptor)229     void setDownloadManagerRequestInterceptor(DownloadManagerRequestInterceptor interceptor) {
230         mDownloadManagerRequestInterceptor = interceptor;
231     }
232 
233     @VisibleForTesting
DownloadManagerService( DownloadNotifier downloadNotifier, Handler handler, long updateDelayInMillis)234     protected DownloadManagerService(
235             DownloadNotifier downloadNotifier, Handler handler, long updateDelayInMillis) {
236         Context applicationContext = ContextUtils.getApplicationContext();
237         mSharedPrefs = SharedPreferencesManager.getInstance();
238         mDownloadNotifier = downloadNotifier;
239         mUpdateDelayInMillis = updateDelayInMillis;
240         mHandler = handler;
241         mDownloadSnackbarController = new DownloadSnackbarController();
242         mOMADownloadHandler = new OMADownloadHandler(applicationContext);
243         DownloadCollectionBridge.setDownloadDelegate(new DownloadDelegateImpl());
244         // Note that this technically leaks the native object, however, DownloadManagerService
245         // is a singleton that lives forever and there's no clean shutdown of Chrome on Android.
246         init();
247         mOMADownloadHandler.clearPendingOMADownloads();
248     }
249 
250     @VisibleForTesting
init()251     protected void init() {
252         DownloadController.setDownloadNotificationService(this);
253         // Post a delayed task to resume all pending downloads.
254         mHandler.postDelayed(() -> mDownloadNotifier.resumePendingDownloads(), RESUME_DELAY_MILLIS);
255         // Clean up unused shared prefs. TODO(qinmin): remove this after M84.
256         mSharedPrefs.removeKey(ChromePreferenceKeys.DOWNLOAD_UMA_ENTRY);
257     }
258 
259     // TODO(https://crbug.com/1060940): Remove this function and update all use cases so that
260     // the profile would be available instead of isOffTheRecord boolean.
getProfileKey(boolean isOffTheRecord)261     private static ProfileKey getProfileKey(boolean isOffTheRecord) {
262         // If off-the-record is not requested, the request might be before native initialization.
263         if (!isOffTheRecord) return ProfileKey.getLastUsedRegularProfileKey();
264 
265         return Profile.getLastUsedRegularProfile().getPrimaryOTRProfile().getProfileKey();
266     }
267 
268     /**
269      * Initializes download related systems for background task.
270      */
initForBackgroundTask()271     public void initForBackgroundTask() {
272         getNativeDownloadManagerService();
273     }
274 
275     /**
276      * Pre-load shared prefs to avoid being blocked on the disk access async task in the future.
277      */
warmUpSharedPrefs()278     public static void warmUpSharedPrefs() {
279         getAutoRetryCountSharedPreference();
280     }
281 
getDownloadNotifier()282     public DownloadNotifier getDownloadNotifier() {
283         return mDownloadNotifier;
284     }
285 
286     /** @return The {@link DownloadInfoBarController} controller associated with the profile. */
getInfoBarController(boolean isIncognito)287     public DownloadInfoBarController getInfoBarController(boolean isIncognito) {
288         return isIncognito ? mIncognitoInfoBarController : mInfoBarController;
289     }
290 
291     /** For testing only. */
setInfoBarControllerForTesting(DownloadInfoBarController infoBarController)292     public void setInfoBarControllerForTesting(DownloadInfoBarController infoBarController) {
293         mInfoBarController = infoBarController;
294     }
295 
296     // Deprecated after new download backend.
297     @Override
onDownloadCompleted(final DownloadInfo downloadInfo)298     public void onDownloadCompleted(final DownloadInfo downloadInfo) {
299         @DownloadStatus
300         int status = DownloadStatus.COMPLETE;
301         String mimeType = downloadInfo.getMimeType();
302         if (downloadInfo.getBytesReceived() == 0) {
303             status = DownloadStatus.FAILED;
304         } else {
305             mimeType = MimeUtils.remapGenericMimeType(
306                     mimeType, downloadInfo.getOriginalUrl(), downloadInfo.getFileName());
307         }
308         DownloadInfo newInfo =
309                 DownloadInfo.Builder.fromDownloadInfo(downloadInfo).setMimeType(mimeType).build();
310         DownloadItem downloadItem = new DownloadItem(false, newInfo);
311         downloadItem.setSystemDownloadId(
312                 DownloadManagerBridge.getDownloadIdForDownloadGuid(downloadInfo.getDownloadGuid()));
313         updateDownloadProgress(downloadItem, status);
314         updateDownloadInfoBar(downloadItem);
315     }
316 
317     // Deprecated after new download backend.
318     @Override
onDownloadUpdated(final DownloadInfo downloadInfo)319     public void onDownloadUpdated(final DownloadInfo downloadInfo) {
320         DownloadItem item = new DownloadItem(false, downloadInfo);
321         // If user manually paused a download, this download is no longer auto resumable.
322         if (downloadInfo.isPaused()) {
323             removeAutoResumableDownload(item.getId());
324         }
325         updateDownloadProgress(item, DownloadStatus.IN_PROGRESS);
326         updateDownloadInfoBar(item);
327         scheduleUpdateIfNeeded();
328     }
329 
330     // Deprecated after new download backend.
331     @Override
onDownloadCancelled(final DownloadInfo downloadInfo)332     public void onDownloadCancelled(final DownloadInfo downloadInfo) {
333         DownloadInfo newInfo = DownloadInfo.Builder.fromDownloadInfo(downloadInfo)
334                                        .setState(DownloadState.CANCELLED)
335                                        .build();
336         DownloadItem item = new DownloadItem(false, newInfo);
337         removeAutoResumableDownload(item.getId());
338         updateDownloadProgress(new DownloadItem(false, downloadInfo), DownloadStatus.CANCELLED);
339         updateDownloadInfoBar(item);
340     }
341 
342     // Deprecated after new download backend.
343     @Override
onDownloadInterrupted(final DownloadInfo downloadInfo, boolean isAutoResumable)344     public void onDownloadInterrupted(final DownloadInfo downloadInfo, boolean isAutoResumable) {
345         @DownloadStatus
346         int status = DownloadStatus.INTERRUPTED;
347         DownloadItem item = new DownloadItem(false, downloadInfo);
348         if (!downloadInfo.isResumable()) {
349             status = DownloadStatus.FAILED;
350         } else if (isAutoResumable) {
351             addAutoResumableDownload(item.getId());
352         }
353 
354         updateDownloadProgress(item, status);
355         updateDownloadInfoBar(item);
356 
357         if (CachedFeatureFlags.isEnabled(ChromeFeatureList.DOWNLOADS_AUTO_RESUMPTION_NATIVE)) {
358             return;
359         }
360         DownloadProgress progress = mDownloadProgressMap.get(item.getId());
361         if (progress == null) return;
362         if (!isAutoResumable || sIsNetworkListenerDisabled) return;
363         ConnectivityManager cm =
364                 (ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
365                         Context.CONNECTIVITY_SERVICE);
366         NetworkInfo info = cm.getActiveNetworkInfo();
367         if (info == null || !info.isConnected()) return;
368         if (progress.mCanDownloadWhileMetered
369                 || !isActiveNetworkMetered(ContextUtils.getApplicationContext())) {
370             // Normally the download will automatically resume when network is reconnected.
371             // However, if there are multiple network connections and the interruption is caused
372             // by switching between active networks, onConnectionTypeChanged() will not get called.
373             // As a result, we should resume immediately.
374             scheduleDownloadResumption(item);
375         }
376     }
377 
378     // Deprecated after native auto-resumption.
379     /**
380      * Helper method to schedule a download for resumption.
381      * @param item DownloadItem to resume.
382      */
scheduleDownloadResumption(final DownloadItem item)383     private void scheduleDownloadResumption(final DownloadItem item) {
384         removeAutoResumableDownload(item.getId());
385         // Post a delayed task to avoid an issue that when connectivity status just changed
386         // to CONNECTED, immediately establishing a connection will sometimes fail.
387         mHandler.postDelayed(
388                 () -> resumeDownload(LegacyHelpers.buildLegacyContentId(false, item.getId()),
389                         item, false), mUpdateDelayInMillis);
390     }
391 
392     /**
393      * Called when browser activity is launched. For background resumption and cancellation, this
394      * will not be called.
395      */
onActivityLaunched()396     public void onActivityLaunched() {
397         if (!mActivityLaunched) {
398             mInfoBarController = new DownloadInfoBarController(false);
399             mIncognitoInfoBarController = new DownloadInfoBarController(true);
400 
401             DownloadNotificationService.clearResumptionAttemptLeft();
402 
403             DownloadManagerService.getDownloadManagerService().checkForExternallyRemovedDownloads(
404                     /*isOffTheRecord=*/false);
405             mActivityLaunched = true;
406         }
407     }
408 
updateDownloadInfoBar(DownloadItem item)409     private void updateDownloadInfoBar(DownloadItem item) {
410         DownloadInfoBarController infobarController =
411                 getInfoBarController(item.getDownloadInfo().isOffTheRecord());
412         if (infobarController != null) infobarController.onDownloadItemUpdated(item);
413     }
414 
415     /**
416      * Broadcast that a download was successful.
417      * @param downloadInfo info about the download.
418      */
419     // For testing only.
broadcastDownloadSuccessful(DownloadInfo downloadInfo)420     protected void broadcastDownloadSuccessful(DownloadInfo downloadInfo) {
421         for (DownloadObserver observer : mDownloadObservers) {
422             observer.broadcastDownloadSuccessfulForTesting(downloadInfo);
423         }
424     }
425 
426     /**
427      * Gets download information from SharedPreferences.
428      * @param sharedPrefs The SharedPreferencesManager to read from.
429      * @param type Type of the information to retrieve.
430      * @return download information saved to the SharedPrefs for the given type.
431      */
432     @VisibleForTesting
getStoredDownloadInfo( SharedPreferencesManager sharedPrefs, String type)433     protected static Set<String> getStoredDownloadInfo(
434             SharedPreferencesManager sharedPrefs, String type) {
435         return new HashSet<>(sharedPrefs.readStringSet(type));
436     }
437 
438     /**
439      * Stores download information to shared preferences. The information can be
440      * either pending download IDs, or pending OMA downloads.
441      *
442      * @param sharedPrefs   SharedPreferencesManager to write to.
443      * @param type          Type of the information.
444      * @param downloadInfo  Information to be saved.
445      * @param forceCommit   Whether to synchronously update shared preferences.
446      */
447     @SuppressLint({"ApplySharedPref", "CommitPrefEdits"})
storeDownloadInfo(SharedPreferencesManager sharedPrefs, String type, Set<String> downloadInfo, boolean forceCommit)448     static void storeDownloadInfo(SharedPreferencesManager sharedPrefs, String type,
449             Set<String> downloadInfo, boolean forceCommit) {
450         boolean success;
451         if (downloadInfo.isEmpty()) {
452             if (forceCommit) {
453                 success = sharedPrefs.removeKeySync(type);
454             } else {
455                 sharedPrefs.removeKey(type);
456                 success = true;
457             }
458         } else {
459             if (forceCommit) {
460                 success = sharedPrefs.writeStringSetSync(type, downloadInfo);
461             } else {
462                 sharedPrefs.writeStringSet(type, downloadInfo);
463                 success = true;
464             }
465         }
466 
467         if (!success) {
468             // Write synchronously because it might be used on restart and needs to stay
469             // up-to-date.
470             Log.e(TAG, "Failed to write DownloadInfo " + type);
471         }
472     }
473 
474     /**
475      * Updates notifications for a given list of downloads.
476      * @param progresses A list of notifications to update.
477      */
updateAllNotifications(List<DownloadProgress> progresses)478     private void updateAllNotifications(List<DownloadProgress> progresses) {
479         assert ThreadUtils.runningOnUiThread();
480         for (int i = 0; i < progresses.size(); ++i) {
481             updateNotification(progresses.get(i));
482         }
483     }
484 
485     // Deprecated after new download backend.
486     /**
487      * Update notification for a specific download.
488      * @param progress Specific notification to update.
489      */
updateNotification(DownloadProgress progress)490     private void updateNotification(DownloadProgress progress) {
491         DownloadItem item = progress.mDownloadItem;
492         DownloadInfo info = item.getDownloadInfo();
493         boolean notificationUpdateScheduled = true;
494         boolean removeFromDownloadProgressMap = true;
495         switch (progress.mDownloadStatus) {
496             case DownloadStatus.COMPLETE:
497                 notificationUpdateScheduled = updateDownloadSuccessNotification(progress);
498                 removeFromDownloadProgressMap = notificationUpdateScheduled;
499                 break;
500             case DownloadStatus.FAILED:
501                 // TODO(cmsy): Use correct FailState.
502                 mDownloadNotifier.notifyDownloadFailed(info);
503                 Log.w(TAG, "Download failed: " + info.getFilePath());
504                 break;
505             case DownloadStatus.IN_PROGRESS:
506                 if (info.isPaused()) {
507                     mDownloadNotifier.notifyDownloadPaused(info);
508                     DownloadNotificationUmaHelper.recordDownloadResumptionHistogram(
509                             UmaDownloadResumption.MANUAL_PAUSE);
510                 } else {
511                     mDownloadNotifier.notifyDownloadProgress(
512                             info, progress.mStartTimeInMillis, progress.mCanDownloadWhileMetered);
513                     removeFromDownloadProgressMap = false;
514                 }
515                 break;
516             case DownloadStatus.CANCELLED:
517                 mDownloadNotifier.notifyDownloadCanceled(item.getContentId());
518                 break;
519             case DownloadStatus.INTERRUPTED:
520                 mDownloadNotifier.notifyDownloadInterrupted(
521                         info, progress.mIsAutoResumable, PendingState.PENDING_NETWORK);
522                 removeFromDownloadProgressMap = !progress.mIsAutoResumable;
523                 break;
524             default:
525                 assert false;
526                 break;
527         }
528         if (notificationUpdateScheduled) progress.mIsUpdated = false;
529         if (removeFromDownloadProgressMap) mDownloadProgressMap.remove(item.getId());
530     }
531 
532     // Deprecated after new download backend.
533     /**
534      * Helper method to schedule a task to update the download success notification.
535      * @param progress Download progress to update.
536      * @return True if the task can be scheduled, or false otherwise.
537      */
updateDownloadSuccessNotification(DownloadProgress progress)538     private boolean updateDownloadSuccessNotification(DownloadProgress progress) {
539         final boolean isSupportedMimeType = progress.mIsSupportedMimeType;
540         final DownloadItem item = progress.mDownloadItem;
541         final DownloadInfo info = item.getDownloadInfo();
542 
543         AsyncTask<Pair<Boolean, Boolean>> task = new AsyncTask<Pair<Boolean, Boolean>>() {
544             @Override
545             public Pair<Boolean, Boolean> doInBackground() {
546                 boolean success = mDisableAddCompletedDownloadForTesting
547                         || ContentUriUtils.isContentUri(item.getDownloadInfo().getFilePath());
548                 if (!success
549                         && !ChromeFeatureList.isEnabled(
550                                 ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
551                     long systemDownloadId = DownloadManagerBridge.addCompletedDownload(
552                             info.getFileName(), info.getDescription(), info.getMimeType(),
553                             info.getFilePath(), info.getBytesReceived(), info.getOriginalUrl(),
554                             info.getReferrer(), info.getDownloadGuid());
555                     success = systemDownloadId != DownloadConstants.INVALID_DOWNLOAD_ID;
556                     if (success) item.setSystemDownloadId(systemDownloadId);
557                 }
558                 boolean canResolve = success
559                         && (MimeUtils.isOMADownloadDescription(item.getDownloadInfo().getMimeType())
560                                 || canResolveDownloadItem(item, isSupportedMimeType));
561                 return Pair.create(success, canResolve);
562             }
563 
564             @Override
565             protected void onPostExecute(Pair<Boolean, Boolean> result) {
566                 DownloadInfo info = item.getDownloadInfo();
567                 if (result.first) {
568                     mDownloadNotifier.notifyDownloadSuccessful(
569                             info, item.getSystemDownloadId(), result.second, isSupportedMimeType);
570                     broadcastDownloadSuccessful(info);
571                 } else {
572                     info = DownloadInfo.Builder.fromDownloadInfo(info)
573                                    .setFailState(FailState.CANNOT_DOWNLOAD)
574                                    .build();
575                     mDownloadNotifier.notifyDownloadFailed(info);
576                     // TODO(qinmin): get the failure message from native.
577                 }
578             }
579         };
580         try {
581             task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
582             return true;
583         } catch (RejectedExecutionException e) {
584             // Reaching thread limit, update will be reschduled for the next run.
585             Log.e(TAG, "Thread limit reached, reschedule notification update later.");
586             return false;
587         }
588     }
589 
590     @CalledByNative
handleOMADownload(DownloadItem download, long systemDownloadId)591     private void handleOMADownload(DownloadItem download, long systemDownloadId) {
592         mOMADownloadHandler.handleOMADownload(download.getDownloadInfo(), systemDownloadId);
593     }
594 
595     /**
596      * Handle auto opennable files after download completes.
597      * TODO(qinmin): move this to DownloadManagerBridge.
598      *
599      * @param download A download item.
600      */
handleAutoOpenAfterDownload(DownloadItem download)601     private void handleAutoOpenAfterDownload(DownloadItem download) {
602         if (MimeUtils.isOMADownloadDescription(download.getDownloadInfo().getMimeType())) {
603             mOMADownloadHandler.handleOMADownload(
604                     download.getDownloadInfo(), download.getSystemDownloadId());
605             return;
606         }
607         openDownloadedContent(download.getDownloadInfo(), download.getSystemDownloadId(),
608                 DownloadOpenSource.AUTO_OPEN);
609     }
610 
611     // Deprecated after new download backend.
612     /**
613      * Schedule an update if there is no update scheduled.
614      */
615     @VisibleForTesting
scheduleUpdateIfNeeded()616     protected void scheduleUpdateIfNeeded() {
617         if (mIsUIUpdateScheduled) return;
618 
619         mIsUIUpdateScheduled = true;
620         final List<DownloadProgress> progressPendingUpdate = new ArrayList<DownloadProgress>();
621         Iterator<DownloadProgress> iter = mDownloadProgressMap.values().iterator();
622         while (iter.hasNext()) {
623             DownloadProgress progress = iter.next();
624             if (progress.mIsUpdated) {
625                 progressPendingUpdate.add(progress);
626             }
627         }
628         if (progressPendingUpdate.isEmpty()) {
629             mIsUIUpdateScheduled = false;
630             return;
631         }
632         updateAllNotifications(progressPendingUpdate);
633 
634         Runnable scheduleNextUpdateTask = () -> {
635             mIsUIUpdateScheduled = false;
636             scheduleUpdateIfNeeded();
637         };
638         mHandler.postDelayed(scheduleNextUpdateTask, mUpdateDelayInMillis);
639     }
640 
641     /**
642      * Updates the progress of a download.
643      *
644      * @param downloadItem Information about the download.
645      * @param downloadStatus Status of the download.
646      */
647     // Deprecated after new download backend.
updateDownloadProgress( DownloadItem downloadItem, @DownloadStatus int downloadStatus)648     private void updateDownloadProgress(
649             DownloadItem downloadItem, @DownloadStatus int downloadStatus) {
650         boolean isSupportedMimeType = downloadStatus == DownloadStatus.COMPLETE
651                 && isSupportedMimeType(downloadItem.getDownloadInfo().getMimeType());
652         String id = downloadItem.getId();
653         DownloadProgress progress = mDownloadProgressMap.get(id);
654         long bytesReceived = downloadItem.getDownloadInfo().getBytesReceived();
655         if (progress == null) {
656             if (!downloadItem.getDownloadInfo().isPaused()) {
657                 long startTime = System.currentTimeMillis();
658                 progress = new DownloadProgress(startTime,
659                         isActiveNetworkMetered(ContextUtils.getApplicationContext()), downloadItem,
660                         downloadStatus);
661                 progress.mIsUpdated = true;
662                 progress.mIsSupportedMimeType = isSupportedMimeType;
663                 mDownloadProgressMap.put(id, progress);
664                 sFirstSeenDownloadIds.add(id);
665 
666                 // This is mostly for testing, when the download is not tracked/progress is null but
667                 // downloadStatus is not DownloadStatus.IN_PROGRESS.
668                 if (downloadStatus != DownloadStatus.IN_PROGRESS) {
669                     updateNotification(progress);
670                 }
671             }
672             return;
673         }
674 
675         progress.mDownloadStatus = downloadStatus;
676         progress.mDownloadItem = downloadItem;
677         progress.mIsUpdated = true;
678         progress.mIsAutoResumable = mAutoResumableDownloadIds.contains(id);
679         progress.mIsSupportedMimeType = isSupportedMimeType;
680         switch (downloadStatus) {
681             case DownloadStatus.COMPLETE:
682             case DownloadStatus.FAILED:
683             case DownloadStatus.CANCELLED:
684                 clearDownloadRetryCount(id, true);
685                 clearDownloadRetryCount(id, false);
686                 updateNotification(progress);
687                 sFirstSeenDownloadIds.remove(id);
688                 break;
689             case DownloadStatus.INTERRUPTED:
690                 updateNotification(progress);
691                 break;
692             case DownloadStatus.IN_PROGRESS:
693                 if (downloadItem.getDownloadInfo().isPaused()) {
694                     updateNotification(progress);
695                 }
696                 break;
697             default:
698                 assert false;
699         }
700     }
701 
702     /** See {@link DownloadManagerBridge.enqueueNewDownload}. */
enqueueNewDownload(final DownloadItem item, boolean notifyCompleted)703     public void enqueueNewDownload(final DownloadItem item, boolean notifyCompleted) {
704         if (mDownloadManagerRequestInterceptor != null) {
705             mDownloadManagerRequestInterceptor.interceptDownloadRequest(item, notifyCompleted);
706             return;
707         }
708 
709         DownloadEnqueueRequest request = new DownloadEnqueueRequest();
710         request.url = item.getDownloadInfo().getUrl();
711         request.fileName = item.getDownloadInfo().getFileName();
712         request.description = item.getDownloadInfo().getDescription();
713         request.mimeType = item.getDownloadInfo().getMimeType();
714         request.cookie = item.getDownloadInfo().getCookie();
715         request.referrer = item.getDownloadInfo().getReferrer();
716         request.userAgent = item.getDownloadInfo().getUserAgent();
717         request.notifyCompleted = notifyCompleted;
718         DownloadManagerBridge.enqueueNewDownload(
719                 request, response -> { onDownloadEnqueued(item, response); });
720     }
721 
onDownloadEnqueued(DownloadItem downloadItem, DownloadEnqueueResponse response)722     public void onDownloadEnqueued(DownloadItem downloadItem, DownloadEnqueueResponse response) {
723         downloadItem.setStartTime(response.startTime);
724         downloadItem.setSystemDownloadId(response.downloadId);
725         if (!response.result) {
726             onDownloadFailed(downloadItem, response.failureReason);
727             return;
728         }
729 
730         getInfoBarController(downloadItem.getDownloadInfo().isOffTheRecord()).onDownloadStarted();
731     }
732 
733     @Nullable
getLaunchIntentForDownload(@ullable String filePath, long downloadId, boolean isSupportedMimeType, String originalUrl, String referrer, @Nullable String mimeType)734     static Intent getLaunchIntentForDownload(@Nullable String filePath, long downloadId,
735             boolean isSupportedMimeType, String originalUrl, String referrer,
736             @Nullable String mimeType) {
737         assert !ThreadUtils.runningOnUiThread();
738         if (downloadId == DownloadConstants.INVALID_DOWNLOAD_ID) {
739             if (!ContentUriUtils.isContentUri(filePath)) return null;
740             return getLaunchIntentFromDownloadUri(
741                     filePath, isSupportedMimeType, originalUrl, referrer, mimeType);
742         }
743 
744         DownloadManagerBridge.DownloadQueryResult queryResult =
745                 DownloadManagerBridge.queryDownloadResult(downloadId);
746         if (mimeType == null) mimeType = queryResult.mimeType;
747 
748         Uri contentUri = filePath == null ? queryResult.contentUri
749                                           : DownloadUtils.getUriForOtherApps(filePath);
750         if (contentUri == null || Uri.EMPTY.equals(contentUri)) return null;
751 
752         Uri fileUri = filePath == null ? contentUri : Uri.fromFile(new File(filePath));
753         return createLaunchIntent(
754                 fileUri, contentUri, mimeType, isSupportedMimeType, originalUrl, referrer);
755     }
756 
757     /**
758      * Similar to getLaunchIntentForDownload(), but only works for download that is stored as a
759      * content Uri.
760      * @param context    Context of the app.
761      * @param contentUri Uri of the download.
762      * @param isSupportedMimeType Whether the MIME type is supported by browser.
763      * @param originalUrl The original url of the downloaded file
764      * @param referrer   Referrer of the downloaded file.
765      * @param mimeType   MIME type of the downloaded file.
766      * @return the intent to launch for the given download item.
767      */
768     @Nullable
getLaunchIntentFromDownloadUri(String contentUri, boolean isSupportedMimeType, String originalUrl, String referrer, @Nullable String mimeType)769     private static Intent getLaunchIntentFromDownloadUri(String contentUri,
770             boolean isSupportedMimeType, String originalUrl, String referrer,
771             @Nullable String mimeType) {
772         assert !ThreadUtils.runningOnUiThread();
773         assert ContentUriUtils.isContentUri(contentUri);
774 
775         Uri uri = Uri.parse(contentUri);
776         if (mimeType == null) {
777             try (Cursor cursor = ContextUtils.getApplicationContext().getContentResolver().query(
778                          uri, null, null, null, null)) {
779                 if (cursor == null || cursor.getCount() == 0) return null;
780                 cursor.moveToNext();
781                 mimeType = cursor.getString(cursor.getColumnIndex(MediaColumns.MIME_TYPE));
782                 cursor.close();
783             }
784         }
785         return createLaunchIntent(uri, uri, mimeType, isSupportedMimeType, originalUrl, referrer);
786     }
787 
788     /**
789      * Creates a an intent to launch a download.
790      * @param fileUri File uri of the download has an actual file path. Otherwise, this is the same
791      *                as |contentUri|.
792      * @param contentUri Content uri of the download.
793      * @param isSupportedMimeType Whether the MIME type is supported by browser.
794      * @param originalUrl The original url of the downloaded file
795      * @param referrer   Referrer of the downloaded file.
796      * @return the intent to launch for the given download item.
797      */
createLaunchIntent(Uri fileUri, Uri contentUri, String mimeType, boolean isSupportedMimeType, String originalUrl, String referrer)798     private static Intent createLaunchIntent(Uri fileUri, Uri contentUri, String mimeType,
799             boolean isSupportedMimeType, String originalUrl, String referrer) {
800         if (isSupportedMimeType) {
801             // Redirect the user to an internal media viewer.  The file path is necessary to show
802             // the real file path to the user instead of a content:// download ID.
803             return MediaViewerUtils.getMediaViewerIntent(
804                     fileUri, contentUri, mimeType, true /* allowExternalAppHandlers */);
805         }
806         return MediaViewerUtils.createViewIntentForUri(contentUri, mimeType, originalUrl, referrer);
807     }
808 
809     /**
810      * Return whether a download item can be resolved to any activity.
811      *
812      * @param context Context of the app.
813      * @param download A download item.
814      * @param isSupportedMimeType Whether the MIME type is supported by browser.
815      * @return true if the download item can be resolved, or false otherwise.
816      */
canResolveDownloadItem(DownloadItem download, boolean isSupportedMimeType)817     static boolean canResolveDownloadItem(DownloadItem download, boolean isSupportedMimeType) {
818         assert !ThreadUtils.runningOnUiThread();
819         Intent intent = getLaunchIntentForDownload(download.getDownloadInfo().getFilePath(),
820                 download.getSystemDownloadId(), isSupportedMimeType, null, null,
821                 download.getDownloadInfo().getMimeType());
822         return (intent == null) ? false : ExternalNavigationHandler.resolveIntent(intent, true);
823     }
824 
825     /** See {@link #openDownloadedContent(Context, String, boolean, boolean, String, long)}. */
openDownloadedContent(final DownloadInfo downloadInfo, final long downloadId, @DownloadOpenSource int source)826     protected void openDownloadedContent(final DownloadInfo downloadInfo, final long downloadId,
827             @DownloadOpenSource int source) {
828         openDownloadedContent(ContextUtils.getApplicationContext(), downloadInfo.getFilePath(),
829                 isSupportedMimeType(downloadInfo.getMimeType()), downloadInfo.isOffTheRecord(),
830                 downloadInfo.getDownloadGuid(), downloadId, downloadInfo.getOriginalUrl(),
831                 downloadInfo.getReferrer(), source, downloadInfo.getMimeType());
832     }
833 
834     /**
835      * Launch the intent for a given download item, or Download Home if that's not possible.
836      * TODO(qinmin): Move this to DownloadManagerBridge.
837      *
838      * @param context             Context to use.
839      * @param filePath            Path to the downloaded item.
840      * @param isSupportedMimeType Whether the MIME type is supported by Chrome.
841      * @param isOffTheRecord      Whether the download was for a off the record profile.
842      * @param downloadGuid        GUID of the download item in DownloadManager.
843      * @param downloadId          ID of the download item in DownloadManager.
844      * @param originalUrl         The original url of the downloaded file.
845      * @param referrer            Referrer of the downloaded file.
846      * @param source              The source that tries to open the download.
847      * @param mimeType            MIME type of the download, could be null.
848      */
openDownloadedContent(final Context context, final String filePath, final boolean isSupportedMimeType, final boolean isOffTheRecord, final String downloadGuid, final long downloadId, final String originalUrl, final String referrer, @DownloadOpenSource int source, @Nullable String mimeType)849     protected static void openDownloadedContent(final Context context, final String filePath,
850             final boolean isSupportedMimeType, final boolean isOffTheRecord,
851             final String downloadGuid, final long downloadId, final String originalUrl,
852             final String referrer, @DownloadOpenSource int source, @Nullable String mimeType) {
853         new AsyncTask<Intent>() {
854             @Override
855             public Intent doInBackground() {
856                 return getLaunchIntentForDownload(
857                         filePath, downloadId, isSupportedMimeType, originalUrl, referrer, mimeType);
858             }
859 
860             @Override
861             protected void onPostExecute(Intent intent) {
862                 boolean didLaunchIntent = intent != null
863                         && ExternalNavigationHandler.resolveIntent(intent, true)
864                         && DownloadUtils.fireOpenIntentForDownload(context, intent);
865 
866                 if (!didLaunchIntent) {
867                     openDownloadsPage(context, source);
868                     return;
869                 }
870 
871                 if (didLaunchIntent && hasDownloadManagerService()) {
872                     DownloadManagerService.getDownloadManagerService().updateLastAccessTime(
873                             downloadGuid, isOffTheRecord);
874                     DownloadManager manager =
875                             (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
876                     String mimeType = manager.getMimeTypeForDownloadedFile(downloadId);
877                     DownloadMetrics.recordDownloadOpen(source, mimeType);
878                 }
879             }
880         }
881                 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
882     }
883 
884     /**
885      * Called when a download fails.
886      *
887      * @param fileName Name of the download file.
888      * @param reason Reason of failure reported by android DownloadManager
889      */
890     @VisibleForTesting
onDownloadFailed(DownloadItem item, int reason)891     protected void onDownloadFailed(DownloadItem item, int reason) {
892         String failureMessage =
893                 getDownloadFailureMessage(item.getDownloadInfo().getFileName(), reason);
894 
895         if (mDownloadSnackbarController.getSnackbarManager() != null) {
896             mDownloadSnackbarController.onDownloadFailed(failureMessage,
897                     reason == DownloadManager.ERROR_FILE_ALREADY_EXISTS,
898                     item.getDownloadInfo().isOffTheRecord());
899         } else {
900             Toast.makeText(ContextUtils.getApplicationContext(), failureMessage, Toast.LENGTH_SHORT)
901                     .show();
902         }
903     }
904 
905     /**
906      * Open the Activity which shows a list of all downloads.
907      * @param context Application context
908      * @param source The source where the user action coming from.
909      */
openDownloadsPage(Context context, @DownloadOpenSource int source)910     public static void openDownloadsPage(Context context, @DownloadOpenSource int source) {
911         if (DownloadUtils.showDownloadManager(null, null, source)) return;
912 
913         // Open the Android Download Manager.
914         Intent pageView = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
915         pageView.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
916         try {
917             context.startActivity(pageView);
918         } catch (ActivityNotFoundException e) {
919             Log.e(TAG, "Cannot find Downloads app", e);
920         }
921     }
922 
923     // Deprecated after new download backend.
924     @Override
resumeDownload(ContentId id, DownloadItem item, boolean hasUserGesture)925     public void resumeDownload(ContentId id, DownloadItem item, boolean hasUserGesture) {
926         DownloadProgress progress = mDownloadProgressMap.get(item.getId());
927         if (progress != null && progress.mDownloadStatus == DownloadStatus.IN_PROGRESS
928                 && !progress.mDownloadItem.getDownloadInfo().isPaused()) {
929             // Download already in progress, do nothing
930             return;
931         }
932         int uma =
933                 hasUserGesture ? UmaDownloadResumption.CLICKED : UmaDownloadResumption.AUTO_STARTED;
934         DownloadNotificationUmaHelper.recordDownloadResumptionHistogram(uma);
935         if (progress == null) {
936             assert !item.getDownloadInfo().isPaused();
937             // If the download was not resumed before, the browser must have been killed while the
938             // download is active.
939             if (!sFirstSeenDownloadIds.contains(item.getId())) {
940                 sFirstSeenDownloadIds.add(item.getId());
941                 DownloadNotificationUmaHelper.recordDownloadResumptionHistogram(
942                         UmaDownloadResumption.BROWSER_KILLED);
943             }
944             updateDownloadProgress(item, DownloadStatus.IN_PROGRESS);
945             progress = mDownloadProgressMap.get(item.getId());
946         }
947         if (hasUserGesture) {
948             // If user manually resumes a download, update the connection type that the download
949             // can start. If the previous connection type is metered, manually resuming on an
950             // unmetered network should not affect the original connection type.
951             if (!progress.mCanDownloadWhileMetered) {
952                 progress.mCanDownloadWhileMetered =
953                         isActiveNetworkMetered(ContextUtils.getApplicationContext());
954             }
955             incrementDownloadRetryCount(item.getId(), true);
956             clearDownloadRetryCount(item.getId(), true);
957         } else {
958             // TODO(qinmin): Consolidate this logic with the logic in notification service that
959             // throttles browser restarts.
960             SharedPreferences sharedPrefs = getAutoRetryCountSharedPreference();
961             int count = sharedPrefs.getInt(item.getId(), 0);
962             if (count >= getAutoResumptionLimit()) {
963                 removeAutoResumableDownload(item.getId());
964                 onDownloadInterrupted(item.getDownloadInfo(), false);
965                 return;
966             }
967             incrementDownloadRetryCount(item.getId(), false);
968         }
969 
970         // Downloads started from incognito mode should not be resumed in reduced mode.
971         if (!ProfileManager.isInitialized() && item.getDownloadInfo().isOffTheRecord()) return;
972 
973         DownloadManagerServiceJni.get().resumeDownload(getNativeDownloadManagerService(),
974                 DownloadManagerService.this, item.getId(),
975                 getProfileKey(item.getDownloadInfo().isOffTheRecord()), hasUserGesture);
976     }
977 
978     /**
979      * Called to retry a download. May resume the current download or start a new one.
980      * @param id The {@link ContentId} of the download to retry.
981      * @param item The current download that needs to retry.
982      * @param hasUserGesture Whether the request was originated due to user gesture.
983      */
984     // Deprecated after new download backend.
985     // TODO(shaktisahu): Add retry to offline content provider or route it from resume call.
retryDownload(ContentId id, DownloadItem item, boolean hasUserGesture)986     public void retryDownload(ContentId id, DownloadItem item, boolean hasUserGesture) {
987         DownloadManagerServiceJni.get().retryDownload(getNativeDownloadManagerService(),
988                 DownloadManagerService.this, item.getId(),
989                 getProfileKey(item.getDownloadInfo().isOffTheRecord()), hasUserGesture);
990     }
991 
992     /**
993      * Called to cancel a download.
994      * @param id The {@link ContentId} of the download to cancel.
995      * @param isOffTheRecord Whether the download is off the record.
996      */
997     // Deprecated after new download backend.
998     @Override
cancelDownload(ContentId id, boolean isOffTheRecord)999     public void cancelDownload(ContentId id, boolean isOffTheRecord) {
1000         DownloadManagerServiceJni.get().cancelDownload(getNativeDownloadManagerService(),
1001                 DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord));
1002         DownloadProgress progress = mDownloadProgressMap.get(id.id);
1003         if (progress != null) {
1004             DownloadInfo info =
1005                     DownloadInfo.Builder.fromDownloadInfo(progress.mDownloadItem.getDownloadInfo())
1006                             .build();
1007             onDownloadCancelled(info);
1008             removeDownloadProgress(id.id);
1009         } else {
1010             mDownloadNotifier.notifyDownloadCanceled(id);
1011             DownloadInfoBarController infoBarController = getInfoBarController(isOffTheRecord);
1012             if (infoBarController != null) infoBarController.onDownloadItemRemoved(id);
1013         }
1014     }
1015 
1016     /**
1017      * Called to pause a download.
1018      * @param id The {@link ContentId} of the download to pause.
1019      * @param isOffTheRecord Whether the download is off the record.
1020      */
1021     // Deprecated after new download backend.
1022     @Override
pauseDownload(ContentId id, boolean isOffTheRecord)1023     public void pauseDownload(ContentId id, boolean isOffTheRecord) {
1024         DownloadManagerServiceJni.get().pauseDownload(getNativeDownloadManagerService(),
1025                 DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord));
1026         DownloadProgress progress = mDownloadProgressMap.get(id.id);
1027         // Calling pause will stop listening to the download item. Update its progress now.
1028         // If download is already completed, canceled or failed, there is no need to update the
1029         // download notification.
1030         if (progress != null
1031                 && (progress.mDownloadStatus == DownloadStatus.INTERRUPTED
1032                            || progress.mDownloadStatus == DownloadStatus.IN_PROGRESS)) {
1033             DownloadInfo info = DownloadInfo.Builder.fromDownloadInfo(
1034                     progress.mDownloadItem.getDownloadInfo()).setIsPaused(true)
1035                     .setBytesReceived(UNKNOWN_BYTES_RECEIVED).build();
1036             onDownloadUpdated(info);
1037         }
1038     }
1039 
1040     @Override
destroyServiceDelegate()1041     public void destroyServiceDelegate() {
1042         // Lifecycle of DownloadManagerService allows for this call to be ignored.
1043     }
1044 
1045     /**
1046      * Removes a download from the list.
1047      * @param downloadGuid GUID of the download.
1048      * @param isOffTheRecord Whether the download is off the record.
1049      * @param externallyRemoved If the file is externally removed by other applications.
1050      */
removeDownload( final String downloadGuid, boolean isOffTheRecord, boolean externallyRemoved)1051     public void removeDownload(
1052             final String downloadGuid, boolean isOffTheRecord, boolean externallyRemoved) {
1053         mHandler.post(() -> {
1054             DownloadManagerServiceJni.get().removeDownload(getNativeDownloadManagerService(),
1055                     DownloadManagerService.this, downloadGuid, getProfileKey(isOffTheRecord));
1056             removeDownloadProgress(downloadGuid);
1057         });
1058 
1059         if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
1060             return;
1061         }
1062 
1063         DownloadManagerBridge.removeCompletedDownload(downloadGuid, externallyRemoved);
1064     }
1065 
1066     /**
1067      * Checks whether the download can be opened by the browser.
1068      * @param isOffTheRecord Whether the download is off the record.
1069      * @param mimeType MIME type of the file.
1070      * @return Whether the download is openable by the browser.
1071      */
isDownloadOpenableInBrowser(boolean isOffTheRecord, String mimeType)1072     public boolean isDownloadOpenableInBrowser(boolean isOffTheRecord, String mimeType) {
1073         // TODO(qinmin): for audio and video, check if the codec is supported by Chrome.
1074         return isSupportedMimeType(mimeType);
1075     }
1076 
1077     /**
1078      * Checks whether a file with the given MIME type can be opened by the browser.
1079      * @param mimeType MIME type of the file.
1080      * @return Whether the file would be openable by the browser.
1081      */
isSupportedMimeType(String mimeType)1082     public static boolean isSupportedMimeType(String mimeType) {
1083         return DownloadManagerServiceJni.get().isSupportedMimeType(mimeType);
1084     }
1085 
1086     /**
1087      * Helper method to create and retrieve the native DownloadManagerService when needed.
1088      * @return pointer to native DownloadManagerService.
1089      */
getNativeDownloadManagerService()1090     private long getNativeDownloadManagerService() {
1091         if (mNativeDownloadManagerService == 0) {
1092             boolean startupCompleted = ProfileManager.isInitialized();
1093             mNativeDownloadManagerService = DownloadManagerServiceJni.get().init(
1094                     DownloadManagerService.this, startupCompleted);
1095             if (!startupCompleted) ProfileManager.addObserver(this);
1096         }
1097         return mNativeDownloadManagerService;
1098     }
1099 
1100     @Override
onProfileAdded(Profile profile)1101     public void onProfileAdded(Profile profile) {
1102         ProfileManager.removeObserver(this);
1103         DownloadManagerServiceJni.get().onProfileAdded(
1104                 mNativeDownloadManagerService, DownloadManagerService.this, profile);
1105     }
1106 
1107     @Override
onProfileDestroyed(Profile profile)1108     public void onProfileDestroyed(Profile profile) {}
1109 
1110     @CalledByNative
onResumptionFailed(String downloadGuid)1111     void onResumptionFailed(String downloadGuid) {
1112         mDownloadNotifier.notifyDownloadFailed(new DownloadInfo.Builder()
1113                                                        .setDownloadGuid(downloadGuid)
1114                                                        .setFailState(FailState.CANNOT_DOWNLOAD)
1115                                                        .build());
1116         removeDownloadProgress(downloadGuid);
1117         DownloadNotificationUmaHelper.recordDownloadResumptionHistogram(
1118                 UmaDownloadResumption.FAILED);
1119     }
1120 
1121     /**
1122      * Called when download success notification is shown.
1123      * @param info Information about the download.
1124      * @param canResolve Whether to open the download automatically.
1125      * @param notificationId Notification ID of the download.
1126      * @param systemDownloadId System download ID assigned by the Android DownloadManager.
1127      */
onSuccessNotificationShown( DownloadInfo info, boolean canResolve, int notificationId, long systemDownloadId)1128     public void onSuccessNotificationShown(
1129             DownloadInfo info, boolean canResolve, int notificationId, long systemDownloadId) {
1130         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)) {
1131             if (canResolve && MimeUtils.canAutoOpenMimeType(info.getMimeType())
1132                     && info.hasUserGesture()) {
1133                 DownloadItem item = new DownloadItem(false, info);
1134                 item.setSystemDownloadId(systemDownloadId);
1135                 handleAutoOpenAfterDownload(item);
1136             } else {
1137                 DownloadInfoBarController infobarController =
1138                         getInfoBarController(info.isOffTheRecord());
1139                 if (infobarController != null) {
1140                     infobarController.onNotificationShown(info.getContentId(), notificationId);
1141                 }
1142             }
1143         } else {
1144             if (getInfoBarController(info.isOffTheRecord()) != null) {
1145                 getInfoBarController(info.isOffTheRecord())
1146                         .onNotificationShown(info.getContentId(), notificationId);
1147             }
1148         }
1149 
1150         if (BrowserStartupController.getInstance().isFullBrowserStarted()) {
1151             Profile profile = info.isOffTheRecord()
1152                     ? Profile.getLastUsedRegularProfile().getPrimaryOTRProfile()
1153                     : Profile.getLastUsedRegularProfile();
1154             Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
1155             tracker.notifyEvent(EventConstants.DOWNLOAD_COMPLETED);
1156         }
1157     }
1158 
1159     /**
1160      * Helper method to record the bytes wasted metrics when a download completes.
1161      * @param name Histogram name
1162      * @param bytesWasted Bytes wasted during download.
1163      */
recordBytesWasted(String name, long bytesWasted)1164     private void recordBytesWasted(String name, long bytesWasted) {
1165         RecordHistogram.recordCustomCountHistogram(name,
1166                 (int) ConversionUtils.bytesToKilobytes(bytesWasted), 1,
1167                 ConversionUtils.KILOBYTES_PER_GIGABYTE, 50);
1168     }
1169 
1170     /**
1171      * Used only for android DownloadManager associated downloads.
1172      * @param item The associated download item.
1173      * @param showNotification Whether to show notification for this download.
1174      * @param result The query result about the download.
1175      */
onQueryCompleted(DownloadItem item, boolean showNotification, DownloadManagerBridge.DownloadQueryResult result)1176     public void onQueryCompleted(DownloadItem item, boolean showNotification,
1177             DownloadManagerBridge.DownloadQueryResult result) {
1178         DownloadInfo.Builder builder = item.getDownloadInfo() == null
1179                 ? new DownloadInfo.Builder()
1180                 : DownloadInfo.Builder.fromDownloadInfo(item.getDownloadInfo());
1181         builder.setBytesTotalSize(result.bytesTotal);
1182         builder.setBytesReceived(result.bytesDownloaded);
1183         if (!TextUtils.isEmpty(result.fileName)) builder.setFileName(result.fileName);
1184         if (!TextUtils.isEmpty(result.mimeType)) builder.setMimeType(result.mimeType);
1185         builder.setFilePath(result.filePath);
1186         item.setDownloadInfo(builder.build());
1187 
1188         if (result.downloadStatus == DownloadStatus.IN_PROGRESS) return;
1189         if (showNotification) {
1190             switch (result.downloadStatus) {
1191                 case DownloadStatus.COMPLETE:
1192                     new AsyncTask<Boolean>() {
1193                         @Override
1194                         protected Boolean doInBackground() {
1195                             return canResolveDownloadItem(item,
1196                                     isSupportedMimeType(item.getDownloadInfo().getMimeType()));
1197                         }
1198 
1199                         @Override
1200                         protected void onPostExecute(Boolean canResolve) {
1201                             if (MimeUtils.canAutoOpenMimeType(result.mimeType)
1202                                     && item.getDownloadInfo().hasUserGesture() && canResolve) {
1203                                 handleAutoOpenAfterDownload(item);
1204                             } else {
1205                                 getInfoBarController(item.getDownloadInfo().isOffTheRecord())
1206                                         .onItemUpdated(DownloadItem.createOfflineItem(item), null);
1207                             }
1208                         }
1209                     }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1210                     break;
1211                 case DownloadStatus.FAILED:
1212                     onDownloadFailed(item, result.failureReason);
1213                     break;
1214                 default:
1215                     break;
1216             }
1217         }
1218     }
1219 
1220     /**
1221      * Called by tests to disable listening to network connection changes.
1222      */
1223     @VisibleForTesting
disableNetworkListenerForTest()1224     static void disableNetworkListenerForTest() {
1225         sIsNetworkListenerDisabled = true;
1226     }
1227 
1228     /**
1229      * Called by tests to set the network type.
1230      * @isNetworkMetered Whether the network should appear to be metered.
1231      */
1232     @VisibleForTesting
setIsNetworkMeteredForTest(boolean isNetworkMetered)1233     static void setIsNetworkMeteredForTest(boolean isNetworkMetered) {
1234         sIsNetworkMetered = isNetworkMetered;
1235     }
1236 
1237     /**
1238      * Helper method to add an auto resumable download.
1239      * @param guid Id of the download item.
1240      */
1241     // Deprecated after native auto-resumption handler.
addAutoResumableDownload(String guid)1242     private void addAutoResumableDownload(String guid) {
1243         if (CachedFeatureFlags.isEnabled(ChromeFeatureList.DOWNLOADS_AUTO_RESUMPTION_NATIVE)) {
1244             return;
1245         }
1246         if (mAutoResumableDownloadIds.isEmpty() && !sIsNetworkListenerDisabled) {
1247             mNetworkChangeNotifier = new NetworkChangeNotifierAutoDetect(
1248                     this, new RegistrationPolicyAlwaysRegister());
1249         }
1250         if (!mAutoResumableDownloadIds.contains(guid)) {
1251             mAutoResumableDownloadIds.add(guid);
1252         }
1253     }
1254 
1255     /**
1256      * Helper method to remove an auto resumable download.
1257      * @param guid Id of the download item.
1258      */
1259     // Deprecated after native auto-resumption.
removeAutoResumableDownload(String guid)1260     private void removeAutoResumableDownload(String guid) {
1261         if (CachedFeatureFlags.isEnabled(ChromeFeatureList.DOWNLOADS_AUTO_RESUMPTION_NATIVE)) {
1262             return;
1263         }
1264         if (mAutoResumableDownloadIds.isEmpty()) return;
1265         mAutoResumableDownloadIds.remove(guid);
1266         stopListenToConnectionChangeIfNotNeeded();
1267     }
1268 
1269     /**
1270      * Helper method to remove a download from |mDownloadProgressMap|.
1271      * @param guid Id of the download item.
1272      */
1273     // Deprecated after new download backend.
removeDownloadProgress(String guid)1274     private void removeDownloadProgress(String guid) {
1275         mDownloadProgressMap.remove(guid);
1276         removeAutoResumableDownload(guid);
1277         sFirstSeenDownloadIds.remove(guid);
1278     }
1279 
1280     // Deprecated after native auto resumption.
1281     @Override
onConnectionTypeChanged(int connectionType)1282     public void onConnectionTypeChanged(int connectionType) {
1283         if (CachedFeatureFlags.isEnabled(ChromeFeatureList.DOWNLOADS_AUTO_RESUMPTION_NATIVE)) {
1284             return;
1285         }
1286         if (mAutoResumableDownloadIds.isEmpty()) return;
1287         if (connectionType == ConnectionType.CONNECTION_NONE) return;
1288         boolean isMetered = isActiveNetworkMetered(ContextUtils.getApplicationContext());
1289         // Make a copy of |mAutoResumableDownloadIds| as scheduleDownloadResumption() may delete
1290         // elements inside the array.
1291         List<String> copies = new ArrayList<String>(mAutoResumableDownloadIds);
1292         Iterator<String> iterator = copies.iterator();
1293         while (iterator.hasNext()) {
1294             final String id = iterator.next();
1295             final DownloadProgress progress = mDownloadProgressMap.get(id);
1296             // Introduce some delay in each resumption so we don't start all of them immediately.
1297             if (progress != null && (progress.mCanDownloadWhileMetered || !isMetered)) {
1298                 scheduleDownloadResumption(progress.mDownloadItem);
1299             }
1300         }
1301         stopListenToConnectionChangeIfNotNeeded();
1302     }
1303 
1304     /**
1305      * Helper method to stop listening to the connection type change
1306      * if it is no longer needed.
1307      */
1308     // Deprecated after native auto resumption.
stopListenToConnectionChangeIfNotNeeded()1309     private void stopListenToConnectionChangeIfNotNeeded() {
1310         if (mAutoResumableDownloadIds.isEmpty() && mNetworkChangeNotifier != null) {
1311             mNetworkChangeNotifier.destroy();
1312             mNetworkChangeNotifier = null;
1313         }
1314     }
1315 
1316     // Deprecated after native auto resumption.
isActiveNetworkMetered(Context context)1317     static boolean isActiveNetworkMetered(Context context) {
1318         if (sIsNetworkListenerDisabled) return sIsNetworkMetered;
1319         ConnectivityManager cm =
1320                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
1321         return cm.isActiveNetworkMetered();
1322     }
1323 
1324     /** Adds a new DownloadObserver to the list. */
1325     // Deprecated after new download backend.
addDownloadObserver(DownloadObserver observer)1326     public void addDownloadObserver(DownloadObserver observer) {
1327         mDownloadObservers.addObserver(observer);
1328         DownloadSharedPreferenceHelper.getInstance().addObserver(observer);
1329     }
1330 
1331     /** Removes a DownloadObserver from the list. */
1332     // Deprecated after new download backend.
removeDownloadObserver(DownloadObserver observer)1333     public void removeDownloadObserver(DownloadObserver observer) {
1334         mDownloadObservers.removeObserver(observer);
1335         DownloadSharedPreferenceHelper.getInstance().removeObserver(observer);
1336     }
1337 
1338     /**
1339      * Begins sending back information about all entries in the user's DownloadHistory via
1340      * {@link #onAllDownloadsRetrieved}.  If the DownloadHistory is not initialized yet, the
1341      * callback will be delayed.
1342      *
1343      * @param isOffTheRecord Whether or not to get downloads for the off the record profile.
1344      */
1345     // Deprecated after new download backend.
getAllDownloads(boolean isOffTheRecord)1346     public void getAllDownloads(boolean isOffTheRecord) {
1347         DownloadManagerServiceJni.get().getAllDownloads(getNativeDownloadManagerService(),
1348                 DownloadManagerService.this, getProfileKey(isOffTheRecord));
1349     }
1350 
1351     /**
1352      * Fires an Intent that alerts the DownloadNotificationService that an action must be taken
1353      * for a particular item.
1354      */
1355     // Deprecated after new download backend.
broadcastDownloadAction(DownloadItem downloadItem, String action)1356     public void broadcastDownloadAction(DownloadItem downloadItem, String action) {
1357         Context appContext = ContextUtils.getApplicationContext();
1358             Intent intent = DownloadNotificationFactory.buildActionIntent(appContext, action,
1359                     LegacyHelpers.buildLegacyContentId(false, downloadItem.getId()),
1360                     downloadItem.getDownloadInfo().isOffTheRecord());
1361             addCancelExtra(intent, downloadItem);
1362             appContext.startService(intent);
1363     }
1364 
1365     // Deprecated after new download backend.
renameDownload(ContentId id, String name, Callback<Integer > callback, boolean isOffTheRecord)1366     public void renameDownload(ContentId id, String name,
1367             Callback<Integer /*RenameResult*/> callback, boolean isOffTheRecord) {
1368         DownloadManagerServiceJni.get().renameDownload(getNativeDownloadManagerService(),
1369                 DownloadManagerService.this, id.id, name, callback, getProfileKey(isOffTheRecord));
1370     }
1371 
1372     /**
1373      * Change the download schedule to start the download in a different condition.
1374      * @param id The id of the {@link OfflineItem} that requests the change.
1375      * @param schedule The download schedule that defines when to start the download.
1376      * @param isOffTheRecord Whether the download is for off the record profile.
1377      */
1378     // Deprecated after new download backend.
changeSchedule( final ContentId id, final OfflineItemSchedule schedule, boolean isOffTheRecord)1379     public void changeSchedule(
1380             final ContentId id, final OfflineItemSchedule schedule, boolean isOffTheRecord) {
1381         boolean onlyOnWifi = (schedule == null) ? false : schedule.onlyOnWifi;
1382         long startTimeMs = (schedule == null) ? -1 : schedule.startTimeMs;
1383         DownloadManagerServiceJni.get().changeSchedule(getNativeDownloadManagerService(),
1384                 DownloadManagerService.this, id.id, onlyOnWifi, startTimeMs,
1385                 getProfileKey(isOffTheRecord));
1386     }
1387 
1388     /**
1389      * Add an Intent extra for StateAtCancel UMA to know the state of a request prior to a
1390      * user-initated cancel.
1391      * @param intent The Intent associated with the download action.
1392      * @param downloadItem The download associated with download action.
1393      */
1394     // Deprecated after new download backend.
addCancelExtra(Intent intent, DownloadItem downloadItem)1395     private void addCancelExtra(Intent intent, DownloadItem downloadItem) {
1396         if (intent.getAction().equals(DownloadNotificationService.ACTION_DOWNLOAD_CANCEL)) {
1397             int state;
1398             if (DownloadUtils.isDownloadPaused(downloadItem)) {
1399                 state = DownloadNotificationUmaHelper.StateAtCancel.PAUSED;
1400             } else if (DownloadUtils.isDownloadPending(downloadItem)) {
1401                 if (downloadItem.getDownloadInfo().getPendingState()
1402                         == PendingState.PENDING_NETWORK) {
1403                     state = DownloadNotificationUmaHelper.StateAtCancel.PENDING_NETWORK;
1404                 } else {
1405                     state = DownloadNotificationUmaHelper.StateAtCancel.PENDING_ANOTHER_DOWNLOAD;
1406                 }
1407             } else {
1408                 state = DownloadNotificationUmaHelper.StateAtCancel.DOWNLOADING;
1409             }
1410             intent.putExtra(DownloadNotificationService.EXTRA_DOWNLOAD_STATE_AT_CANCEL, state);
1411         }
1412     }
1413 
1414     /**
1415      * Checks if the files associated with any downloads have been removed by an external action.
1416      * @param isOffTheRecord Whether or not to check downloads for the off the record profile.
1417      */
checkForExternallyRemovedDownloads(boolean isOffTheRecord)1418     public void checkForExternallyRemovedDownloads(boolean isOffTheRecord) {
1419         DownloadManagerServiceJni.get().checkForExternallyRemovedDownloads(
1420                 getNativeDownloadManagerService(), DownloadManagerService.this,
1421                 getProfileKey(isOffTheRecord));
1422     }
1423 
1424     // Deprecated after new download backend.
1425     @CalledByNative
createDownloadItemList()1426     private List<DownloadItem> createDownloadItemList() {
1427         return new ArrayList<DownloadItem>();
1428     }
1429 
1430     // Deprecated after new download backend.
1431     @CalledByNative
addDownloadItemToList(List<DownloadItem> list, DownloadItem item)1432     private void addDownloadItemToList(List<DownloadItem> list, DownloadItem item) {
1433         list.add(item);
1434     }
1435 
1436     // Deprecated after new download backend.
1437     @CalledByNative
onAllDownloadsRetrieved(final List<DownloadItem> list, ProfileKey profileKey)1438     private void onAllDownloadsRetrieved(final List<DownloadItem> list, ProfileKey profileKey) {
1439         // TODO(https://crbug.com/1099577): Pass the profileKey/profile to adapter instead of the
1440         // boolean.
1441         boolean isOffTheRecord = profileKey.isOffTheRecord();
1442         for (DownloadObserver adapter : mDownloadObservers) {
1443             adapter.onAllDownloadsRetrieved(list, isOffTheRecord);
1444         }
1445         maybeShowMissingSdCardError(list);
1446     }
1447 
1448     /**
1449      * Shows a snackbar that tells the user that files may be missing because no SD card was found
1450      * in the case that the error was not shown before and at least one of the items was
1451      * externally removed and has a path that points to a missing external drive.
1452      *
1453      * @param list  List of DownloadItems to check.
1454      */
1455     // TODO(shaktisahu): Drive this from a similar observer.
maybeShowMissingSdCardError(List<DownloadItem> list)1456     private void maybeShowMissingSdCardError(List<DownloadItem> list) {
1457         PrefService prefService = UserPrefs.get(Profile.getLastUsedRegularProfile());
1458         // Only show the missing directory snackbar once.
1459         if (!prefService.getBoolean(Pref.SHOW_MISSING_SD_CARD_ERROR_ANDROID)) return;
1460 
1461         DownloadDirectoryProvider provider = DownloadDirectoryProvider.getInstance();
1462         provider.getAllDirectoriesOptions((ArrayList<DirectoryOption> dirs) -> {
1463             if (dirs.size() > 1) return;
1464             String externalStorageDir = provider.getExternalStorageDirectory();
1465 
1466             for (DownloadItem item : list) {
1467                 boolean missingOnSDCard = isFilePathOnMissingExternalDrive(
1468                         item.getDownloadInfo().getFilePath(), externalStorageDir, dirs);
1469                 if (!isUnresumableOrCancelled(item) && missingOnSDCard) {
1470                     mHandler.post(() -> {
1471                         // TODO(shaktisahu): Show it on infobar in the right way.
1472                         mDownloadSnackbarController.onDownloadDirectoryNotFound();
1473                     });
1474                     prefService.setBoolean(Pref.SHOW_MISSING_SD_CARD_ERROR_ANDROID, false);
1475                     break;
1476                 }
1477             }
1478         });
1479     }
1480 
1481     /**
1482      * Checks to see if the item is either unresumable or cancelled.
1483      *
1484      * @param downloadItem  Item to check.
1485      * @return              Whether the item is unresumable or cancelled.
1486      */
isUnresumableOrCancelled(DownloadItem downloadItem)1487     private boolean isUnresumableOrCancelled(DownloadItem downloadItem) {
1488         @DownloadState
1489         int state = downloadItem.getDownloadInfo().state();
1490         return (state == DownloadState.INTERRUPTED && !downloadItem.getDownloadInfo().isResumable())
1491                 || state == DownloadState.CANCELLED;
1492     }
1493 
1494     /**
1495      * Returns whether a given file path is in a directory that is no longer available, most likely
1496      * because it is on an SD card that was removed.
1497      *
1498      * @param filePath  The file path to check, can be a content URI.
1499      * @param externalStorageDir  The absolute path of external storage directory for primary
1500      * storage.
1501      * @param directoryOptions  All available download directories including primary storage and
1502      * secondary storage.
1503      *
1504      * @return          Whether this file path is in a directory that is no longer available.
1505      */
isFilePathOnMissingExternalDrive(String filePath, String externalStorageDir, ArrayList<DirectoryOption> directoryOptions)1506     private boolean isFilePathOnMissingExternalDrive(String filePath, String externalStorageDir,
1507             ArrayList<DirectoryOption> directoryOptions) {
1508         if (TextUtils.isEmpty(filePath) || filePath.contains(externalStorageDir)
1509                 || ContentUriUtils.isContentUri(filePath)) {
1510             return false;
1511         }
1512 
1513         for (DirectoryOption directory : directoryOptions) {
1514             if (TextUtils.isEmpty(directory.location)) continue;
1515             if (filePath.contains(directory.location)) return false;
1516         }
1517 
1518         return true;
1519     }
1520 
1521     // Deprecated after new download backend.
1522     @CalledByNative
onDownloadItemCreated(DownloadItem item)1523     private void onDownloadItemCreated(DownloadItem item) {
1524         for (DownloadObserver adapter : mDownloadObservers) {
1525             adapter.onDownloadItemCreated(item);
1526         }
1527     }
1528 
1529     // Deprecated after new download backend.
1530     @CalledByNative
onDownloadItemUpdated(DownloadItem item)1531     private void onDownloadItemUpdated(DownloadItem item) {
1532         for (DownloadObserver adapter : mDownloadObservers) {
1533             adapter.onDownloadItemUpdated(item);
1534         }
1535     }
1536 
1537     // Deprecated after new download backend.
1538     @CalledByNative
onDownloadItemRemoved(String guid, boolean isOffTheRecord)1539     private void onDownloadItemRemoved(String guid, boolean isOffTheRecord) {
1540         DownloadInfoBarController infobarController = getInfoBarController(isOffTheRecord);
1541         if (infobarController != null) {
1542             infobarController.onDownloadItemRemoved(
1543                     LegacyHelpers.buildLegacyContentId(false, guid));
1544         }
1545 
1546         for (DownloadObserver adapter : mDownloadObservers) {
1547             adapter.onDownloadItemRemoved(guid, isOffTheRecord);
1548         }
1549     }
1550 
1551     // Deprecated after new download backend.
1552     @CalledByNative
openDownloadItem(DownloadItem downloadItem, @DownloadOpenSource int source)1553     private void openDownloadItem(DownloadItem downloadItem, @DownloadOpenSource int source) {
1554         DownloadInfo downloadInfo = downloadItem.getDownloadInfo();
1555         boolean canOpen =
1556                 DownloadUtils.openFile(downloadInfo.getFilePath(), downloadInfo.getMimeType(),
1557                         downloadInfo.getDownloadGuid(), downloadInfo.isOffTheRecord(),
1558                         downloadInfo.getOriginalUrl(), downloadInfo.getReferrer(), source);
1559         if (!canOpen) {
1560             openDownloadsPage(ContextUtils.getApplicationContext(), source);
1561         }
1562     }
1563 
1564     /**
1565      * Opens a download. If the download cannot be opened, download home will be opened instead.
1566      * @param id The {@link ContentId} of the download to be opened.
1567      * @param source The source where the user opened this download.
1568      */
1569     // Deprecated after new download backend.
openDownload(ContentId id, boolean isOffTheRecord, @DownloadOpenSource int source)1570     public void openDownload(ContentId id, boolean isOffTheRecord, @DownloadOpenSource int source) {
1571         DownloadManagerServiceJni.get().openDownload(getNativeDownloadManagerService(),
1572                 DownloadManagerService.this, id.id, getProfileKey(isOffTheRecord), source);
1573     }
1574 
1575     /**
1576      * Checks whether the download will be immediately opened after completion.
1577      * @param downloadItem The download item to be opened.
1578      * @return True if the download will be auto-opened, false otherwise.
1579      */
checkIfDownloadWillAutoOpen(DownloadItem downloadItem, Callback<Boolean> callback)1580     public void checkIfDownloadWillAutoOpen(DownloadItem downloadItem, Callback<Boolean> callback) {
1581         assert(downloadItem.getDownloadInfo().state() == DownloadState.COMPLETE);
1582 
1583         AsyncTask<Boolean> task = new AsyncTask<Boolean>() {
1584             @Override
1585             public Boolean doInBackground() {
1586                 DownloadInfo info = downloadItem.getDownloadInfo();
1587                 boolean isSupportedMimeType = isSupportedMimeType(info.getMimeType());
1588                 boolean canResolve = MimeUtils.isOMADownloadDescription(info.getMimeType())
1589                         || canResolveDownloadItem(downloadItem, isSupportedMimeType);
1590                 return canResolve && MimeUtils.canAutoOpenMimeType(info.getMimeType())
1591                         && info.hasUserGesture();
1592             }
1593             @Override
1594             protected void onPostExecute(Boolean result) {
1595                 callback.onResult(result);
1596             }
1597         };
1598 
1599         try {
1600             task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1601         } catch (RejectedExecutionException e) {
1602             // Reaching thread limit, update will be reschduled for the next run.
1603             Log.e(TAG, "Thread limit reached, reschedule notification update later.");
1604         }
1605     }
1606 
1607     /**
1608      * Called when a download is canceled before download target is determined.
1609      *
1610      * @param item The download item.
1611      * @param isExternalStorageMissing Whether the reason for failure is missing external storage.
1612      */
1613     @CalledByNative
onDownloadItemCanceled( DownloadItem item, boolean isExternalStorageMissing)1614     private static void onDownloadItemCanceled(
1615             DownloadItem item, boolean isExternalStorageMissing) {
1616         DownloadManagerService service = getDownloadManagerService();
1617         int reason = isExternalStorageMissing ? DownloadManager.ERROR_DEVICE_NOT_FOUND
1618                 : DownloadManager.ERROR_FILE_ALREADY_EXISTS;
1619         service.onDownloadFailed(item, reason);
1620 
1621         // TODO(shaktisahu): Notify infobar controller.
1622     }
1623 
1624     /**
1625      * Get the message to display when a download fails.
1626      *
1627      * @param fileName Name of the download file.
1628      * @param reason Reason of failure reported by android DownloadManager.
1629      */
getDownloadFailureMessage(String fileName, int reason)1630     private String getDownloadFailureMessage(String fileName, int reason) {
1631         Context appContext = ContextUtils.getApplicationContext();
1632         switch (reason) {
1633             case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
1634                 return appContext.getString(
1635                         R.string.download_failed_reason_file_already_exists, fileName);
1636             case DownloadManager.ERROR_FILE_ERROR:
1637                 return appContext.getString(
1638                         R.string.download_failed_reason_file_system_error, fileName);
1639             case DownloadManager.ERROR_INSUFFICIENT_SPACE:
1640                 return appContext.getString(
1641                         R.string.download_failed_reason_insufficient_space, fileName);
1642             case DownloadManager.ERROR_CANNOT_RESUME:
1643             case DownloadManager.ERROR_HTTP_DATA_ERROR:
1644                 return appContext.getString(
1645                         R.string.download_failed_reason_network_failures, fileName);
1646             case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
1647             case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
1648                 return appContext.getString(
1649                         R.string.download_failed_reason_server_issues, fileName);
1650             case DownloadManager.ERROR_DEVICE_NOT_FOUND:
1651                 return appContext.getString(
1652                         R.string.download_failed_reason_storage_not_found, fileName);
1653             case DownloadManager.ERROR_UNKNOWN:
1654             default:
1655                 return appContext.getString(
1656                         R.string.download_failed_reason_unknown_error, fileName);
1657         }
1658     }
1659 
1660     /**
1661      * Returns the SharedPreferences for download retry count.
1662      * @return The SharedPreferences to use.
1663      */
getAutoRetryCountSharedPreference()1664     private static SharedPreferences getAutoRetryCountSharedPreference() {
1665         return ContextUtils.getApplicationContext().getSharedPreferences(
1666                 DOWNLOAD_RETRY_COUNT_FILE_NAME, Context.MODE_PRIVATE);
1667     }
1668 
1669     /**
1670      * Increments the interruption count for a download. If the interruption count reaches a certain
1671      * threshold, the download will no longer auto resume unless user click the resume button to
1672      * clear the count.
1673      *
1674      * @param downloadGuid Download GUID.
1675      * @param hasUserGesture Whether the retry is caused by user gesture.
1676      */
1677     // Deprecated after new download backend.
incrementDownloadRetryCount(String downloadGuid, boolean hasUserGesture)1678     private void incrementDownloadRetryCount(String downloadGuid, boolean hasUserGesture) {
1679         String name = getDownloadRetryCountSharedPrefName(downloadGuid, hasUserGesture, false);
1680         incrementDownloadRetrySharedPreferenceCount(name);
1681         name = getDownloadRetryCountSharedPrefName(downloadGuid, hasUserGesture, true);
1682         incrementDownloadRetrySharedPreferenceCount(name);
1683     }
1684 
1685     /**
1686      * Helper method to increment the retry count for a SharedPreference entry.
1687      * @param sharedPreferenceName Name of the SharedPreference entry.
1688      */
1689     // Deprecated after new download backend.
incrementDownloadRetrySharedPreferenceCount(String sharedPreferenceName)1690     private void incrementDownloadRetrySharedPreferenceCount(String sharedPreferenceName) {
1691         SharedPreferences sharedPrefs = getAutoRetryCountSharedPreference();
1692         int count = sharedPrefs.getInt(sharedPreferenceName, 0);
1693         SharedPreferences.Editor editor = sharedPrefs.edit();
1694         count++;
1695         editor.putInt(sharedPreferenceName, count);
1696         editor.apply();
1697     }
1698 
1699     /**
1700      * Helper method to retrieve the SharedPreference name for different download retry types.
1701      * TODO(qinmin): introduce a proto for this and consolidate all the UMA metrics (including
1702      * retry counts in DownloadHistory) stored in persistent storage.
1703      * @param downloadGuid Guid of the download.
1704      * @param hasUserGesture Whether the SharedPreference is for manual retry attempts.
1705      * @param isTotalCount Whether the SharedPreference is for total retry attempts.
1706      */
1707     // Deprecated after new download backend.
getDownloadRetryCountSharedPrefName( String downloadGuid, boolean hasUserGesture, boolean isTotalCount)1708     private String getDownloadRetryCountSharedPrefName(
1709             String downloadGuid, boolean hasUserGesture, boolean isTotalCount) {
1710         if (isTotalCount) return downloadGuid + DOWNLOAD_TOTAL_RETRY_SUFFIX;
1711         if (hasUserGesture) return downloadGuid + DOWNLOAD_MANUAL_RETRY_SUFFIX;
1712         return downloadGuid;
1713     }
1714 
1715     /**
1716      * clears the retry count for a download.
1717      *
1718      * @param downloadGuid Download GUID.
1719      * @param isAutoRetryOnly Whether to clear the auto retry count only.
1720      */
1721     // Deprecated after new download backend.
clearDownloadRetryCount(String downloadGuid, boolean isAutoRetryOnly)1722     private void clearDownloadRetryCount(String downloadGuid, boolean isAutoRetryOnly) {
1723         SharedPreferences sharedPrefs = getAutoRetryCountSharedPreference();
1724         String name = getDownloadRetryCountSharedPrefName(downloadGuid, !isAutoRetryOnly, false);
1725         int count = Math.min(sharedPrefs.getInt(name, 0), 200);
1726         assert count >= 0;
1727         SharedPreferences.Editor editor = sharedPrefs.edit();
1728         editor.remove(name);
1729         if (isAutoRetryOnly) {
1730             RecordHistogram.recordSparseHistogram(
1731                     "MobileDownload.ResumptionsCount.Automatic", count);
1732         } else {
1733             RecordHistogram.recordSparseHistogram("MobileDownload.ResumptionsCount.Manual", count);
1734             name = getDownloadRetryCountSharedPrefName(downloadGuid, false, true);
1735             count = sharedPrefs.getInt(name, 0);
1736             assert count >= 0;
1737             RecordHistogram.recordSparseHistogram(
1738                     "MobileDownload.ResumptionsCount.Total", Math.min(count, 500));
1739             editor.remove(name);
1740         }
1741         editor.apply();
1742     }
1743 
1744     // Deprecated after new download backend.
getAutoResumptionLimit()1745     int getAutoResumptionLimit() {
1746         if (mAutoResumptionLimit < 0) {
1747             mAutoResumptionLimit = DownloadManagerServiceJni.get().getAutoResumptionLimit();
1748         }
1749         return mAutoResumptionLimit;
1750     }
1751 
1752     /**
1753      * Creates an interrupted download in native code to be used by instrumentation tests.
1754      * @param url URL of the download.
1755      * @param guid Download GUID.
1756      * @param targetPath Target file path.
1757      */
createInterruptedDownloadForTest(String url, String guid, String targetPath)1758     void createInterruptedDownloadForTest(String url, String guid, String targetPath) {
1759         DownloadManagerServiceJni.get().createInterruptedDownloadForTest(
1760                 getNativeDownloadManagerService(), DownloadManagerService.this, url, guid,
1761                 targetPath);
1762     }
1763 
disableAddCompletedDownloadToDownloadManager()1764     void disableAddCompletedDownloadToDownloadManager() {
1765         mDisableAddCompletedDownloadForTesting = true;
1766     }
1767 
1768     /**
1769      * Updates the last access time of a download.
1770      * @param downloadGuid Download GUID.
1771      * @param isOffTheRecord Whether the download is off the record.
1772      */
1773     // Deprecated after new download backend.
updateLastAccessTime(String downloadGuid, boolean isOffTheRecord)1774     public void updateLastAccessTime(String downloadGuid, boolean isOffTheRecord) {
1775         if (TextUtils.isEmpty(downloadGuid)) return;
1776 
1777         DownloadManagerServiceJni.get().updateLastAccessTime(getNativeDownloadManagerService(),
1778                 DownloadManagerService.this, downloadGuid, getProfileKey(isOffTheRecord));
1779     }
1780 
1781     // Deprecated after native auto-resumption handler.
1782     @Override
onConnectionSubtypeChanged(int newConnectionSubtype)1783     public void onConnectionSubtypeChanged(int newConnectionSubtype) {}
1784 
1785     // Deprecated after native auto-resumption handler.
1786     @Override
onNetworkConnect(long netId, int connectionType)1787     public void onNetworkConnect(long netId, int connectionType) {}
1788 
1789     // Deprecated after native auto-resumption handler.
1790     @Override
onNetworkSoonToDisconnect(long netId)1791     public void onNetworkSoonToDisconnect(long netId) {}
1792 
1793     // Deprecated after native auto-resumption handler.
1794     @Override
onNetworkDisconnect(long netId)1795     public void onNetworkDisconnect(long netId) {}
1796 
1797     // Deprecated after native auto-resumption handler.
1798     @Override
purgeActiveNetworkList(long[] activeNetIds)1799     public void purgeActiveNetworkList(long[] activeNetIds) {}
1800 
1801     @NativeMethods
1802     interface Natives {
isSupportedMimeType(String mimeType)1803         boolean isSupportedMimeType(String mimeType);
getAutoResumptionLimit()1804         int getAutoResumptionLimit();
init(DownloadManagerService caller, boolean isProfileAdded)1805         long init(DownloadManagerService caller, boolean isProfileAdded);
openDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey, int source)1806         void openDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1807                 String downloadGuid, ProfileKey profileKey, int source);
resumeDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey, boolean hasUserGesture)1808         void resumeDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1809                 String downloadGuid, ProfileKey profileKey, boolean hasUserGesture);
retryDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey, boolean hasUserGesture)1810         void retryDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1811                 String downloadGuid, ProfileKey profileKey, boolean hasUserGesture);
cancelDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey)1812         void cancelDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1813                 String downloadGuid, ProfileKey profileKey);
pauseDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey)1814         void pauseDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1815                 String downloadGuid, ProfileKey profileKey);
removeDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey)1816         void removeDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1817                 String downloadGuid, ProfileKey profileKey);
renameDownload(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, String targetName, Callback< Integer> callback, ProfileKey profileKey)1818         void renameDownload(long nativeDownloadManagerService, DownloadManagerService caller,
1819                 String downloadGuid, String targetName, Callback</*RenameResult*/ Integer> callback,
1820                 ProfileKey profileKey);
changeSchedule(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, boolean onlyOnWifi, long startTimeMs, ProfileKey profileKey)1821         void changeSchedule(long nativeDownloadManagerService, DownloadManagerService caller,
1822                 String downloadGuid, boolean onlyOnWifi, long startTimeMs, ProfileKey profileKey);
getAllDownloads(long nativeDownloadManagerService, DownloadManagerService caller, ProfileKey profileKey)1823         void getAllDownloads(long nativeDownloadManagerService, DownloadManagerService caller,
1824                 ProfileKey profileKey);
checkForExternallyRemovedDownloads(long nativeDownloadManagerService, DownloadManagerService caller, ProfileKey profileKey)1825         void checkForExternallyRemovedDownloads(long nativeDownloadManagerService,
1826                 DownloadManagerService caller, ProfileKey profileKey);
updateLastAccessTime(long nativeDownloadManagerService, DownloadManagerService caller, String downloadGuid, ProfileKey profileKey)1827         void updateLastAccessTime(long nativeDownloadManagerService, DownloadManagerService caller,
1828                 String downloadGuid, ProfileKey profileKey);
onProfileAdded( long nativeDownloadManagerService, DownloadManagerService caller, Profile profile)1829         void onProfileAdded(
1830                 long nativeDownloadManagerService, DownloadManagerService caller, Profile profile);
createInterruptedDownloadForTest(long nativeDownloadManagerService, DownloadManagerService caller, String url, String guid, String targetPath)1831         void createInterruptedDownloadForTest(long nativeDownloadManagerService,
1832                 DownloadManagerService caller, String url, String guid, String targetPath);
1833     }
1834 }
1835