* The downloaded file is not scanned by MediaScanner. * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. *
* By default, downloads are saved to a generated filename in the shared download cache and * may be deleted by the system at any time to reclaim space. * * @return this object */ public Request setDestinationUri(Uri uri) { mDestinationUri = uri; return this; } /** * Set the local destination for the downloaded file to the system cache dir (/cache). * This is only available to System apps with the permission * {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}. *
* The downloaded file is not scanned by MediaScanner. * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. *
* Files downloaded to /cache may be deleted by the system at any time to reclaim space. * * @return this object * @hide */ public Request setDestinationToSystemCache() { mUseSystemCache = true; return this; } /** * Set the local destination for the downloaded file to a path within the application's * external files directory (as returned by {@link Context#getExternalFilesDir(String)}. *
* The downloaded file is not scanned by MediaScanner. * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. * * @param context the {@link Context} to use in determining the external files directory * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)} * @param subPath the path within the external directory, including the destination filename * @return this object */ public Request setDestinationInExternalFilesDir(Context context, String dirType, String subPath) { setDestinationFromBase(context.getExternalFilesDir(dirType), subPath); return this; } /** * Set the local destination for the downloaded file to a path within the public external * storage directory (as returned by * {@link Environment#getExternalStoragePublicDirectory(String)}. *
* The downloaded file is not scanned by MediaScanner. * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. * * @param dirType the directory type to pass to * {@link Environment#getExternalStoragePublicDirectory(String)} * @param subPath the path within the external directory, including the destination filename * @return this object */ public Request setDestinationInExternalPublicDir(String dirType, String subPath) { File file = Environment.getExternalStoragePublicDirectory(dirType); if (file.exists()) { if (!file.isDirectory()) { throw new IllegalStateException(file.getAbsolutePath() + " already exists and is not a directory"); } } else { if (!file.mkdir()) { throw new IllegalStateException("Unable to create directory: "+ file.getAbsolutePath()); } } setDestinationFromBase(file, subPath); return this; } private void setDestinationFromBase(File base, String subPath) { if (subPath == null) { throw new NullPointerException("subPath cannot be null"); } mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath); } /** * If the file to be downloaded is to be scanned by MediaScanner, this method * should be called before {@link DownloadManager#enqueue(Request)} is called. */ public void allowScanningByMediaScanner() { mScannable = true; } /** * Add an HTTP header to be included with the download request. The header will be added to * the end of the list. * @param header HTTP header name * @param value header value * @return this object * @see HTTP/1.1 * Message Headers */ public Request addRequestHeader(String header, String value) { if (header == null) { throw new NullPointerException("header cannot be null"); } if (header.contains(":")) { throw new IllegalArgumentException("header may not contain ':'"); } if (value == null) { value = ""; } mRequestHeaders.add(Pair.create(header, value)); return this; } /** * Set the title of this download, to be displayed in notifications (if enabled). If no * title is given, a default one will be assigned based on the download filename, once the * download starts. * @return this object */ public Request setTitle(CharSequence title) { mTitle = title; return this; } /** * Set a description of this download, to be displayed in notifications (if enabled) * @return this object */ public Request setDescription(CharSequence description) { mDescription = description; return this; } /** * Set the MIME content type of this download. This will override the content type declared * in the server's response. * @see HTTP/1.1 * Media Types * @return this object */ public Request setMimeType(String mimeType) { mMimeType = mimeType; return this; } /** * Control whether a system notification is posted by the download manager while this * download is running. If enabled, the download manager posts notifications about downloads * through the system {@link android.app.NotificationManager}. By default, a notification is * shown. * * If set to false, this requires the permission * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. * * @param show whether the download manager should show a notification for this download. * @return this object * @deprecated use {@link #setNotificationVisibility(int)} */ @Deprecated public Request setShowRunningNotification(boolean show) { return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) : setNotificationVisibility(VISIBILITY_HIDDEN); } /** * Control whether a system notification is posted by the download manager while this * download is running or when it is completed. * If enabled, the download manager posts notifications about downloads * through the system {@link android.app.NotificationManager}. * By default, a notification is shown only when the download is in progress. *
* It can take the following values: {@link #VISIBILITY_HIDDEN}, * {@link #VISIBILITY_VISIBLE}, * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}. *
* If set to {@link #VISIBILITY_HIDDEN}, this requires the permission
* android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
*
* @param visibility the visibility setting value
* @return this object
*/
public Request setNotificationVisibility(int visibility) {
mNotificationVisibility = visibility;
return this;
}
/**
* Restrict the types of networks over which this download may proceed.
* By default, all network types are allowed. Consider using
* {@link #setAllowedOverMetered(boolean)} instead, since it's more
* flexible.
*
* @param flags any combination of the NETWORK_* bit flags.
* @return this object
*/
public Request setAllowedNetworkTypes(int flags) {
mAllowedNetworkTypes = flags;
return this;
}
/**
* Set whether this download may proceed over a roaming connection. By default, roaming is
* allowed.
* @param allowed whether to allow a roaming connection to be used
* @return this object
*/
public Request setAllowedOverRoaming(boolean allowed) {
mRoamingAllowed = allowed;
return this;
}
/**
* Set whether this download may proceed over a metered network
* connection. By default, metered networks are allowed.
*
* @see ConnectivityManager#isActiveNetworkMetered()
*/
public Request setAllowedOverMetered(boolean allow) {
mMeteredAllowed = allow;
return this;
}
/**
* Set whether this download should be displayed in the system's Downloads UI. True by
* default.
* @param isVisible whether to display this download in the Downloads UI
* @return this object
*/
public Request setVisibleInDownloadsUi(boolean isVisible) {
mIsVisibleInDownloadsUi = isVisible;
return this;
}
/**
* @return ContentValues to be passed to DownloadProvider.insert()
*/
ContentValues toContentValues(String packageName) {
ContentValues values = new ContentValues();
assert mUri != null;
values.put(Downloads.Impl.COLUMN_URI, mUri.toString());
values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName);
if (mDestinationUri != null) {
values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
} else {
values.put(Downloads.Impl.COLUMN_DESTINATION,
(this.mUseSystemCache) ?
Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION :
Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
}
// is the file supposed to be media-scannable?
values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES :
SCANNABLE_VALUE_NO);
if (!mRequestHeaders.isEmpty()) {
encodeHttpHeaders(values);
}
putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed);
values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
return values;
}
private void encodeHttpHeaders(ContentValues values) {
int index = 0;
for (Pair
* If the specified downloaded file is in external storage (for example, /sdcard dir),
* then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds
* to the filepath on sdcard.
*
* @param id the id of the downloaded file.
* @return the {@link Uri} for the given downloaded file id, if download was successful. null
* otherwise.
*/
public Uri getUriForDownloadedFile(long id) {
// to check if the file is in cache, get its destination from the database
Query query = new Query().setFilterById(id);
Cursor cursor = null;
try {
cursor = query(query);
if (cursor == null) {
return null;
}
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
if (DownloadManager.STATUS_SUCCESSFUL == status) {
int indx = cursor.getColumnIndexOrThrow(
Downloads.Impl.COLUMN_DESTINATION);
int destination = cursor.getInt(indx);
// TODO: if we ever add API to DownloadManager to let the caller specify
// non-external storage for a downloaded file, then the following code
// should also check for that destination.
if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION ||
destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION ||
destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING ||
destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) {
// return private uri
return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id);
} else {
// return public uri
String path = cursor.getString(
cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME));
return Uri.fromFile(new File(path));
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
// downloaded file not found or its status is not 'successfully completed'
return null;
}
/**
* Returns {@link Uri} for the given downloaded file id, if the file is
* downloaded successfully. otherwise, null is returned.
*
* If the specified downloaded file is in external storage (for example, /sdcard dir),
* then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds
* to the filepath on sdcard.
*
* @param id the id of the downloaded file.
* @return the {@link Uri} for the given downloaded file id, if download was successful. null
* otherwise.
*/
public String getMimeTypeForDownloadedFile(long id) {
Query query = new Query().setFilterById(id);
Cursor cursor = null;
try {
cursor = query(query);
if (cursor == null) {
return null;
}
while (cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
// downloaded file not found or its status is not 'successfully completed'
return null;
}
/**
* Restart the given downloads, which must have already completed (successfully or not). This
* method will only work when called from within the download manager's process.
* @param ids the IDs of the downloads
* @hide
*/
public void restartDownload(long... ids) {
Cursor cursor = query(new Query().setFilterById(ids));
try {
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
throw new IllegalArgumentException("Cannot restart incomplete download: "
+ cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
}
}
} finally {
cursor.close();
}
ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
values.putNull(Downloads.Impl._DATA);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
}
/**
* Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
* there's no limit
*
* @param context the {@link Context} to use for accessing the {@link ContentResolver}
* @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if
* there's no limit
*/
public static Long getMaxBytesOverMobile(Context context) {
try {
return Settings.Secure.getLong(context.getContentResolver(),
Settings.Secure.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
} catch (SettingNotFoundException exc) {
return null;
}
}
/**
* Returns recommended maximum size, in bytes, of downloads that may go over a mobile
* connection; or null if there's no recommended limit. The user will have the option to bypass
* this limit.
*
* @param context the {@link Context} to use for accessing the {@link ContentResolver}
* @return recommended maximum size, in bytes, of downloads that may go over a mobile
* connection; or null if there's no recommended limit.
*/
public static Long getRecommendedMaxBytesOverMobile(Context context) {
try {
return Settings.Secure.getLong(context.getContentResolver(),
Settings.Secure.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
} catch (SettingNotFoundException exc) {
return null;
}
}
/** {@hide} */
public static boolean isActiveNetworkExpensive(Context context) {
// TODO: connect to NetworkPolicyManager
return false;
}
/** {@hide} */
public static long getActiveNetworkWarningBytes(Context context) {
// TODO: connect to NetworkPolicyManager
return -1;
}
/**
* Adds a file to the downloads database system, so it could appear in Downloads App
* (and thus become eligible for management by the Downloads App).
*
* It is helpful to make the file scannable by MediaScanner by setting the param
* isMediaScannerScannable to true. It makes the file visible in media managing
* applications such as Gallery App, which could be a useful purpose of using this API.
*
* @param title the title that would appear for this file in Downloads App.
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
* Gallery app).
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
* Gallery app to display the file, if the file contents represent a video/image).
* @param length length of the downloaded file
* @param showNotification true if a notification is to be sent, false otherwise
* @return an ID for the download entry added to the downloads app, unique across the system
* This ID is used to make future calls related to this download.
*/
public long addCompletedDownload(String title, String description,
boolean isMediaScannerScannable, String mimeType, String path, long length,
boolean showNotification) {
// make sure the input args are non-null/non-zero
validateArgumentIsNonEmpty("title", title);
validateArgumentIsNonEmpty("description", description);
validateArgumentIsNonEmpty("path", path);
validateArgumentIsNonEmpty("mimeType", mimeType);
if (length < 0) {
throw new IllegalArgumentException(" invalid value for param: totalBytes");
}
// if there is already an entry with the given path name in downloads.db, return its id
Request request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD)
.setTitle(title)
.setDescription(description)
.setMimeType(mimeType);
ContentValues values = request.toContentValues(null);
values.put(Downloads.Impl.COLUMN_DESTINATION,
Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
values.put(Downloads.Impl._DATA, path);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
(isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
Request.SCANNABLE_VALUE_NO);
values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
if (downloadUri == null) {
return -1;
}
return Long.parseLong(downloadUri.getLastPathSegment());
}
private static final String NON_DOWNLOADMANAGER_DOWNLOAD =
"non-dwnldmngr-download-dont-retry2download";
private static void validateArgumentIsNonEmpty(String paramName, String val) {
if (TextUtils.isEmpty(val)) {
throw new IllegalArgumentException(paramName + " can't be null");
}
}
/**
* Get the DownloadProvider URI for the download with the given ID.
*/
Uri getDownloadUri(long id) {
return ContentUris.withAppendedId(mBaseUri, id);
}
/**
* Get a parameterized SQL WHERE clause to select a bunch of IDs.
*/
static String getWhereClauseForIds(long[] ids) {
StringBuilder whereClause = new StringBuilder();
whereClause.append("(");
for (int i = 0; i < ids.length; i++) {
if (i > 0) {
whereClause.append("OR ");
}
whereClause.append(Downloads.Impl._ID);
whereClause.append(" = ? ");
}
whereClause.append(")");
return whereClause.toString();
}
/**
* Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
*/
static String[] getWhereArgsForIds(long[] ids) {
String[] whereArgs = new String[ids.length];
for (int i = 0; i < ids.length; i++) {
whereArgs[i] = Long.toString(ids[i]);
}
return whereArgs;
}
/**
* This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
* presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
* Some columns correspond directly to underlying values while others are computed from
* underlying data.
*/
private static class CursorTranslator extends CursorWrapper {
private Uri mBaseUri;
public CursorTranslator(Cursor cursor, Uri baseUri) {
super(cursor);
mBaseUri = baseUri;
}
@Override
public int getInt(int columnIndex) {
return (int) getLong(columnIndex);
}
@Override
public long getLong(int columnIndex) {
if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
} else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
} else {
return super.getLong(columnIndex);
}
}
@Override
public String getString(int columnIndex) {
return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() :
super.getString(columnIndex);
}
private String getLocalUri() {
long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
String localPath = getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
if (localPath == null) {
return null;
}
return Uri.fromFile(new File(localPath)).toString();
}
// return content URI for cache download
long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
}
private long getReason(int status) {
switch (translateStatus(status)) {
case STATUS_FAILED:
return getErrorCode(status);
case STATUS_PAUSED:
return getPausedReason(status);
default:
return 0; // arbitrary value when status is not an error
}
}
private long getPausedReason(int status) {
switch (status) {
case Downloads.Impl.STATUS_WAITING_TO_RETRY:
return PAUSED_WAITING_TO_RETRY;
case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
return PAUSED_WAITING_FOR_NETWORK;
case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
return PAUSED_QUEUED_FOR_WIFI;
default:
return PAUSED_UNKNOWN;
}
}
private long getErrorCode(int status) {
if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
|| (500 <= status && status < 600)) {
// HTTP status code
return status;
}
switch (status) {
case Downloads.Impl.STATUS_FILE_ERROR:
return ERROR_FILE_ERROR;
case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
return ERROR_UNHANDLED_HTTP_CODE;
case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
return ERROR_HTTP_DATA_ERROR;
case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
return ERROR_TOO_MANY_REDIRECTS;
case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
return ERROR_INSUFFICIENT_SPACE;
case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
return ERROR_DEVICE_NOT_FOUND;
case Downloads.Impl.STATUS_CANNOT_RESUME:
return ERROR_CANNOT_RESUME;
case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
return ERROR_FILE_ALREADY_EXISTS;
default:
return ERROR_UNKNOWN;
}
}
private int translateStatus(int status) {
switch (status) {
case Downloads.Impl.STATUS_PENDING:
return STATUS_PENDING;
case Downloads.Impl.STATUS_RUNNING:
return STATUS_RUNNING;
case Downloads.Impl.STATUS_PAUSED_BY_APP:
case Downloads.Impl.STATUS_WAITING_TO_RETRY:
case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
return STATUS_PAUSED;
case Downloads.Impl.STATUS_SUCCESS:
return STATUS_SUCCESSFUL;
default:
assert Downloads.Impl.isStatusError(status);
return STATUS_FAILED;
}
}
}
}