Index: chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..71045581966e282facd0f27ef8825eea041bed64 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java |
@@ -0,0 +1,719 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.download; |
+ |
+import android.app.DownloadManager; |
+import android.content.ActivityNotFoundException; |
+import android.content.BroadcastReceiver; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.content.IntentFilter; |
+import android.content.SharedPreferences; |
+import android.database.Cursor; |
+import android.net.Uri; |
+import android.os.AsyncTask; |
+import android.os.Environment; |
+import android.os.Handler; |
+import android.preference.PreferenceManager; |
+import android.support.v4.util.LongSparseArray; |
+import android.text.TextUtils; |
+import android.util.Log; |
+import android.widget.Toast; |
+ |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.chrome.R; |
+import org.chromium.content.browser.DownloadController; |
+import org.chromium.content.browser.DownloadInfo; |
+ |
+import java.io.File; |
+import java.util.HashSet; |
+import java.util.Set; |
+import java.util.concurrent.ConcurrentHashMap; |
+import java.util.concurrent.atomic.AtomicBoolean; |
+ |
+/** |
+ * Chrome implementation of the DownloadNotificationService interface. This class is responsible for |
+ * keeping track of which downloads are in progress. It generates updates for progress of downloads |
+ * and handles cleaning up of interrupted progress notifications. |
+ */ |
+public class DownloadManagerService extends BroadcastReceiver implements |
+ DownloadController.DownloadNotificationService { |
+ private static final String TAG = "DownloadNotificationService"; |
+ private static final String DOWNLOAD_NOTIFICATION_IDS = "DownloadNotificationIds"; |
+ private static final String DOWNLOAD_DIRECTORY = "Download"; |
+ protected static final String PENDING_OMA_DOWNLOADS = "PendingOMADownloads"; |
+ private static final long UPDATE_DELAY_MILLIS = 1000; |
+ |
+ private static DownloadManagerService sDownloadManagerService; |
+ |
+ private final SharedPreferences mSharedPrefs; |
+ private final ConcurrentHashMap<Integer, DownloadProgress> mDownloadProgressMap = |
+ new ConcurrentHashMap<Integer, DownloadProgress>(4, 0.75f, 2); |
+ |
+ private final DownloadNotifier mDownloadNotifier; |
+ // Delay between UI updates. |
+ private final long mUpdateDelayInMillis; |
+ |
+ // Flag to track if we need to post a task to update download notifications. |
+ private final AtomicBoolean mIsUIUpdateScheduled; |
+ private final Handler mHandler; |
+ private final Context mContext; |
+ |
+ private final LongSparseArray<DownloadInfo> mPendingAutoOpenDownloads = |
+ new LongSparseArray<DownloadInfo>(); |
+ private OMADownloadHandler mOMADownloadHandler; |
+ |
+ /** |
+ * Enum representing status of a download. |
+ */ |
+ private enum DownloadStatus { |
+ IN_PROGRESS, |
+ COMPLETE, |
+ FAILED |
+ } |
+ |
+ /** |
+ * Class representing progress of a download. |
+ */ |
+ private static class DownloadProgress { |
+ final long mStartTimeInMillis; |
+ volatile DownloadInfo mDownloadInfo; |
+ volatile DownloadStatus mDownloadStatus; |
+ |
+ DownloadProgress(long startTimeInMillis, DownloadInfo downloadInfo, |
+ DownloadStatus downloadStatus) { |
+ mStartTimeInMillis = startTimeInMillis; |
+ mDownloadInfo = downloadInfo; |
+ mDownloadStatus = downloadStatus; |
+ } |
+ } |
+ |
+ /** |
+ * Class representing an OMA download entry to be stored in SharedPrefs. |
+ */ |
+ @VisibleForTesting |
+ protected static class OMAEntry { |
+ final long mDownloadId; |
+ final String mInstallNotifyURI; |
+ |
+ OMAEntry(long downloadId, String installNotifyURI) { |
+ mDownloadId = downloadId; |
+ mInstallNotifyURI = installNotifyURI; |
+ } |
+ |
+ /** |
+ * Parse OMA entry from the SharedPrefs String |
+ * TODO(qinmin): use a file instead of SharedPrefs to store the OMA entry. |
+ * |
+ * @param entry String contains the OMA information. |
+ * @return an OMAEntry object. |
+ */ |
+ @VisibleForTesting |
+ static OMAEntry parseOMAEntry(String entry) { |
+ int index = entry.indexOf(","); |
+ long downloadId = Long.parseLong(entry.substring(0, index)); |
+ return new OMAEntry(downloadId, entry.substring(index + 1)); |
+ } |
+ |
+ /** |
+ * Generates a string for an OMA entry to be inserted into the SharedPrefs. |
+ * TODO(qinmin): use a file instead of SharedPrefs to store the OMA entry. |
+ * |
+ * @return a String representing the download entry. |
+ */ |
+ String generateSharedPrefsString() { |
+ return String.valueOf(mDownloadId) + "," + mInstallNotifyURI; |
+ } |
+ } |
+ |
+ /** |
+ * Creates DownloadManagerService. |
+ */ |
+ public static DownloadManagerService getDownloadManagerService(final Context context) { |
+ ThreadUtils.assertOnUiThread(); |
+ assert context == context.getApplicationContext(); |
+ if (sDownloadManagerService == null) { |
+ sDownloadManagerService = new DownloadManagerService(context, |
+ new SystemDownloadNotifier(context), new Handler(), UPDATE_DELAY_MILLIS); |
+ } |
+ return sDownloadManagerService; |
+ } |
+ |
+ /** |
+ * For tests only: sets the DownloadManagerService. |
+ * @param service An instance of DownloadManagerService. |
+ * @return Null or a currently set instance of DownloadManagerService. |
+ */ |
+ @VisibleForTesting |
+ public static DownloadManagerService setDownloadManagerService(DownloadManagerService service) { |
+ ThreadUtils.assertOnUiThread(); |
+ DownloadManagerService prev = sDownloadManagerService; |
+ sDownloadManagerService = service; |
+ return prev; |
+ } |
+ |
+ @VisibleForTesting |
+ protected DownloadManagerService(Context context, |
+ DownloadNotifier downloadNotifier, |
+ Handler handler, |
+ long updateDelayInMillis) { |
+ mContext = context; |
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context |
+ .getApplicationContext()); |
+ mDownloadNotifier = downloadNotifier; |
+ mUpdateDelayInMillis = updateDelayInMillis; |
+ mHandler = handler; |
+ mIsUIUpdateScheduled = new AtomicBoolean(false); |
+ mOMADownloadHandler = new OMADownloadHandler(context); |
+ } |
+ |
+ @Override |
+ public void onDownloadCompleted(final DownloadInfo downloadInfo) { |
+ DownloadStatus status = DownloadStatus.COMPLETE; |
+ if (!downloadInfo.isSuccessful() || downloadInfo.getContentLength() == 0) { |
+ status = DownloadStatus.FAILED; |
+ } |
+ updateDownloadProgress(downloadInfo, status); |
+ scheduleUpdateIfNeeded(); |
+ } |
+ |
+ @Override |
+ public void onDownloadUpdated(final DownloadInfo downloadInfo) { |
+ updateDownloadProgress(downloadInfo, DownloadStatus.IN_PROGRESS); |
+ scheduleUpdateIfNeeded(); |
+ } |
+ |
+ /** |
+ * Clear any pending notifications for incomplete downloads by reading them from shared prefs. |
+ * When Clank is restarted it clears any old notifications for incomplete downloads. |
+ */ |
+ public void clearPendingDownloadNotifications() { |
+ if (mSharedPrefs.contains(DOWNLOAD_NOTIFICATION_IDS)) { |
+ Set<String> downloadIds = getStoredDownloadInfo(DOWNLOAD_NOTIFICATION_IDS); |
+ for (String id : downloadIds) { |
+ int notificationId = parseNotificationId(id); |
+ if (notificationId > 0) { |
+ mDownloadNotifier.cancelNotification(notificationId); |
+ Log.w(TAG, "Download failed: Cleared download id:" + id); |
+ } |
+ } |
+ mSharedPrefs.edit().remove(DOWNLOAD_NOTIFICATION_IDS); |
+ mSharedPrefs.edit().apply(); |
+ } |
+ if (mSharedPrefs.contains(PENDING_OMA_DOWNLOADS)) { |
+ Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS); |
+ for (String omaDownload : omaDownloads) { |
+ OMAEntry entry = OMAEntry.parseOMAEntry(omaDownload); |
+ clearPendingOMADownload(entry.mDownloadId, entry.mInstallNotifyURI); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Async task to clear the pending OMA download from SharedPrefs and inform |
+ * the OMADownloadHandler about download status. |
+ */ |
+ private class ClearPendingOMADownloadTask extends AsyncTask<Void, Void, Integer> { |
+ private DownloadInfo mDownloadInfo; |
+ private final long mDownloadId; |
+ private final String mInstallNotifyURI; |
+ private int mFailureReason; |
+ |
+ public ClearPendingOMADownloadTask(long downloadId, String installNotifyURI) { |
+ mDownloadId = downloadId; |
+ mInstallNotifyURI = installNotifyURI; |
+ mDownloadInfo = mPendingAutoOpenDownloads.get(downloadId); |
+ } |
+ |
+ @Override |
+ public Integer doInBackground(Void...voids) { |
+ DownloadManager manager = |
+ (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); |
+ Cursor c = manager.query(new DownloadManager.Query().setFilterById(mDownloadId)); |
+ int statusIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); |
+ int reasonIndex = c.getColumnIndex(DownloadManager.COLUMN_REASON); |
+ int filenameIndex = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME); |
+ while (c.moveToNext()) { |
+ int status = c.getInt(statusIndex); |
+ if (mDownloadInfo == null) { |
+ // Chrome has been killed, reconstruct a DownloadInfo. |
+ mDownloadInfo = new DownloadInfo.Builder() |
+ .setDownloadId((int) mDownloadId) |
+ .setDescription(c.getString( |
+ c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION))) |
+ .setMimeType(c.getString( |
+ c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE))) |
+ .setContentLength(Long.parseLong(c.getString( |
+ c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)))) |
+ .build(); |
+ } |
+ if (status == DownloadManager.STATUS_SUCCESSFUL) { |
+ mDownloadInfo = DownloadInfo.Builder.fromDownloadInfo(mDownloadInfo) |
+ .setFilePath(c.getString(filenameIndex)) |
+ .build(); |
+ } else if (status == DownloadManager.STATUS_FAILED) { |
+ mFailureReason = c.getInt(reasonIndex); |
+ manager.remove(mDownloadId); |
+ } |
+ return status; |
+ } |
+ return DownloadManager.STATUS_FAILED; |
+ } |
+ |
+ @Override |
+ protected void onPostExecute(Integer result) { |
+ if (result == DownloadManager.STATUS_SUCCESSFUL) { |
+ mOMADownloadHandler.onDownloadCompleted(mDownloadInfo, mInstallNotifyURI); |
+ removeOMADownloadFromSharedPrefs(mDownloadId); |
+ } else if (result == DownloadManager.STATUS_FAILED) { |
+ mOMADownloadHandler.onDownloadFailed( |
+ mDownloadInfo, mFailureReason, mInstallNotifyURI); |
+ removeOMADownloadFromSharedPrefs(mDownloadId); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Clear pending OMA downloads for a particular download ID. |
+ * |
+ * @param downloadId Download identifier. |
+ * @param info Information about the download. |
+ * @param installNotifyURI URI to notify after installation. |
+ */ |
+ private void clearPendingOMADownload(long downloadId, String installNotifyURI) { |
+ ClearPendingOMADownloadTask task = new ClearPendingOMADownloadTask( |
+ downloadId, installNotifyURI); |
+ task.execute(); |
+ } |
+ |
+ /** |
+ * Parse the notification ID from a String object. |
+ * |
+ * @param id String containing the notification ID. |
+ * @return notification ID. |
+ */ |
+ private static int parseNotificationId(String id) { |
+ try { |
+ return Integer.parseInt(id); |
+ } catch (NumberFormatException nfe) { |
+ Log.w(TAG, "Exception while parsing download id:" + id); |
+ return -1; |
+ } |
+ } |
+ |
+ /** |
+ * Broadcast that a download was successful. |
+ * @param downloadInfo info about the download. |
+ */ |
+ protected void broadcastDownloadSuccessful(DownloadInfo downloadInfo) {} |
+ |
+ /** |
+ * Gets download information from SharedPrefs. |
+ * @param type Type of the information to retrieve. |
+ * @return download information saved to the SharedPrefs for the given type. |
+ */ |
+ @VisibleForTesting |
+ protected Set<String> getStoredDownloadInfo(String type) { |
+ return new HashSet<String>(mSharedPrefs.getStringSet( |
+ type, new HashSet<String>())); |
+ } |
+ |
+ /** |
+ * Removes a donwload Id from SharedPrefs. |
+ * @param downloadId ID to be removed. |
+ */ |
+ private void removeDownloadIdFromSharedPrefs(int downloadId) { |
+ Set<String> downloadIds = getStoredDownloadInfo(DOWNLOAD_NOTIFICATION_IDS); |
+ String id = Integer.toString(downloadId); |
+ if (downloadIds.contains(id)) { |
+ downloadIds.remove(id); |
+ storeDownloadInfo(DOWNLOAD_NOTIFICATION_IDS, downloadIds); |
+ } |
+ } |
+ |
+ /** |
+ * Add download ID to SharedPrefs. |
+ * @param downloadId ID to be stored. |
+ */ |
+ private void addDownloadIdToSharedPrefs(int downloadId) { |
+ Set<String> downloadIds = getStoredDownloadInfo(DOWNLOAD_NOTIFICATION_IDS); |
+ downloadIds.add(Integer.toString(downloadId)); |
+ storeDownloadInfo(DOWNLOAD_NOTIFICATION_IDS, downloadIds); |
+ } |
+ |
+ /** |
+ * Add OMA download info to SharedPrefs. |
+ * @param omaInfo OMA download information to save. |
+ */ |
+ @VisibleForTesting |
+ protected void addOMADownloadToSharedPrefs(String omaInfo) { |
+ Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS); |
+ omaDownloads.add(omaInfo); |
+ storeDownloadInfo(PENDING_OMA_DOWNLOADS, omaDownloads); |
+ } |
+ |
+ /** |
+ * Remove OMA download info from SharedPrefs. |
+ * @param downloadId ID to be removed. |
+ */ |
+ private void removeOMADownloadFromSharedPrefs(long downloadId) { |
+ Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS); |
+ for (String omaDownload : omaDownloads) { |
+ OMAEntry entry = OMAEntry.parseOMAEntry(omaDownload); |
+ if (entry.mDownloadId == downloadId) { |
+ omaDownloads.remove(omaDownload); |
+ storeDownloadInfo(PENDING_OMA_DOWNLOADS, omaDownloads); |
+ return; |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Check if a download ID is in OMA SharedPrefs. |
+ * @param downloadId Download identifier to check. |
+ * @param true if it is in the SharedPrefs, or false otherwise. |
+ */ |
+ private boolean isDownloadIdInOMASharedPrefs(long downloadId) { |
+ Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS); |
+ for (String omaDownload : omaDownloads) { |
+ OMAEntry entry = OMAEntry.parseOMAEntry(omaDownload); |
+ if (entry.mDownloadId == downloadId) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ /** |
+ * Stores download information to shared preferences. The information can be |
+ * either pending download IDs, or pending OMA downloads. |
+ * |
+ * @param type Type of the information. |
+ * @param downloadInfo Information to be saved. |
+ */ |
+ private void storeDownloadInfo(String type, Set<String> downloadInfo) { |
+ SharedPreferences.Editor editor = mSharedPrefs.edit(); |
+ if (downloadInfo.isEmpty()) { |
+ editor.remove(type); |
+ } else { |
+ editor.putStringSet(type, downloadInfo); |
+ } |
+ editor.apply(); |
+ } |
+ |
+ /** |
+ * Updates notifications for all current downloads. Should not be called from UI thread. |
+ */ |
+ private void updateAllNotifications() { |
+ assert !ThreadUtils.runningOnUiThread(); |
+ for (DownloadProgress progress : mDownloadProgressMap.values()) { |
+ if (progress != null) { |
+ switch (progress.mDownloadStatus) { |
+ case COMPLETE: |
+ removeProgressNotificationForDownload(progress.mDownloadInfo |
+ .getDownloadId()); |
+ mDownloadNotifier.notifyDownloadSuccessful(progress.mDownloadInfo); |
+ broadcastDownloadSuccessful(progress.mDownloadInfo); |
+ break; |
+ case FAILED: |
+ removeProgressNotificationForDownload(progress.mDownloadInfo |
+ .getDownloadId()); |
+ mDownloadNotifier.notifyDownloadFailed(progress.mDownloadInfo); |
+ Log.w(TAG, "Download failed: " + progress.mDownloadInfo.getFilePath()); |
+ break; |
+ case IN_PROGRESS: |
+ mDownloadNotifier.notifyDownloadProgress(progress.mDownloadInfo, |
+ progress.mStartTimeInMillis); |
+ } |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Schedule an update if there is no update scheduled. |
+ */ |
+ private void scheduleUpdateIfNeeded() { |
+ if (mIsUIUpdateScheduled.compareAndSet(false, true)) { |
+ Runnable updateTask = new Runnable() { |
+ @Override |
+ public void run() { |
+ new AsyncTask<Void, Void, Void>() { |
+ @Override |
+ public Void doInBackground(Void... params) { |
+ updateAllNotifications(); |
+ return null; |
+ } |
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
+ mIsUIUpdateScheduled.set(false); |
+ } |
+ }; |
+ mHandler.postDelayed(updateTask, mUpdateDelayInMillis); |
+ } |
+ } |
+ |
+ /** |
+ * Cancel the progress notification of download and clear any cached information about this |
+ * download. |
+ * |
+ * @param downloadId Download Identifier. |
+ */ |
+ private void removeProgressNotificationForDownload(int downloadId) { |
+ mDownloadProgressMap.remove(downloadId); |
+ mDownloadNotifier.cancelNotification(downloadId); |
+ removeDownloadIdFromSharedPrefs(downloadId); |
+ } |
+ |
+ /** |
+ * Updates the progress of a download. |
+ * |
+ * @param downloadInfo Information about the download. |
+ * @param status Status of the download. |
+ */ |
+ private void updateDownloadProgress(DownloadInfo downloadInfo, DownloadStatus status) { |
+ assert downloadInfo.hasDownloadId(); |
+ int downloadId = downloadInfo.getDownloadId(); |
+ DownloadProgress progress = mDownloadProgressMap.get(downloadId); |
+ if (progress == null) { |
+ progress = new DownloadProgress(System.currentTimeMillis(), downloadInfo, |
+ status); |
+ if (status == DownloadStatus.IN_PROGRESS) { |
+ // A new in-progress download, add an entry to shared prefs to make sure |
+ // to clear the notification. |
+ addDownloadIdToSharedPrefs(downloadId); |
+ } |
+ mDownloadProgressMap.putIfAbsent(downloadId, progress); |
+ } else { |
+ progress.mDownloadStatus = status; |
+ progress.mDownloadInfo = downloadInfo; |
+ } |
+ } |
+ |
+ /** |
+ * Sets the download handler for OMA downloads, for testing purpose. |
+ * |
+ * @param omaDownloadHandler Download handler for OMA contents. |
+ */ |
+ @VisibleForTesting |
+ protected void setOMADownloadHandler(OMADownloadHandler omaDownloadHandler) { |
+ mOMADownloadHandler = omaDownloadHandler; |
+ } |
+ |
+ @Override |
+ public void onReceive(Context context, Intent intent) { |
+ String action = intent.getAction(); |
+ if (!DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) return; |
+ final DownloadManager manager = |
+ (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); |
+ |
+ long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); |
+ if (downloadId == -1) return; |
+ boolean isPendingOMADownload = mOMADownloadHandler.isPendingOMADownload(downloadId); |
+ boolean isInOMASharedPrefs = isDownloadIdInOMASharedPrefs(downloadId); |
+ if (isPendingOMADownload || isInOMASharedPrefs) { |
+ clearPendingOMADownload(downloadId, null); |
+ mPendingAutoOpenDownloads.remove(downloadId); |
+ } else if (mPendingAutoOpenDownloads.get(downloadId) != null) { |
+ Cursor c = manager.query(new DownloadManager.Query().setFilterById(downloadId)); |
+ int statusIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); |
+ while (c.moveToNext()) { |
+ int status = c.getInt(statusIndex); |
+ DownloadInfo info = mPendingAutoOpenDownloads.get(downloadId); |
+ switch (status) { |
+ case DownloadManager.STATUS_SUCCESSFUL: |
+ try { |
+ mPendingAutoOpenDownloads.remove(downloadId); |
+ if (OMADownloadHandler.OMA_DOWNLOAD_DESCRIPTOR_MIME.equalsIgnoreCase( |
+ info.getMimeType())) { |
+ mOMADownloadHandler.handleOMADownload( |
+ info, downloadId); |
+ manager.remove(downloadId); |
+ break; |
+ } |
+ Uri uri = manager.getUriForDownloadedFile(downloadId); |
+ Intent launchIntent = new Intent(Intent.ACTION_VIEW); |
+ |
+ launchIntent.setDataAndType( |
+ uri, manager.getMimeTypeForDownloadedFile(downloadId)); |
+ launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ |
+ mContext.startActivity(launchIntent); |
+ } catch (ActivityNotFoundException e) { |
+ Log.w(TAG, "Activity not found."); |
+ } |
+ break; |
+ case DownloadManager.STATUS_FAILED: |
+ mPendingAutoOpenDownloads.remove(downloadId); |
+ break; |
+ default: |
+ break; |
+ } |
+ } |
+ } |
+ |
+ if (mPendingAutoOpenDownloads.size() == 0) { |
+ mContext.unregisterReceiver(this); |
+ } |
+ } |
+ |
+ /** |
+ * Sends the download request to Android download manager. If |notifyCompleted| is true, |
+ * a notification will be sent to the user once download is complete and the downloaded |
+ * content will be saved to the public directory on external storage. Otherwise, the |
+ * download will be saved in the app directory and user will not get any notifications |
+ * after download completion. |
+ * |
+ * @param info Download information about the download. |
+ * @param notifyCompleted Whether to notify the user after Downloadmanager completes the |
+ * download. |
+ */ |
+ public void enqueueDownloadManagerRequest(final DownloadInfo info, boolean notifyCompleted) { |
+ EnqueueDownloadRequestTask task = new EnqueueDownloadRequestTask(info); |
+ task.execute(notifyCompleted); |
+ } |
+ |
+ /** |
+ * Async task to enqueue a download request into DownloadManager. |
+ */ |
+ private class EnqueueDownloadRequestTask extends |
+ AsyncTask<Boolean, Void, Boolean> { |
+ private int mErrorId; |
+ private long mDownloadId; |
+ private DownloadInfo mDownloadInfo; |
+ |
+ public EnqueueDownloadRequestTask(DownloadInfo downloadInfo) { |
+ mDownloadInfo = downloadInfo; |
+ } |
+ |
+ @Override |
+ public Boolean doInBackground(Boolean... booleans) { |
+ boolean notifyCompleted = booleans[0]; |
+ Uri uri = Uri.parse(mDownloadInfo.getUrl()); |
+ DownloadManager.Request request; |
+ try { |
+ request = new DownloadManager.Request(uri); |
+ } catch (IllegalArgumentException e) { |
+ mErrorId = R.string.cannot_download_http_or_https; |
+ return false; |
+ } |
+ |
+ request.setMimeType(mDownloadInfo.getMimeType()); |
+ try { |
+ if (notifyCompleted) { |
+ // Set downloaded file destination to /sdcard/Download or, should it be |
+ // set to one of several Environment.DIRECTORY* dirs depending on mimetype? |
+ request.setDestinationInExternalPublicDir( |
+ Environment.DIRECTORY_DOWNLOADS, mDownloadInfo.getFileName()); |
+ } else { |
+ File dir = new File(mContext.getExternalFilesDir(null), DOWNLOAD_DIRECTORY); |
+ if (dir.mkdir() || dir.isDirectory()) { |
+ File file = new File(dir, mDownloadInfo.getFileName()); |
+ request.setDestinationUri(Uri.fromFile(file)); |
+ } else { |
+ mErrorId = R.string.cannot_create_download_directory_title; |
+ return false; |
+ } |
+ } |
+ } catch (IllegalStateException e) { |
+ mErrorId = R.string.cannot_create_download_directory_title; |
+ return false; |
+ } |
+ |
+ if (notifyCompleted) { |
+ // Let this downloaded file be scanned by MediaScanner - so that it can |
+ // show up in Gallery app, for example. |
+ request.allowScanningByMediaScanner(); |
+ request.setNotificationVisibility( |
+ DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); |
+ } else { |
+ request.setNotificationVisibility( |
+ DownloadManager.Request.VISIBILITY_VISIBLE); |
+ } |
+ String description = mDownloadInfo.getDescription(); |
+ if (TextUtils.isEmpty(description)) { |
+ description = mDownloadInfo.getFileName(); |
+ } |
+ request.setDescription(description); |
+ request.addRequestHeader("Cookie", mDownloadInfo.getCookie()); |
+ request.addRequestHeader("Referer", mDownloadInfo.getReferer()); |
+ request.addRequestHeader("User-Agent", mDownloadInfo.getUserAgent()); |
+ |
+ DownloadManager manager = |
+ (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); |
+ try { |
+ mDownloadId = manager.enqueue(request); |
+ } catch (IllegalArgumentException e) { |
+ // See crbug.com/143499 for more details. |
+ Log.e(TAG, "Download failed: " + e); |
+ mErrorId = R.string.cannot_download_generic; |
+ return false; |
+ } |
+ return true; |
+ } |
+ |
+ @Override |
+ protected void onPostExecute(Boolean result) { |
+ boolean isPendingOMADownload = mOMADownloadHandler.isPendingOMADownload( |
+ (long) mDownloadInfo.getDownloadId()); |
+ if (!result) { |
+ Toast.makeText(mContext, mErrorId, Toast.LENGTH_SHORT).show(); |
+ if (isPendingOMADownload) { |
+ mOMADownloadHandler.onDownloadFailed( |
+ mDownloadInfo, DownloadManager.ERROR_UNKNOWN, null); |
+ } |
+ return; |
+ } |
+ Toast.makeText(mContext, R.string.download_pending, Toast.LENGTH_SHORT).show(); |
+ if (isPendingOMADownload) { |
+ // A new downloadId is generated, needs to update the OMADownloadHandler |
+ // about this. |
+ mDownloadInfo = mOMADownloadHandler.updateDownloadInfo( |
+ mDownloadInfo, mDownloadId); |
+ // TODO(qinmin): use a file instead of shared prefs to save the |
+ // OMA information in case chrome is killed. This will allow us to |
+ // save more information like cookies and user agent. |
+ String notifyUri = mOMADownloadHandler.getInstallNotifyInfo(mDownloadId); |
+ if (!TextUtils.isEmpty(notifyUri)) { |
+ OMAEntry entry = new OMAEntry(mDownloadId, notifyUri); |
+ addOMADownloadToSharedPrefs(entry.generateSharedPrefsString()); |
+ } |
+ } |
+ if (shouldOpenAfterDownload(mDownloadInfo) || isPendingOMADownload) { |
+ if (mPendingAutoOpenDownloads.size() == 0) { |
+ mContext.registerReceiver(DownloadManagerService.this, |
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); |
+ } |
+ mPendingAutoOpenDownloads.put(mDownloadId, mDownloadInfo); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Determines if the download should be immediately opened after |
+ * downloading. |
+ * |
+ * @param downloadInfo Information about the download. |
+ * @return true if the downloaded content should be opened, or false otherwise. |
+ */ |
+ @VisibleForTesting |
+ static boolean shouldOpenAfterDownload(DownloadInfo downloadInfo) { |
+ String type = downloadInfo.getMimeType(); |
+ return downloadInfo.hasUserGesture() |
+ && !isAttachment(downloadInfo.getContentDisposition()) |
+ && (type.equalsIgnoreCase("application/pdf") |
+ || type.equalsIgnoreCase(OMADownloadHandler.OMA_DOWNLOAD_DESCRIPTOR_MIME)); |
+ } |
+ |
+ /** |
+ * Returns true if the download meant to be treated as an attachment. |
+ * |
+ * @param contentDisposition Content disposition of the download. |
+ * @return true if the downloaded is an attachment, or false otherwise. |
+ */ |
+ public static boolean isAttachment(String contentDisposition) { |
+ return contentDisposition != null |
+ && contentDisposition.regionMatches(true, 0, "attachment", 0, 10); |
+ } |
+} |