// Copyright 2016 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.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.support.v4.app.NotificationManagerCompat; import org.chromium.base.ContextUtils; import org.chromium.base.Log; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * A wrapper for Android DownloadManager to provide utility functions. */ public class DownloadManagerDelegate { private static final String TAG = "DownloadDelegate"; private static final long INVALID_SYSTEM_DOWNLOAD_ID = -1; private static final String DOWNLOAD_ID_MAPPINGS_FILE_NAME = "download_id_mappings"; protected final Context mContext; public DownloadManagerDelegate(Context context) { mContext = context; } /** * Inserts a new download ID mapping into the SharedPreferences * @param downloadId system download ID from Android DownloadManager. * @param downloadGuid Download GUID. */ private void addDownloadIdMapping(long downloadId, String downloadGuid) { SharedPreferences sharedPrefs = getSharedPreferences(); SharedPreferences.Editor editor = sharedPrefs.edit(); editor.putLong(downloadGuid, downloadId); editor.apply(); } /** * Removes a download Id mapping from the SharedPreferences given the download GUID. * @param guid Download GUID. * @return the Android DownloadManager's download ID that is removed, or * INVALID_SYSTEM_DOWNLOAD_ID if it is not found. */ private long removeDownloadIdMapping(String downloadGuid) { SharedPreferences sharedPrefs = getSharedPreferences(); long downloadId = sharedPrefs.getLong(downloadGuid, INVALID_SYSTEM_DOWNLOAD_ID); if (downloadId != INVALID_SYSTEM_DOWNLOAD_ID) { SharedPreferences.Editor editor = sharedPrefs.edit(); editor.remove(downloadGuid); editor.apply(); } return downloadId; } /** * Lazily retrieve the SharedPreferences when needed. Since download operations are not very * frequent, no need to load all SharedPreference entries into a hashmap in the memory. * @return the SharedPreferences instance. */ private SharedPreferences getSharedPreferences() { return ContextUtils.getApplicationContext().getSharedPreferences( DOWNLOAD_ID_MAPPINGS_FILE_NAME, Context.MODE_PRIVATE); } /** * @see android.app.DownloadManager#addCompletedDownload(String, String, boolean, String, * String, long, boolean) */ protected long addCompletedDownload(String fileName, String description, String mimeType, String path, long length, String originalUrl, String referer, String downloadGuid) { DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext); boolean useSystemNotification = !notificationManager.areNotificationsEnabled(); long downloadId = -1; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { Class<?> c = manager.getClass(); try { Class[] args = {String.class, String.class, boolean.class, String.class, String.class, long.class, boolean.class, Uri.class, Uri.class}; Method method = c.getMethod("addCompletedDownload", args); Uri originalUri = Uri.parse(originalUrl); Uri refererUri = referer == null ? Uri.EMPTY : Uri.parse(referer); downloadId = (Long) method.invoke(manager, fileName, description, true, mimeType, path, length, useSystemNotification, originalUri, refererUri); } catch (SecurityException e) { Log.e(TAG, "Cannot access the needed method."); } catch (NoSuchMethodException e) { Log.e(TAG, "Cannot find the needed method."); } catch (InvocationTargetException e) { Log.e(TAG, "Error calling the needed method."); } catch (IllegalAccessException e) { Log.e(TAG, "Error accessing the needed method."); } } else { downloadId = manager.addCompletedDownload(fileName, description, true, mimeType, path, length, useSystemNotification); } addDownloadIdMapping(downloadId, downloadGuid); return downloadId; } /** * Removes a download from Android DownloadManager. * @param downloadGuid The GUID of the download. */ void removeCompletedDownload(String downloadGuid) { long downloadId = removeDownloadIdMapping(downloadGuid); if (downloadId != INVALID_SYSTEM_DOWNLOAD_ID) { DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); manager.remove(downloadId); } } /** * Interface for returning the query result when it completes. */ public interface DownloadQueryCallback { /** * Callback function to return query result. * @param result Query result from android DownloadManager. * @param showNotifications Whether to show status notifications. */ public void onQueryCompleted(DownloadQueryResult result, boolean showNotifications); } /** * Result for querying the Android DownloadManager. */ static class DownloadQueryResult { public final DownloadItem item; public final int downloadStatus; public final long downloadTimeInMilliseconds; public final long bytesDownloaded; public final boolean canResolve; public final int failureReason; DownloadQueryResult(DownloadItem item, int downloadStatus, long downloadTimeInMilliseconds, long bytesDownloaded, boolean canResolve, int failureReason) { this.item = item; this.downloadStatus = downloadStatus; this.downloadTimeInMilliseconds = downloadTimeInMilliseconds; this.canResolve = canResolve; this.bytesDownloaded = bytesDownloaded; this.failureReason = failureReason; } } /** * Query the Android DownloadManager for download status. * @param downloadItem Download item to query. * @param showNotifications Whether to show status notifications. * @param callback Callback to be notified when query completes. */ void queryDownloadResult( DownloadItem downloadItem, boolean showNotifications, DownloadQueryCallback callback) { DownloadQueryTask task = new DownloadQueryTask(downloadItem, showNotifications, callback); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } /** * Async task to query download status from Android DownloadManager */ private class DownloadQueryTask extends AsyncTask<Void, Void, DownloadQueryResult> { private final DownloadItem mDownloadItem; private final boolean mShowNotifications; private final DownloadQueryCallback mCallback; public DownloadQueryTask(DownloadItem downloadItem, boolean showNotifications, DownloadQueryCallback callback) { mDownloadItem = downloadItem; mShowNotifications = showNotifications; mCallback = callback; } @Override public DownloadQueryResult doInBackground(Void... voids) { DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); Cursor c = manager.query( new DownloadManager.Query().setFilterById(mDownloadItem.getSystemDownloadId())); if (c == null) { return new DownloadQueryResult(mDownloadItem, DownloadManagerService.DOWNLOAD_STATUS_CANCELLED, 0, 0, false, 0); } long bytesDownloaded = 0; boolean canResolve = false; int downloadStatus = DownloadManagerService.DOWNLOAD_STATUS_IN_PROGRESS; int failureReason = 0; long lastModifiedTime = 0; if (c.moveToNext()) { int statusIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); if (status == DownloadManager.STATUS_SUCCESSFUL) { downloadStatus = DownloadManagerService.DOWNLOAD_STATUS_COMPLETE; if (mShowNotifications) { canResolve = DownloadManagerService.isOMADownloadDescription( mDownloadItem.getDownloadInfo()) || DownloadManagerService.canResolveDownloadItem( mContext, mDownloadItem, false); } } else if (status == DownloadManager.STATUS_FAILED) { downloadStatus = DownloadManagerService.DOWNLOAD_STATUS_FAILED; failureReason = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON)); } lastModifiedTime = c.getLong(c.getColumnIndex(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP)); bytesDownloaded = c.getLong(c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); } else { downloadStatus = DownloadManagerService.DOWNLOAD_STATUS_CANCELLED; } c.close(); long totalTime = Math.max(0, lastModifiedTime - mDownloadItem.getStartTime()); return new DownloadQueryResult(mDownloadItem, downloadStatus, totalTime, bytesDownloaded, canResolve, failureReason); } @Override protected void onPostExecute(DownloadQueryResult result) { mCallback.onQueryCompleted(result, mShowNotifications); } } }