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