2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 import android.annotation.Nullable;
20 import android.annotation.SdkConstant;
21 import android.annotation.SdkConstant.SdkConstantType;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.database.Cursor;
28 import android.database.CursorWrapper;
29 import android.net.ConnectivityManager;
30 import android.net.NetworkPolicyManager;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Environment;
34 import android.os.FileUtils;
35 import android.os.ParcelFileDescriptor;
36 import android.provider.Downloads;
37 import android.provider.Settings;
38 import android.provider.MediaStore.Images;
39 import android.provider.Settings.SettingNotFoundException;
40 import android.text.TextUtils;
41 import android.util.Pair;
44 import java.io.FileNotFoundException;
45 import java.util.ArrayList;
46 import java.util.List;
49 * The download manager is a system service that handles long-running HTTP downloads. Clients may
50 * request that a URI be downloaded to a particular destination file. The download manager will
51 * conduct the download in the background, taking care of HTTP interactions and retrying downloads
52 * after failures or across connectivity changes and system reboots.
54 * Instances of this class should be obtained through
55 * {@link android.content.Context#getSystemService(String)} by passing
56 * {@link android.content.Context#DOWNLOAD_SERVICE}.
58 * Apps that request downloads through this API should register a broadcast receiver for
59 * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
60 * download in a notification or from the downloads UI.
62 * Note that the application must have the {@link android.Manifest.permission#INTERNET}
63 * permission to use this class.
65 public class DownloadManager {
68 * An identifier for a particular download, unique across the system. Clients use this ID to
69 * make subsequent calls related to the download.
71 public final static String COLUMN_ID = Downloads.Impl._ID;
74 * The client-supplied title for this download. This will be displayed in system notifications.
75 * Defaults to the empty string.
77 public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE;
80 * The client-supplied description of this download. This will be displayed in system
81 * notifications. Defaults to the empty string.
83 public final static String COLUMN_DESCRIPTION = Downloads.Impl.COLUMN_DESCRIPTION;
86 * URI to be downloaded.
88 public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI;
91 * Internet Media Type of the downloaded file. If no value is provided upon creation, this will
92 * initially be null and will be filled in based on the server's response once the download has
95 * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
97 public final static String COLUMN_MEDIA_TYPE = "media_type";
100 * Total size of the download in bytes. This will initially be -1 and will be filled in once
101 * the download starts.
103 public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
106 * Uri where downloaded file will be stored. If a destination is supplied by client, that URI
107 * will be used here. Otherwise, the value will initially be null and will be filled in with a
108 * generated URI once the download has started.
110 public final static String COLUMN_LOCAL_URI = "local_uri";
113 * Path to the downloaded file on disk.
115 * Note that apps may not have filesystem permissions to directly access
116 * this path. Instead of trying to open this path directly, apps should use
117 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain access.
119 * @deprecated apps should transition to using
120 * {@link ContentResolver#openFileDescriptor(Uri, String)}
124 public final static String COLUMN_LOCAL_FILENAME = "local_filename";
127 * Current status of the download, as one of the STATUS_* constants.
129 public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
132 public static final String COLUMN_FILE_NAME_HINT = Downloads.Impl.COLUMN_FILE_NAME_HINT;
135 * Provides more detail on the status of the download. Its meaning depends on the value of
136 * {@link #COLUMN_STATUS}.
138 * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
139 * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
140 * 2616. Otherwise, it will hold one of the ERROR_* constants.
142 * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
143 * paused. It will hold one of the PAUSED_* constants.
145 * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
146 * column's value is undefined.
148 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
151 public final static String COLUMN_REASON = "reason";
154 * Number of bytes download so far.
156 public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
159 * Timestamp when the download was last modified, in {@link System#currentTimeMillis
160 * System.currentTimeMillis()} (wall clock time in UTC).
162 public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
165 * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
166 * used to delete the entries from MediaProvider database when it is deleted from the
169 public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI;
172 public static final String COLUMN_DESTINATION = Downloads.Impl.COLUMN_DESTINATION;
177 public final static String COLUMN_ALLOW_WRITE = Downloads.Impl.COLUMN_ALLOW_WRITE;
180 * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
182 public final static int STATUS_PENDING = 1 << 0;
185 * Value of {@link #COLUMN_STATUS} when the download is currently running.
187 public final static int STATUS_RUNNING = 1 << 1;
190 * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume.
192 public final static int STATUS_PAUSED = 1 << 2;
195 * Value of {@link #COLUMN_STATUS} when the download has successfully completed.
197 public final static int STATUS_SUCCESSFUL = 1 << 3;
200 * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried).
202 public final static int STATUS_FAILED = 1 << 4;
205 * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit
206 * under any other error code.
208 public final static int ERROR_UNKNOWN = 1000;
211 * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
212 * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
213 * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
215 public final static int ERROR_FILE_ERROR = 1001;
218 * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
221 public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
224 * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
227 public final static int ERROR_HTTP_DATA_ERROR = 1004;
230 * Value of {@link #COLUMN_REASON} when there were too many redirects.
232 public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
235 * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
236 * this is because the SD card is full.
238 public final static int ERROR_INSUFFICIENT_SPACE = 1006;
241 * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
242 * this is because the SD card is not mounted.
244 public final static int ERROR_DEVICE_NOT_FOUND = 1007;
247 * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
248 * resume the download.
250 public final static int ERROR_CANNOT_RESUME = 1008;
253 * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
254 * download manager will not overwrite an existing file).
256 public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
259 * Value of {@link #COLUMN_REASON} when the download has failed because of
260 * {@link NetworkPolicyManager} controls on the requesting application.
264 public final static int ERROR_BLOCKED = 1010;
267 * Value of {@link #COLUMN_REASON} when the download is paused because some network error
268 * occurred and the download manager is waiting before retrying the request.
270 public final static int PAUSED_WAITING_TO_RETRY = 1;
273 * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
276 public final static int PAUSED_WAITING_FOR_NETWORK = 2;
279 * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
280 * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
282 public final static int PAUSED_QUEUED_FOR_WIFI = 3;
285 * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
287 public final static int PAUSED_UNKNOWN = 4;
290 * Value of {@link #COLUMN_REASON} when the download is paused manually.
294 public final static int PAUSED_MANUAL = 5;
297 * Broadcast intent action sent by the download manager when a download completes.
299 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
300 public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
303 * Broadcast intent action sent by the download manager when the user clicks on a running
304 * download, either from a system notification or from the downloads UI.
306 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
307 public final static String ACTION_NOTIFICATION_CLICKED =
308 "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
311 * Intent action to launch an activity to display all downloads.
313 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
314 public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
317 * Intent extra included with {@link #ACTION_VIEW_DOWNLOADS} to start DownloadApp in
320 public final static String INTENT_EXTRAS_SORT_BY_SIZE =
321 "android.app.DownloadManager.extra_sortBySize";
324 * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
325 * long) of the download that just completed.
327 public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
330 * When clicks on multiple notifications are received, the following
331 * provides an array of download ids corresponding to the download notification that was
332 * clicked. It can be retrieved by the receiver of this
333 * Intent using {@link android.content.Intent#getLongArrayExtra(String)}.
335 public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids";
338 * columns to request from DownloadProvider.
341 public static final String[] UNDERLYING_COLUMNS = new String[] {
342 DownloadManager.COLUMN_ID,
343 DownloadManager.COLUMN_LOCAL_FILENAME,
344 DownloadManager.COLUMN_MEDIAPROVIDER_URI,
345 DownloadManager.COLUMN_DESTINATION,
346 DownloadManager.COLUMN_TITLE,
347 DownloadManager.COLUMN_DESCRIPTION,
348 DownloadManager.COLUMN_URI,
349 DownloadManager.COLUMN_STATUS,
350 DownloadManager.COLUMN_FILE_NAME_HINT,
351 DownloadManager.COLUMN_MEDIA_TYPE,
352 DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
353 DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP,
354 DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR,
355 DownloadManager.COLUMN_ALLOW_WRITE,
356 DownloadManager.COLUMN_LOCAL_URI,
357 DownloadManager.COLUMN_REASON
361 * This class contains all the information necessary to request a new download. The URI is the
362 * only required parameter.
364 * Note that the default download destination is a shared volume where the system might delete
365 * your file if it needs to reclaim space for system use. If this is a problem, use a location
366 * on external storage (see {@link #setDestinationUri(Uri)}.
368 public static class Request {
370 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
371 * {@link ConnectivityManager#TYPE_MOBILE}.
373 public static final int NETWORK_MOBILE = 1 << 0;
376 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
377 * {@link ConnectivityManager#TYPE_WIFI}.
379 public static final int NETWORK_WIFI = 1 << 1;
382 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
383 * {@link ConnectivityManager#TYPE_BLUETOOTH}.
387 public static final int NETWORK_BLUETOOTH = 1 << 2;
390 private Uri mDestinationUri;
391 private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
392 private CharSequence mTitle;
393 private CharSequence mDescription;
394 private String mMimeType;
395 private int mAllowedNetworkTypes = ~0; // default to all network types allowed
396 private boolean mRoamingAllowed = true;
397 private boolean mMeteredAllowed = true;
398 private int mFlags = 0;
399 private boolean mIsVisibleInDownloadsUi = true;
400 private boolean mScannable = false;
401 private boolean mUseSystemCache = false;
402 /** if a file is designated as a MediaScanner scannable file, the following value is
403 * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
405 private static final int SCANNABLE_VALUE_YES = 0;
406 // value of 1 is stored in the above column by DownloadProvider after it is scanned by
408 /** if a file is designated as a file that should not be scanned by MediaScanner,
409 * the following value is stored in the database column
410 * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
412 private static final int SCANNABLE_VALUE_NO = 2;
415 * This download is visible but only shows in the notifications
416 * while it's in progress.
418 public static final int VISIBILITY_VISIBLE = 0;
421 * This download is visible and shows in the notifications while
422 * in progress and after completion.
424 public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
427 * This download doesn't show in the UI or in the notifications.
429 public static final int VISIBILITY_HIDDEN = 2;
432 * This download shows in the notifications after completion ONLY.
433 * It is usuable only with
434 * {@link DownloadManager#addCompletedDownload(String, String,
435 * boolean, String, String, long, boolean)}.
437 public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3;
439 /** can take any of the following values: {@link #VISIBILITY_HIDDEN}
440 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE},
441 * {@link #VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION}
443 private int mNotificationVisibility = VISIBILITY_VISIBLE;
446 * @param uri the HTTP or HTTPS URI to download.
448 public Request(Uri uri) {
450 throw new NullPointerException();
452 String scheme = uri.getScheme();
453 if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
454 throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
459 Request(String uriString) {
460 mUri = Uri.parse(uriString);
464 * Set the local destination for the downloaded file. Must be a file URI to a path on
465 * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE
468 * The downloaded file is not scanned by MediaScanner.
469 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
471 * By default, downloads are saved to a generated filename in the shared download cache and
472 * may be deleted by the system at any time to reclaim space.
474 * @return this object
476 public Request setDestinationUri(Uri uri) {
477 mDestinationUri = uri;
482 * Set the local destination for the downloaded file to the system cache dir (/cache).
483 * This is only available to System apps with the permission
484 * {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}.
486 * The downloaded file is not scanned by MediaScanner.
487 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
489 * Files downloaded to /cache may be deleted by the system at any time to reclaim space.
491 * @return this object
494 public Request setDestinationToSystemCache() {
495 mUseSystemCache = true;
500 * Set the local destination for the downloaded file to a path within
501 * the application's external files directory (as returned by
502 * {@link Context#getExternalFilesDir(String)}.
504 * The downloaded file is not scanned by MediaScanner. But it can be
505 * made scannable by calling {@link #allowScanningByMediaScanner()}.
507 * @param context the {@link Context} to use in determining the external
509 * @param dirType the directory type to pass to
510 * {@link Context#getExternalFilesDir(String)}
511 * @param subPath the path within the external directory, including the
512 * destination filename
513 * @return this object
514 * @throws IllegalStateException If the external storage directory
515 * cannot be found or created.
517 public Request setDestinationInExternalFilesDir(Context context, String dirType,
519 final File file = context.getExternalFilesDir(dirType);
521 throw new IllegalStateException("Failed to get external storage files directory");
522 } else if (file.exists()) {
523 if (!file.isDirectory()) {
524 throw new IllegalStateException(file.getAbsolutePath() +
525 " already exists and is not a directory");
528 if (!file.mkdirs()) {
529 throw new IllegalStateException("Unable to create directory: "+
530 file.getAbsolutePath());
533 setDestinationFromBase(file, subPath);
538 * Set the local destination for the downloaded file to a path within
539 * the public external storage directory (as returned by
540 * {@link Environment#getExternalStoragePublicDirectory(String)}).
542 * The downloaded file is not scanned by MediaScanner. But it can be
543 * made scannable by calling {@link #allowScanningByMediaScanner()}.
545 * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)}
546 * @param subPath the path within the external directory, including the
547 * destination filename
548 * @return this object
549 * @throws IllegalStateException If the external storage directory
550 * cannot be found or created.
552 public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
553 File file = Environment.getExternalStoragePublicDirectory(dirType);
555 throw new IllegalStateException("Failed to get external storage public directory");
556 } else if (file.exists()) {
557 if (!file.isDirectory()) {
558 throw new IllegalStateException(file.getAbsolutePath() +
559 " already exists and is not a directory");
562 if (!file.mkdirs()) {
563 throw new IllegalStateException("Unable to create directory: "+
564 file.getAbsolutePath());
567 setDestinationFromBase(file, subPath);
571 private void setDestinationFromBase(File base, String subPath) {
572 if (subPath == null) {
573 throw new NullPointerException("subPath cannot be null");
575 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
579 * If the file to be downloaded is to be scanned by MediaScanner, this method
580 * should be called before {@link DownloadManager#enqueue(Request)} is called.
582 public void allowScanningByMediaScanner() {
587 * Add an HTTP header to be included with the download request. The header will be added to
588 * the end of the list.
589 * @param header HTTP header name
590 * @param value header value
591 * @return this object
592 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1
593 * Message Headers</a>
595 public Request addRequestHeader(String header, String value) {
596 if (header == null) {
597 throw new NullPointerException("header cannot be null");
599 if (header.contains(":")) {
600 throw new IllegalArgumentException("header may not contain ':'");
605 mRequestHeaders.add(Pair.create(header, value));
610 * Set the title of this download, to be displayed in notifications (if enabled). If no
611 * title is given, a default one will be assigned based on the download filename, once the
613 * @return this object
615 public Request setTitle(CharSequence title) {
621 * Set a description of this download, to be displayed in notifications (if enabled)
622 * @return this object
624 public Request setDescription(CharSequence description) {
625 mDescription = description;
630 * Set the MIME content type of this download. This will override the content type declared
631 * in the server's response.
632 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1
634 * @return this object
636 public Request setMimeType(String mimeType) {
637 mMimeType = mimeType;
642 * Control whether a system notification is posted by the download manager while this
643 * download is running. If enabled, the download manager posts notifications about downloads
644 * through the system {@link android.app.NotificationManager}. By default, a notification is
647 * If set to false, this requires the permission
648 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
650 * @param show whether the download manager should show a notification for this download.
651 * @return this object
652 * @deprecated use {@link #setNotificationVisibility(int)}
655 public Request setShowRunningNotification(boolean show) {
656 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) :
657 setNotificationVisibility(VISIBILITY_HIDDEN);
661 * Control whether a system notification is posted by the download manager while this
662 * download is running or when it is completed.
663 * If enabled, the download manager posts notifications about downloads
664 * through the system {@link android.app.NotificationManager}.
665 * By default, a notification is shown only when the download is in progress.
667 * It can take the following values: {@link #VISIBILITY_HIDDEN},
668 * {@link #VISIBILITY_VISIBLE},
669 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}.
671 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission
672 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
674 * @param visibility the visibility setting value
675 * @return this object
677 public Request setNotificationVisibility(int visibility) {
678 mNotificationVisibility = visibility;
683 * Restrict the types of networks over which this download may proceed.
684 * By default, all network types are allowed. Consider using
685 * {@link #setAllowedOverMetered(boolean)} instead, since it's more
688 * As of {@link android.os.Build.VERSION_CODES#N}, setting only the
689 * {@link #NETWORK_WIFI} flag here is equivalent to calling
690 * {@link #setAllowedOverMetered(boolean)} with {@code false}.
692 * @param flags any combination of the NETWORK_* bit flags.
693 * @return this object
695 public Request setAllowedNetworkTypes(int flags) {
696 mAllowedNetworkTypes = flags;
701 * Set whether this download may proceed over a roaming connection. By default, roaming is
703 * @param allowed whether to allow a roaming connection to be used
704 * @return this object
706 public Request setAllowedOverRoaming(boolean allowed) {
707 mRoamingAllowed = allowed;
712 * Set whether this download may proceed over a metered network
713 * connection. By default, metered networks are allowed.
715 * @see ConnectivityManager#isActiveNetworkMetered()
717 public Request setAllowedOverMetered(boolean allow) {
718 mMeteredAllowed = allow;
723 * Specify that to run this download, the device needs to be plugged in.
724 * This defaults to false.
726 * @param requiresCharging Whether or not the device is plugged in.
727 * @see android.app.job.JobInfo.Builder#setRequiresCharging(boolean)
729 public Request setRequiresCharging(boolean requiresCharging) {
730 if (requiresCharging) {
731 mFlags |= Downloads.Impl.FLAG_REQUIRES_CHARGING;
733 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_CHARGING;
739 * Specify that to run, the download needs the device to be in idle
740 * mode. This defaults to false.
742 * Idle mode is a loose definition provided by the system, which means
743 * that the device is not in use, and has not been in use for some time.
745 * @param requiresDeviceIdle Whether or not the device need be within an
746 * idle maintenance window.
747 * @see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)
749 public Request setRequiresDeviceIdle(boolean requiresDeviceIdle) {
750 if (requiresDeviceIdle) {
751 mFlags |= Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
753 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
759 * Set whether this download should be displayed in the system's Downloads UI. True by
761 * @param isVisible whether to display this download in the Downloads UI
762 * @return this object
764 public Request setVisibleInDownloadsUi(boolean isVisible) {
765 mIsVisibleInDownloadsUi = isVisible;
770 * @return ContentValues to be passed to DownloadProvider.insert()
772 ContentValues toContentValues(String packageName) {
773 ContentValues values = new ContentValues();
775 values.put(Downloads.Impl.COLUMN_URI, mUri.toString());
776 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
777 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName);
779 if (mDestinationUri != null) {
780 values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
781 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
783 values.put(Downloads.Impl.COLUMN_DESTINATION,
784 (this.mUseSystemCache) ?
785 Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION :
786 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
788 // is the file supposed to be media-scannable?
789 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES :
792 if (!mRequestHeaders.isEmpty()) {
793 encodeHttpHeaders(values);
796 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
797 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
798 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
800 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
801 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
802 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
803 values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed);
804 values.put(Downloads.Impl.COLUMN_FLAGS, mFlags);
805 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
810 private void encodeHttpHeaders(ContentValues values) {
812 for (Pair<String, String> header : mRequestHeaders) {
813 String headerString = header.first + ": " + header.second;
814 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
819 private void putIfNonNull(ContentValues contentValues, String key, Object value) {
821 contentValues.put(key, value.toString());
827 * This class may be used to filter download manager queries.
829 public static class Query {
831 * Constant for use with {@link #orderBy}
834 public static final int ORDER_ASCENDING = 1;
837 * Constant for use with {@link #orderBy}
840 public static final int ORDER_DESCENDING = 2;
842 private long[] mIds = null;
843 private Integer mStatusFlags = null;
844 private String mFilterString = null;
845 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
846 private int mOrderDirection = ORDER_DESCENDING;
847 private boolean mOnlyIncludeVisibleInDownloadsUi = false;
850 * Include only the downloads with the given IDs.
851 * @return this object
853 public Query setFilterById(long... ids) {
860 * Include only the downloads that contains the given string in its name.
861 * @return this object
864 public Query setFilterByString(@Nullable String filter) {
865 mFilterString = filter;
870 * Include only downloads with status matching any the given status flags.
871 * @param flags any combination of the STATUS_* bit flags
872 * @return this object
874 public Query setFilterByStatus(int flags) {
875 mStatusFlags = flags;
880 * Controls whether this query includes downloads not visible in the system's Downloads UI.
881 * @param value if true, this query will only include downloads that should be displayed in
882 * the system's Downloads UI; if false (the default), this query will include
883 * both visible and invisible downloads.
884 * @return this object
887 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
888 mOnlyIncludeVisibleInDownloadsUi = value;
893 * Change the sort order of the returned Cursor.
895 * @param column one of the COLUMN_* constants; currently, only
896 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
898 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
899 * @return this object
902 public Query orderBy(String column, int direction) {
903 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
904 throw new IllegalArgumentException("Invalid direction: " + direction);
907 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
908 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
909 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
910 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
912 throw new IllegalArgumentException("Cannot order by " + column);
914 mOrderDirection = direction;
919 * Run this query using the given ContentResolver.
920 * @param projection the projection to pass to ContentResolver.query()
921 * @return the Cursor returned by ContentResolver.query()
923 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
925 List<String> selectionParts = new ArrayList<String>();
926 String[] selectionArgs = null;
928 int whereArgsCount = (mIds == null) ? 0 : mIds.length;
929 whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1;
930 selectionArgs = new String[whereArgsCount];
932 if (whereArgsCount > 0) {
934 selectionParts.add(getWhereClauseForIds(mIds));
935 getWhereArgsForIds(mIds, selectionArgs);
938 if (mFilterString != null) {
939 selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?");
940 selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%";
944 if (mStatusFlags != null) {
945 List<String> parts = new ArrayList<String>();
946 if ((mStatusFlags & STATUS_PENDING) != 0) {
947 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING));
949 if ((mStatusFlags & STATUS_RUNNING) != 0) {
950 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING));
952 if ((mStatusFlags & STATUS_PAUSED) != 0) {
953 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
954 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
955 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
956 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
957 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_MANUAL));
959 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
960 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS));
962 if ((mStatusFlags & STATUS_FAILED) != 0) {
963 parts.add("(" + statusClause(">=", 400)
964 + " AND " + statusClause("<", 600) + ")");
966 selectionParts.add(joinStrings(" OR ", parts));
969 if (mOnlyIncludeVisibleInDownloadsUi) {
970 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
973 // only return rows which are not marked 'deleted = 1'
974 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
976 String selection = joinStrings(" AND ", selectionParts);
977 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
978 String orderBy = mOrderByColumn + " " + orderDirection;
980 return resolver.query(uri, projection, selection, selectionArgs, orderBy);
983 private String joinStrings(String joiner, Iterable<String> parts) {
984 StringBuilder builder = new StringBuilder();
985 boolean first = true;
986 for (String part : parts) {
988 builder.append(joiner);
990 builder.append(part);
993 return builder.toString();
996 private String statusClause(String operator, int value) {
997 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'";
1001 private final ContentResolver mResolver;
1002 private final String mPackageName;
1004 private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
1005 private boolean mAccessFilename;
1010 public DownloadManager(Context context) {
1011 mResolver = context.getContentResolver();
1012 mPackageName = context.getPackageName();
1014 // Callers can access filename columns when targeting old platform
1015 // versions; otherwise we throw telling them it's deprecated.
1016 mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N;
1020 * Makes this object access the download provider through /all_downloads URIs rather than
1021 * /my_downloads URIs, for clients that have permission to do so.
1024 public void setAccessAllDownloads(boolean accessAllDownloads) {
1025 if (accessAllDownloads) {
1026 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
1028 mBaseUri = Downloads.Impl.CONTENT_URI;
1033 public void setAccessFilename(boolean accessFilename) {
1034 mAccessFilename = accessFilename;
1038 * Enqueue a new download. The download will start automatically once the download manager is
1039 * ready to execute it and connectivity is available.
1041 * @param request the parameters specifying this download
1042 * @return an ID for the download, unique across the system. This ID is used to make future
1043 * calls related to this download.
1045 public long enqueue(Request request) {
1046 ContentValues values = request.toContentValues(mPackageName);
1047 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1048 long id = Long.parseLong(downloadUri.getLastPathSegment());
1053 * Marks the specified download as 'to be deleted'. This is done when a completed download
1054 * is to be removed but the row was stored without enough info to delete the corresponding
1055 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
1057 * @param ids the IDs of the downloads to be marked 'deleted'
1058 * @return the number of downloads actually updated
1061 public int markRowDeleted(long... ids) {
1062 if (ids == null || ids.length == 0) {
1063 // called with nothing to remove!
1064 throw new IllegalArgumentException("input param 'ids' can't be null");
1066 return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1070 * Cancel downloads and remove them from the download manager. Each download will be stopped if
1071 * it was running, and it will no longer be accessible through the download manager.
1072 * If there is a downloaded file, partial or complete, it is deleted.
1074 * @param ids the IDs of the downloads to remove
1075 * @return the number of downloads actually removed
1077 public int remove(long... ids) {
1078 return markRowDeleted(ids);
1082 * Query the download manager about downloads that have been requested.
1083 * @param query parameters specifying filters for this query
1084 * @return a Cursor over the result set of downloads, with columns consisting of all the
1085 * COLUMN_* constants.
1087 public Cursor query(Query query) {
1088 Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
1089 if (underlyingCursor == null) {
1092 return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename);
1096 * Open a downloaded file for reading. The download must have completed.
1097 * @param id the ID of the download
1098 * @return a read-only {@link ParcelFileDescriptor}
1099 * @throws FileNotFoundException if the destination file does not already exist
1101 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
1102 return mResolver.openFileDescriptor(getDownloadUri(id), "r");
1106 * Returns the {@link Uri} of the given downloaded file id, if the file is
1107 * downloaded successfully. Otherwise, null is returned.
1109 * @param id the id of the downloaded file.
1110 * @return the {@link Uri} of the given downloaded file id, if download was
1111 * successful. null otherwise.
1113 public Uri getUriForDownloadedFile(long id) {
1114 // to check if the file is in cache, get its destination from the database
1115 Query query = new Query().setFilterById(id);
1116 Cursor cursor = null;
1118 cursor = query(query);
1119 if (cursor == null) {
1122 if (cursor.moveToFirst()) {
1123 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1124 if (DownloadManager.STATUS_SUCCESSFUL == status) {
1125 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
1129 if (cursor != null) {
1133 // downloaded file not found or its status is not 'successfully completed'
1138 * Returns the media type of the given downloaded file id, if the file was
1139 * downloaded successfully. Otherwise, null is returned.
1141 * @param id the id of the downloaded file.
1142 * @return the media type of the given downloaded file id, if download was successful. null
1145 public String getMimeTypeForDownloadedFile(long id) {
1146 Query query = new Query().setFilterById(id);
1147 Cursor cursor = null;
1149 cursor = query(query);
1150 if (cursor == null) {
1153 while (cursor.moveToFirst()) {
1154 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1157 if (cursor != null) {
1161 // downloaded file not found or its status is not 'successfully completed'
1166 * Restart the given downloads, which must have already completed (successfully or not). This
1167 * method will only work when called from within the download manager's process.
1168 * @param ids the IDs of the downloads
1171 public void restartDownload(long... ids) {
1172 Cursor cursor = query(new Query().setFilterById(ids));
1174 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1175 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
1176 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
1177 throw new IllegalArgumentException("Cannot restart incomplete download: "
1178 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
1185 ContentValues values = new ContentValues();
1186 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
1187 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
1188 values.putNull(Downloads.Impl._DATA);
1189 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1190 values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
1191 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1195 * Force the given downloads to proceed even if their size is larger than
1196 * {@link #getMaxBytesOverMobile(Context)}.
1200 public void forceDownload(long... ids) {
1201 ContentValues values = new ContentValues();
1202 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1203 values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
1204 values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1);
1205 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1209 * Pause the given running download manually.
1211 * @param id the ID of the download to be paused
1212 * @return the number of downloads actually updated
1215 public int pauseDownload(long id) {
1216 ContentValues values = new ContentValues();
1217 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PAUSED_MANUAL);
1219 return mResolver.update(ContentUris.withAppendedId(mBaseUri, id), values, null, null);
1223 * Resume the given paused download manually.
1225 * @param id the ID of the download to be resumed
1226 * @return the number of downloads actually updated
1229 public int resumeDownload(long id) {
1230 ContentValues values = new ContentValues();
1231 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_RUNNING);
1233 return mResolver.update(ContentUris.withAppendedId(mBaseUri, id), values, null, null);
1237 * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1240 * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1241 * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1244 public static Long getMaxBytesOverMobile(Context context) {
1246 return Settings.Global.getLong(context.getContentResolver(),
1247 Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
1248 } catch (SettingNotFoundException exc) {
1254 * Rename the given download if the download has completed
1256 * @param context the {@link Context} to use in case need to update MediaProvider
1257 * @param id the downloaded id
1258 * @param displayName the new name to rename to
1259 * @return true if rename was successful, false otherwise
1262 public boolean rename(Context context, long id, String displayName) {
1263 if (!FileUtils.isValidFatFilename(displayName)) {
1264 throw new SecurityException(displayName + " is not a valid filename");
1267 Query query = new Query().setFilterById(id);
1268 Cursor cursor = null;
1269 String oldDisplayName = null;
1270 String mimeType = null;
1272 cursor = query(query);
1273 if (cursor == null) {
1276 if (cursor.moveToFirst()) {
1277 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1278 if (DownloadManager.STATUS_SUCCESSFUL != status) {
1281 oldDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_TITLE));
1282 mimeType = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1285 if (cursor != null) {
1290 if (oldDisplayName == null || mimeType == null) {
1291 throw new IllegalStateException(
1292 "Document with id " + id + " does not exist");
1295 final File parent = Environment.getExternalStoragePublicDirectory(
1296 Environment.DIRECTORY_DOWNLOADS);
1298 final File before = new File(parent, oldDisplayName);
1299 final File after = new File(parent, displayName);
1301 if (after.exists()) {
1302 throw new IllegalStateException("Already exists " + after);
1304 if (!before.renameTo(after)) {
1305 throw new IllegalStateException("Failed to rename to " + after);
1308 // Update MediaProvider if necessary
1309 if (mimeType.startsWith("image/")) {
1310 context.getContentResolver().delete(Images.Media.EXTERNAL_CONTENT_URI,
1311 Images.Media.DATA + "=?",
1313 before.getAbsolutePath()
1316 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
1317 intent.setData(Uri.fromFile(after));
1318 context.sendBroadcast(intent);
1321 ContentValues values = new ContentValues();
1322 values.put(Downloads.Impl.COLUMN_TITLE, displayName);
1323 values.put(Downloads.Impl._DATA, after.toString());
1324 values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
1327 return (mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
1328 getWhereArgsForIds(ids)) == 1);
1332 * Returns recommended maximum size, in bytes, of downloads that may go over a mobile
1333 * connection; or null if there's no recommended limit. The user will have the option to bypass
1336 * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1337 * @return recommended maximum size, in bytes, of downloads that may go over a mobile
1338 * connection; or null if there's no recommended limit.
1340 public static Long getRecommendedMaxBytesOverMobile(Context context) {
1342 return Settings.Global.getLong(context.getContentResolver(),
1343 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
1344 } catch (SettingNotFoundException exc) {
1350 public static boolean isActiveNetworkExpensive(Context context) {
1351 // TODO: connect to NetworkPolicyManager
1356 public static long getActiveNetworkWarningBytes(Context context) {
1357 // TODO: connect to NetworkPolicyManager
1362 * Adds a file to the downloads database system, so it could appear in Downloads App
1363 * (and thus become eligible for management by the Downloads App).
1365 * It is helpful to make the file scannable by MediaScanner by setting the param
1366 * isMediaScannerScannable to true. It makes the file visible in media managing
1367 * applications such as Gallery App, which could be a useful purpose of using this API.
1369 * @param title the title that would appear for this file in Downloads App.
1370 * @param description the description that would appear for this file in Downloads App.
1371 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1372 * scanned by MediaScanner appear in the applications used to view media (for example,
1374 * @param mimeType mimetype of the file.
1375 * @param path absolute pathname to the file. The file should be world-readable, so that it can
1376 * be managed by the Downloads App and any other app that is used to read it (for example,
1377 * Gallery app to display the file, if the file contents represent a video/image).
1378 * @param length length of the downloaded file
1379 * @param showNotification true if a notification is to be sent, false otherwise
1380 * @return an ID for the download entry added to the downloads app, unique across the system
1381 * This ID is used to make future calls related to this download.
1383 public long addCompletedDownload(String title, String description,
1384 boolean isMediaScannerScannable, String mimeType, String path, long length,
1385 boolean showNotification) {
1386 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1387 length, showNotification, false, null, null);
1391 * Adds a file to the downloads database system, so it could appear in Downloads App
1392 * (and thus become eligible for management by the Downloads App).
1394 * It is helpful to make the file scannable by MediaScanner by setting the param
1395 * isMediaScannerScannable to true. It makes the file visible in media managing
1396 * applications such as Gallery App, which could be a useful purpose of using this API.
1398 * @param title the title that would appear for this file in Downloads App.
1399 * @param description the description that would appear for this file in Downloads App.
1400 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1401 * scanned by MediaScanner appear in the applications used to view media (for example,
1403 * @param mimeType mimetype of the file.
1404 * @param path absolute pathname to the file. The file should be world-readable, so that it can
1405 * be managed by the Downloads App and any other app that is used to read it (for example,
1406 * Gallery app to display the file, if the file contents represent a video/image).
1407 * @param length length of the downloaded file
1408 * @param showNotification true if a notification is to be sent, false otherwise
1409 * @param uri the original HTTP URI of the download
1410 * @param referer the HTTP Referer for the download
1411 * @return an ID for the download entry added to the downloads app, unique across the system
1412 * This ID is used to make future calls related to this download.
1414 public long addCompletedDownload(String title, String description,
1415 boolean isMediaScannerScannable, String mimeType, String path, long length,
1416 boolean showNotification, Uri uri, Uri referer) {
1417 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1418 length, showNotification, false, uri, referer);
1422 public long addCompletedDownload(String title, String description,
1423 boolean isMediaScannerScannable, String mimeType, String path, long length,
1424 boolean showNotification, boolean allowWrite) {
1425 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1426 length, showNotification, allowWrite, null, null);
1430 public long addCompletedDownload(String title, String description,
1431 boolean isMediaScannerScannable, String mimeType, String path, long length,
1432 boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
1433 // make sure the input args are non-null/non-zero
1434 validateArgumentIsNonEmpty("title", title);
1435 validateArgumentIsNonEmpty("description", description);
1436 validateArgumentIsNonEmpty("path", path);
1437 validateArgumentIsNonEmpty("mimeType", mimeType);
1439 throw new IllegalArgumentException(" invalid value for param: totalBytes");
1442 // if there is already an entry with the given path name in downloads.db, return its id
1445 request = new Request(uri);
1447 request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD);
1449 request.setTitle(title)
1450 .setDescription(description)
1451 .setMimeType(mimeType);
1452 if (referer != null) {
1453 request.addRequestHeader("Referer", referer.toString());
1455 ContentValues values = request.toContentValues(null);
1456 values.put(Downloads.Impl.COLUMN_DESTINATION,
1457 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
1458 values.put(Downloads.Impl._DATA, path);
1459 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
1460 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
1461 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
1462 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
1463 Request.SCANNABLE_VALUE_NO);
1464 values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
1465 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
1466 values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0);
1467 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1468 if (downloadUri == null) {
1471 return Long.parseLong(downloadUri.getLastPathSegment());
1474 private static final String NON_DOWNLOADMANAGER_DOWNLOAD =
1475 "non-dwnldmngr-download-dont-retry2download";
1477 private static void validateArgumentIsNonEmpty(String paramName, String val) {
1478 if (TextUtils.isEmpty(val)) {
1479 throw new IllegalArgumentException(paramName + " can't be null");
1484 * Get the DownloadProvider URI for the download with the given ID.
1488 public Uri getDownloadUri(long id) {
1489 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
1493 * Get a parameterized SQL WHERE clause to select a bunch of IDs.
1495 static String getWhereClauseForIds(long[] ids) {
1496 StringBuilder whereClause = new StringBuilder();
1497 whereClause.append("(");
1498 for (int i = 0; i < ids.length; i++) {
1500 whereClause.append("OR ");
1502 whereClause.append(Downloads.Impl._ID);
1503 whereClause.append(" = ? ");
1505 whereClause.append(")");
1506 return whereClause.toString();
1510 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
1512 static String[] getWhereArgsForIds(long[] ids) {
1513 String[] whereArgs = new String[ids.length];
1514 return getWhereArgsForIds(ids, whereArgs);
1518 * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])}
1519 * and write it to the supplied args array.
1521 static String[] getWhereArgsForIds(long[] ids, String[] args) {
1522 assert(args.length >= ids.length);
1523 for (int i = 0; i < ids.length; i++) {
1524 args[i] = Long.toString(ids[i]);
1531 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
1532 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
1533 * Some columns correspond directly to underlying values while others are computed from
1536 private static class CursorTranslator extends CursorWrapper {
1537 private final Uri mBaseUri;
1538 private final boolean mAccessFilename;
1540 public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) {
1543 mAccessFilename = accessFilename;
1547 public int getInt(int columnIndex) {
1548 return (int) getLong(columnIndex);
1552 public long getLong(int columnIndex) {
1553 if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
1554 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1555 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
1556 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1558 return super.getLong(columnIndex);
1563 public String getString(int columnIndex) {
1564 final String columnName = getColumnName(columnIndex);
1565 switch (columnName) {
1566 case COLUMN_LOCAL_URI:
1567 return getLocalUri();
1568 case COLUMN_LOCAL_FILENAME:
1569 if (!mAccessFilename) {
1570 throw new SecurityException(
1571 "COLUMN_LOCAL_FILENAME is deprecated;"
1572 + " use ContentResolver.openFileDescriptor() instead");
1575 return super.getString(columnIndex);
1579 private String getLocalUri() {
1580 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
1581 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
1582 destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
1583 destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
1584 String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
1585 if (localPath == null) {
1588 return Uri.fromFile(new File(localPath)).toString();
1591 // return content URI for cache download
1592 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
1593 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString();
1596 private long getReason(int status) {
1597 switch (translateStatus(status)) {
1599 return getErrorCode(status);
1602 return getPausedReason(status);
1605 return 0; // arbitrary value when status is not an error
1609 private long getPausedReason(int status) {
1611 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1612 return PAUSED_WAITING_TO_RETRY;
1614 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1615 return PAUSED_WAITING_FOR_NETWORK;
1617 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1618 return PAUSED_QUEUED_FOR_WIFI;
1620 case Downloads.Impl.STATUS_PAUSED_MANUAL:
1621 return PAUSED_MANUAL;
1624 return PAUSED_UNKNOWN;
1628 private long getErrorCode(int status) {
1629 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
1630 || (500 <= status && status < 600)) {
1636 case Downloads.Impl.STATUS_FILE_ERROR:
1637 return ERROR_FILE_ERROR;
1639 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
1640 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
1641 return ERROR_UNHANDLED_HTTP_CODE;
1643 case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
1644 return ERROR_HTTP_DATA_ERROR;
1646 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
1647 return ERROR_TOO_MANY_REDIRECTS;
1649 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
1650 return ERROR_INSUFFICIENT_SPACE;
1652 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
1653 return ERROR_DEVICE_NOT_FOUND;
1655 case Downloads.Impl.STATUS_CANNOT_RESUME:
1656 return ERROR_CANNOT_RESUME;
1658 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
1659 return ERROR_FILE_ALREADY_EXISTS;
1662 return ERROR_UNKNOWN;
1666 private int translateStatus(int status) {
1668 case Downloads.Impl.STATUS_PENDING:
1669 return STATUS_PENDING;
1671 case Downloads.Impl.STATUS_RUNNING:
1672 return STATUS_RUNNING;
1674 case Downloads.Impl.STATUS_PAUSED_BY_APP:
1675 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1676 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1677 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1678 case Downloads.Impl.STATUS_PAUSED_MANUAL:
1679 return STATUS_PAUSED;
1681 case Downloads.Impl.STATUS_SUCCESS:
1682 return STATUS_SUCCESSFUL;
1685 assert Downloads.Impl.isStatusError(status);
1686 return STATUS_FAILED;