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.SystemApi;
22 import android.annotation.SystemService;
23 import android.annotation.SdkConstant.SdkConstantType;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.database.Cursor;
30 import android.database.CursorWrapper;
31 import android.net.ConnectivityManager;
32 import android.net.NetworkPolicyManager;
33 import android.net.Uri;
34 import android.os.Build;
35 import android.os.Environment;
36 import android.os.FileUtils;
37 import android.os.ParcelFileDescriptor;
38 import android.provider.Downloads;
39 import android.provider.Settings;
40 import android.provider.MediaStore.Images;
41 import android.provider.Settings.SettingNotFoundException;
42 import android.text.TextUtils;
43 import android.util.Pair;
46 import java.io.FileNotFoundException;
47 import java.util.ArrayList;
48 import java.util.List;
51 * The download manager is a system service that handles long-running HTTP downloads. Clients may
52 * request that a URI be downloaded to a particular destination file. The download manager will
53 * conduct the download in the background, taking care of HTTP interactions and retrying downloads
54 * after failures or across connectivity changes and system reboots.
56 * Apps that request downloads through this API should register a broadcast receiver for
57 * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
58 * download in a notification or from the downloads UI.
60 * Note that the application must have the {@link android.Manifest.permission#INTERNET}
61 * permission to use this class.
63 @SystemService(Context.DOWNLOAD_SERVICE)
64 public class DownloadManager {
67 * An identifier for a particular download, unique across the system. Clients use this ID to
68 * make subsequent calls related to the download.
70 public final static String COLUMN_ID = Downloads.Impl._ID;
73 * The client-supplied title for this download. This will be displayed in system notifications.
74 * Defaults to the empty string.
76 public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE;
79 * The client-supplied description of this download. This will be displayed in system
80 * notifications. Defaults to the empty string.
82 public final static String COLUMN_DESCRIPTION = Downloads.Impl.COLUMN_DESCRIPTION;
85 * URI to be downloaded.
87 public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI;
90 * Internet Media Type of the downloaded file. If no value is provided upon creation, this will
91 * initially be null and will be filled in based on the server's response once the download has
94 * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
96 public final static String COLUMN_MEDIA_TYPE = "media_type";
99 * Total size of the download in bytes. This will initially be -1 and will be filled in once
100 * the download starts.
102 public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
105 * Uri where downloaded file will be stored. If a destination is supplied by client, that URI
106 * will be used here. Otherwise, the value will initially be null and will be filled in with a
107 * generated URI once the download has started.
109 public final static String COLUMN_LOCAL_URI = "local_uri";
112 * Path to the downloaded file on disk.
114 * Note that apps may not have filesystem permissions to directly access
115 * this path. Instead of trying to open this path directly, apps should use
116 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain access.
118 * @deprecated apps should transition to using
119 * {@link ContentResolver#openFileDescriptor(Uri, String)}
123 public final static String COLUMN_LOCAL_FILENAME = "local_filename";
126 * Current status of the download, as one of the STATUS_* constants.
128 public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
131 * Provides more detail on the status of the download. Its meaning depends on the value of
132 * {@link #COLUMN_STATUS}.
134 * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
135 * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
136 * 2616. Otherwise, it will hold one of the ERROR_* constants.
138 * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
139 * paused. It will hold one of the PAUSED_* constants.
141 * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
142 * column's value is undefined.
144 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
147 public final static String COLUMN_REASON = "reason";
150 * Number of bytes download so far.
152 public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
155 * Timestamp when the download was last modified, in {@link System#currentTimeMillis
156 * System.currentTimeMillis()} (wall clock time in UTC).
158 public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
161 * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
162 * used to delete the entries from MediaProvider database when it is deleted from the
165 public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI;
170 public final static String COLUMN_ALLOW_WRITE = Downloads.Impl.COLUMN_ALLOW_WRITE;
173 * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
175 public final static int STATUS_PENDING = 1 << 0;
178 * Value of {@link #COLUMN_STATUS} when the download is currently running.
180 public final static int STATUS_RUNNING = 1 << 1;
183 * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume.
185 public final static int STATUS_PAUSED = 1 << 2;
188 * Value of {@link #COLUMN_STATUS} when the download has successfully completed.
190 public final static int STATUS_SUCCESSFUL = 1 << 3;
193 * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried).
195 public final static int STATUS_FAILED = 1 << 4;
198 * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit
199 * under any other error code.
201 public final static int ERROR_UNKNOWN = 1000;
204 * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
205 * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
206 * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
208 public final static int ERROR_FILE_ERROR = 1001;
211 * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
214 public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
217 * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
220 public final static int ERROR_HTTP_DATA_ERROR = 1004;
223 * Value of {@link #COLUMN_REASON} when there were too many redirects.
225 public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
228 * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
229 * this is because the SD card is full.
231 public final static int ERROR_INSUFFICIENT_SPACE = 1006;
234 * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
235 * this is because the SD card is not mounted.
237 public final static int ERROR_DEVICE_NOT_FOUND = 1007;
240 * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
241 * resume the download.
243 public final static int ERROR_CANNOT_RESUME = 1008;
246 * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
247 * download manager will not overwrite an existing file).
249 public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
252 * Value of {@link #COLUMN_REASON} when the download has failed because of
253 * {@link NetworkPolicyManager} controls on the requesting application.
257 public final static int ERROR_BLOCKED = 1010;
260 * Value of {@link #COLUMN_REASON} when the download is paused because some network error
261 * occurred and the download manager is waiting before retrying the request.
263 public final static int PAUSED_WAITING_TO_RETRY = 1;
266 * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
269 public final static int PAUSED_WAITING_FOR_NETWORK = 2;
272 * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
273 * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
275 public final static int PAUSED_QUEUED_FOR_WIFI = 3;
278 * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
280 public final static int PAUSED_UNKNOWN = 4;
283 * Broadcast intent action sent by the download manager when a download completes.
285 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
286 public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
289 * Broadcast intent action sent by the download manager when the user clicks on a running
290 * download, either from a system notification or from the downloads UI.
292 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
293 public final static String ACTION_NOTIFICATION_CLICKED =
294 "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
297 * Intent action to launch an activity to display all downloads.
299 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
300 public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
303 * Intent extra included with {@link #ACTION_VIEW_DOWNLOADS} to start DownloadApp in
306 public final static String INTENT_EXTRAS_SORT_BY_SIZE =
307 "android.app.DownloadManager.extra_sortBySize";
310 * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
311 * long) of the download that just completed.
313 public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
316 * When clicks on multiple notifications are received, the following
317 * provides an array of download ids corresponding to the download notification that was
318 * clicked. It can be retrieved by the receiver of this
319 * Intent using {@link android.content.Intent#getLongArrayExtra(String)}.
321 public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids";
325 public static final String ACTION_DOWNLOAD_COMPLETED =
326 "android.intent.action.DOWNLOAD_COMPLETED";
329 * columns to request from DownloadProvider.
332 public static final String[] UNDERLYING_COLUMNS = new String[] {
334 Downloads.Impl._DATA + " AS " + COLUMN_LOCAL_FILENAME,
335 Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
336 Downloads.Impl.COLUMN_DESTINATION,
337 Downloads.Impl.COLUMN_TITLE,
338 Downloads.Impl.COLUMN_DESCRIPTION,
339 Downloads.Impl.COLUMN_URI,
340 Downloads.Impl.COLUMN_STATUS,
341 Downloads.Impl.COLUMN_FILE_NAME_HINT,
342 Downloads.Impl.COLUMN_MIME_TYPE + " AS " + COLUMN_MEDIA_TYPE,
343 Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + COLUMN_TOTAL_SIZE_BYTES,
344 Downloads.Impl.COLUMN_LAST_MODIFICATION + " AS " + COLUMN_LAST_MODIFIED_TIMESTAMP,
345 Downloads.Impl.COLUMN_CURRENT_BYTES + " AS " + COLUMN_BYTES_DOWNLOADED_SO_FAR,
346 Downloads.Impl.COLUMN_ALLOW_WRITE,
347 /* add the following 'computed' columns to the cursor.
348 * they are not 'returned' by the database, but their inclusion
349 * eliminates need to have lot of methods in CursorTranslator
351 "'placeholder' AS " + COLUMN_LOCAL_URI,
352 "'placeholder' AS " + COLUMN_REASON
356 * This class contains all the information necessary to request a new download. The URI is the
357 * only required parameter.
359 * Note that the default download destination is a shared volume where the system might delete
360 * your file if it needs to reclaim space for system use. If this is a problem, use a location
361 * on external storage (see {@link #setDestinationUri(Uri)}.
363 public static class Request {
365 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
366 * {@link ConnectivityManager#TYPE_MOBILE}.
368 public static final int NETWORK_MOBILE = 1 << 0;
371 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
372 * {@link ConnectivityManager#TYPE_WIFI}.
374 public static final int NETWORK_WIFI = 1 << 1;
377 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
378 * {@link ConnectivityManager#TYPE_BLUETOOTH}.
382 public static final int NETWORK_BLUETOOTH = 1 << 2;
385 private Uri mDestinationUri;
386 private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
387 private CharSequence mTitle;
388 private CharSequence mDescription;
389 private String mMimeType;
390 private int mAllowedNetworkTypes = ~0; // default to all network types allowed
391 private boolean mRoamingAllowed = true;
392 private boolean mMeteredAllowed = true;
393 private int mFlags = 0;
394 private boolean mIsVisibleInDownloadsUi = true;
395 private boolean mScannable = false;
396 private boolean mUseSystemCache = false;
397 /** if a file is designated as a MediaScanner scannable file, the following value is
398 * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
400 private static final int SCANNABLE_VALUE_YES = 0;
401 // value of 1 is stored in the above column by DownloadProvider after it is scanned by
403 /** if a file is designated as a file that should not be scanned by MediaScanner,
404 * the following value is stored in the database column
405 * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
407 private static final int SCANNABLE_VALUE_NO = 2;
410 * This download is visible but only shows in the notifications
411 * while it's in progress.
413 public static final int VISIBILITY_VISIBLE = 0;
416 * This download is visible and shows in the notifications while
417 * in progress and after completion.
419 public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
422 * This download doesn't show in the UI or in the notifications.
424 public static final int VISIBILITY_HIDDEN = 2;
427 * This download shows in the notifications after completion ONLY.
428 * It is usuable only with
429 * {@link DownloadManager#addCompletedDownload(String, String,
430 * boolean, String, String, long, boolean)}.
432 public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3;
434 /** can take any of the following values: {@link #VISIBILITY_HIDDEN}
435 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE},
436 * {@link #VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION}
438 private int mNotificationVisibility = VISIBILITY_VISIBLE;
441 * @param uri the HTTP or HTTPS URI to download.
443 public Request(Uri uri) {
445 throw new NullPointerException();
447 String scheme = uri.getScheme();
448 if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
449 throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
454 Request(String uriString) {
455 mUri = Uri.parse(uriString);
459 * Set the local destination for the downloaded file. Must be a file URI to a path on
460 * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE
463 * The downloaded file is not scanned by MediaScanner.
464 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
466 * By default, downloads are saved to a generated filename in the shared download cache and
467 * may be deleted by the system at any time to reclaim space.
469 * @return this object
471 public Request setDestinationUri(Uri uri) {
472 mDestinationUri = uri;
477 * Set the local destination for the downloaded file to the system cache dir (/cache).
478 * This is only available to System apps with the permission
479 * {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}.
481 * The downloaded file is not scanned by MediaScanner.
482 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
484 * Files downloaded to /cache may be deleted by the system at any time to reclaim space.
486 * @return this object
489 public Request setDestinationToSystemCache() {
490 mUseSystemCache = true;
495 * Set the local destination for the downloaded file to a path within
496 * the application's external files directory (as returned by
497 * {@link Context#getExternalFilesDir(String)}.
499 * The downloaded file is not scanned by MediaScanner. But it can be
500 * made scannable by calling {@link #allowScanningByMediaScanner()}.
502 * @param context the {@link Context} to use in determining the external
504 * @param dirType the directory type to pass to
505 * {@link Context#getExternalFilesDir(String)}
506 * @param subPath the path within the external directory, including the
507 * destination filename
508 * @return this object
509 * @throws IllegalStateException If the external storage directory
510 * cannot be found or created.
512 public Request setDestinationInExternalFilesDir(Context context, String dirType,
514 final File file = context.getExternalFilesDir(dirType);
516 throw new IllegalStateException("Failed to get external storage files directory");
517 } else if (file.exists()) {
518 if (!file.isDirectory()) {
519 throw new IllegalStateException(file.getAbsolutePath() +
520 " already exists and is not a directory");
523 if (!file.mkdirs()) {
524 throw new IllegalStateException("Unable to create directory: "+
525 file.getAbsolutePath());
528 setDestinationFromBase(file, subPath);
533 * Set the local destination for the downloaded file to a path within
534 * the public external storage directory (as returned by
535 * {@link Environment#getExternalStoragePublicDirectory(String)}).
537 * The downloaded file is not scanned by MediaScanner. But it can be
538 * made scannable by calling {@link #allowScanningByMediaScanner()}.
540 * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)}
541 * @param subPath the path within the external directory, including the
542 * destination filename
543 * @return this object
544 * @throws IllegalStateException If the external storage directory
545 * cannot be found or created.
547 public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
548 File file = Environment.getExternalStoragePublicDirectory(dirType);
550 throw new IllegalStateException("Failed to get external storage public directory");
551 } else if (file.exists()) {
552 if (!file.isDirectory()) {
553 throw new IllegalStateException(file.getAbsolutePath() +
554 " already exists and is not a directory");
557 if (!file.mkdirs()) {
558 throw new IllegalStateException("Unable to create directory: "+
559 file.getAbsolutePath());
562 setDestinationFromBase(file, subPath);
566 private void setDestinationFromBase(File base, String subPath) {
567 if (subPath == null) {
568 throw new NullPointerException("subPath cannot be null");
570 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
574 * If the file to be downloaded is to be scanned by MediaScanner, this method
575 * should be called before {@link DownloadManager#enqueue(Request)} is called.
577 public void allowScanningByMediaScanner() {
582 * Add an HTTP header to be included with the download request. The header will be added to
583 * the end of the list.
584 * @param header HTTP header name
585 * @param value header value
586 * @return this object
587 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1
588 * Message Headers</a>
590 public Request addRequestHeader(String header, String value) {
591 if (header == null) {
592 throw new NullPointerException("header cannot be null");
594 if (header.contains(":")) {
595 throw new IllegalArgumentException("header may not contain ':'");
600 mRequestHeaders.add(Pair.create(header, value));
605 * Set the title of this download, to be displayed in notifications (if enabled). If no
606 * title is given, a default one will be assigned based on the download filename, once the
608 * @return this object
610 public Request setTitle(CharSequence title) {
616 * Set a description of this download, to be displayed in notifications (if enabled)
617 * @return this object
619 public Request setDescription(CharSequence description) {
620 mDescription = description;
625 * Set the MIME content type of this download. This will override the content type declared
626 * in the server's response.
627 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1
629 * @return this object
631 public Request setMimeType(String mimeType) {
632 mMimeType = mimeType;
637 * Control whether a system notification is posted by the download manager while this
638 * download is running. If enabled, the download manager posts notifications about downloads
639 * through the system {@link android.app.NotificationManager}. By default, a notification is
642 * If set to false, this requires the permission
643 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
645 * @param show whether the download manager should show a notification for this download.
646 * @return this object
647 * @deprecated use {@link #setNotificationVisibility(int)}
650 public Request setShowRunningNotification(boolean show) {
651 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) :
652 setNotificationVisibility(VISIBILITY_HIDDEN);
656 * Control whether a system notification is posted by the download manager while this
657 * download is running or when it is completed.
658 * If enabled, the download manager posts notifications about downloads
659 * through the system {@link android.app.NotificationManager}.
660 * By default, a notification is shown only when the download is in progress.
662 * It can take the following values: {@link #VISIBILITY_HIDDEN},
663 * {@link #VISIBILITY_VISIBLE},
664 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}.
666 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission
667 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
669 * @param visibility the visibility setting value
670 * @return this object
672 public Request setNotificationVisibility(int visibility) {
673 mNotificationVisibility = visibility;
678 * Restrict the types of networks over which this download may proceed.
679 * By default, all network types are allowed. Consider using
680 * {@link #setAllowedOverMetered(boolean)} instead, since it's more
683 * As of {@link android.os.Build.VERSION_CODES#N}, setting only the
684 * {@link #NETWORK_WIFI} flag here is equivalent to calling
685 * {@link #setAllowedOverMetered(boolean)} with {@code false}.
687 * @param flags any combination of the NETWORK_* bit flags.
688 * @return this object
690 public Request setAllowedNetworkTypes(int flags) {
691 mAllowedNetworkTypes = flags;
696 * Set whether this download may proceed over a roaming connection. By default, roaming is
698 * @param allowed whether to allow a roaming connection to be used
699 * @return this object
701 public Request setAllowedOverRoaming(boolean allowed) {
702 mRoamingAllowed = allowed;
707 * Set whether this download may proceed over a metered network
708 * connection. By default, metered networks are allowed.
710 * @see ConnectivityManager#isActiveNetworkMetered()
712 public Request setAllowedOverMetered(boolean allow) {
713 mMeteredAllowed = allow;
718 * Specify that to run this download, the device needs to be plugged in.
719 * This defaults to false.
721 * @param requiresCharging Whether or not the device is plugged in.
722 * @see android.app.job.JobInfo.Builder#setRequiresCharging(boolean)
724 public Request setRequiresCharging(boolean requiresCharging) {
725 if (requiresCharging) {
726 mFlags |= Downloads.Impl.FLAG_REQUIRES_CHARGING;
728 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_CHARGING;
734 * Specify that to run, the download needs the device to be in idle
735 * mode. This defaults to false.
737 * Idle mode is a loose definition provided by the system, which means
738 * that the device is not in use, and has not been in use for some time.
740 * @param requiresDeviceIdle Whether or not the device need be within an
741 * idle maintenance window.
742 * @see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)
744 public Request setRequiresDeviceIdle(boolean requiresDeviceIdle) {
745 if (requiresDeviceIdle) {
746 mFlags |= Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
748 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
754 * Set whether this download should be displayed in the system's Downloads UI. True by
756 * @param isVisible whether to display this download in the Downloads UI
757 * @return this object
759 public Request setVisibleInDownloadsUi(boolean isVisible) {
760 mIsVisibleInDownloadsUi = isVisible;
765 * @return ContentValues to be passed to DownloadProvider.insert()
767 ContentValues toContentValues(String packageName) {
768 ContentValues values = new ContentValues();
770 values.put(Downloads.Impl.COLUMN_URI, mUri.toString());
771 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
772 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName);
774 if (mDestinationUri != null) {
775 values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
776 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
778 values.put(Downloads.Impl.COLUMN_DESTINATION,
779 (this.mUseSystemCache) ?
780 Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION :
781 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
783 // is the file supposed to be media-scannable?
784 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES :
787 if (!mRequestHeaders.isEmpty()) {
788 encodeHttpHeaders(values);
791 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
792 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
793 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
795 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
796 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
797 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
798 values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed);
799 values.put(Downloads.Impl.COLUMN_FLAGS, mFlags);
800 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
805 private void encodeHttpHeaders(ContentValues values) {
807 for (Pair<String, String> header : mRequestHeaders) {
808 String headerString = header.first + ": " + header.second;
809 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
814 private void putIfNonNull(ContentValues contentValues, String key, Object value) {
816 contentValues.put(key, value.toString());
822 * This class may be used to filter download manager queries.
824 public static class Query {
826 * Constant for use with {@link #orderBy}
829 public static final int ORDER_ASCENDING = 1;
832 * Constant for use with {@link #orderBy}
835 public static final int ORDER_DESCENDING = 2;
837 private long[] mIds = null;
838 private Integer mStatusFlags = null;
839 private String mFilterString = null;
840 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
841 private int mOrderDirection = ORDER_DESCENDING;
842 private boolean mOnlyIncludeVisibleInDownloadsUi = false;
845 * Include only the downloads with the given IDs.
846 * @return this object
848 public Query setFilterById(long... ids) {
855 * Include only the downloads that contains the given string in its name.
856 * @return this object
859 public Query setFilterByString(@Nullable String filter) {
860 mFilterString = filter;
865 * Include only downloads with status matching any the given status flags.
866 * @param flags any combination of the STATUS_* bit flags
867 * @return this object
869 public Query setFilterByStatus(int flags) {
870 mStatusFlags = flags;
875 * Controls whether this query includes downloads not visible in the system's Downloads UI.
876 * @param value if true, this query will only include downloads that should be displayed in
877 * the system's Downloads UI; if false (the default), this query will include
878 * both visible and invisible downloads.
879 * @return this object
882 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
883 mOnlyIncludeVisibleInDownloadsUi = value;
888 * Change the sort order of the returned Cursor.
890 * @param column one of the COLUMN_* constants; currently, only
891 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
893 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
894 * @return this object
897 public Query orderBy(String column, int direction) {
898 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
899 throw new IllegalArgumentException("Invalid direction: " + direction);
902 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
903 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
904 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
905 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
907 throw new IllegalArgumentException("Cannot order by " + column);
909 mOrderDirection = direction;
914 * Run this query using the given ContentResolver.
915 * @param projection the projection to pass to ContentResolver.query()
916 * @return the Cursor returned by ContentResolver.query()
918 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
920 List<String> selectionParts = new ArrayList<String>();
921 String[] selectionArgs = null;
923 int whereArgsCount = (mIds == null) ? 0 : mIds.length;
924 whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1;
925 selectionArgs = new String[whereArgsCount];
927 if (whereArgsCount > 0) {
929 selectionParts.add(getWhereClauseForIds(mIds));
930 getWhereArgsForIds(mIds, selectionArgs);
933 if (mFilterString != null) {
934 selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?");
935 selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%";
939 if (mStatusFlags != null) {
940 List<String> parts = new ArrayList<String>();
941 if ((mStatusFlags & STATUS_PENDING) != 0) {
942 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING));
944 if ((mStatusFlags & STATUS_RUNNING) != 0) {
945 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING));
947 if ((mStatusFlags & STATUS_PAUSED) != 0) {
948 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
949 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
950 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
951 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
953 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
954 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS));
956 if ((mStatusFlags & STATUS_FAILED) != 0) {
957 parts.add("(" + statusClause(">=", 400)
958 + " AND " + statusClause("<", 600) + ")");
960 selectionParts.add(joinStrings(" OR ", parts));
963 if (mOnlyIncludeVisibleInDownloadsUi) {
964 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
967 // only return rows which are not marked 'deleted = 1'
968 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
970 String selection = joinStrings(" AND ", selectionParts);
971 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
972 String orderBy = mOrderByColumn + " " + orderDirection;
974 return resolver.query(uri, projection, selection, selectionArgs, orderBy);
977 private String joinStrings(String joiner, Iterable<String> parts) {
978 StringBuilder builder = new StringBuilder();
979 boolean first = true;
980 for (String part : parts) {
982 builder.append(joiner);
984 builder.append(part);
987 return builder.toString();
990 private String statusClause(String operator, int value) {
991 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'";
995 private final ContentResolver mResolver;
996 private final String mPackageName;
998 private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
999 private boolean mAccessFilename;
1004 public DownloadManager(Context context) {
1005 mResolver = context.getContentResolver();
1006 mPackageName = context.getPackageName();
1008 // Callers can access filename columns when targeting old platform
1009 // versions; otherwise we throw telling them it's deprecated.
1010 mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N;
1014 * Makes this object access the download provider through /all_downloads URIs rather than
1015 * /my_downloads URIs, for clients that have permission to do so.
1018 public void setAccessAllDownloads(boolean accessAllDownloads) {
1019 if (accessAllDownloads) {
1020 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
1022 mBaseUri = Downloads.Impl.CONTENT_URI;
1027 public void setAccessFilename(boolean accessFilename) {
1028 mAccessFilename = accessFilename;
1032 * Enqueue a new download. The download will start automatically once the download manager is
1033 * ready to execute it and connectivity is available.
1035 * @param request the parameters specifying this download
1036 * @return an ID for the download, unique across the system. This ID is used to make future
1037 * calls related to this download.
1039 public long enqueue(Request request) {
1040 ContentValues values = request.toContentValues(mPackageName);
1041 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1042 long id = Long.parseLong(downloadUri.getLastPathSegment());
1047 * Marks the specified download as 'to be deleted'. This is done when a completed download
1048 * is to be removed but the row was stored without enough info to delete the corresponding
1049 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
1051 * @param ids the IDs of the downloads to be marked 'deleted'
1052 * @return the number of downloads actually updated
1055 public int markRowDeleted(long... ids) {
1056 if (ids == null || ids.length == 0) {
1057 // called with nothing to remove!
1058 throw new IllegalArgumentException("input param 'ids' can't be null");
1060 return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1064 * Cancel downloads and remove them from the download manager. Each download will be stopped if
1065 * it was running, and it will no longer be accessible through the download manager.
1066 * If there is a downloaded file, partial or complete, it is deleted.
1068 * @param ids the IDs of the downloads to remove
1069 * @return the number of downloads actually removed
1071 public int remove(long... ids) {
1072 return markRowDeleted(ids);
1076 * Query the download manager about downloads that have been requested.
1077 * @param query parameters specifying filters for this query
1078 * @return a Cursor over the result set of downloads, with columns consisting of all the
1079 * COLUMN_* constants.
1081 public Cursor query(Query query) {
1082 Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
1083 if (underlyingCursor == null) {
1086 return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename);
1090 * Open a downloaded file for reading. The download must have completed.
1091 * @param id the ID of the download
1092 * @return a read-only {@link ParcelFileDescriptor}
1093 * @throws FileNotFoundException if the destination file does not already exist
1095 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
1096 return mResolver.openFileDescriptor(getDownloadUri(id), "r");
1100 * Returns the {@link Uri} of the given downloaded file id, if the file is
1101 * downloaded successfully. Otherwise, null is returned.
1103 * @param id the id of the downloaded file.
1104 * @return the {@link Uri} of the given downloaded file id, if download was
1105 * successful. null otherwise.
1107 public Uri getUriForDownloadedFile(long id) {
1108 // to check if the file is in cache, get its destination from the database
1109 Query query = new Query().setFilterById(id);
1110 Cursor cursor = null;
1112 cursor = query(query);
1113 if (cursor == null) {
1116 if (cursor.moveToFirst()) {
1117 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1118 if (DownloadManager.STATUS_SUCCESSFUL == status) {
1119 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
1123 if (cursor != null) {
1127 // downloaded file not found or its status is not 'successfully completed'
1132 * Returns the media type of the given downloaded file id, if the file was
1133 * downloaded successfully. Otherwise, null is returned.
1135 * @param id the id of the downloaded file.
1136 * @return the media type of the given downloaded file id, if download was successful. null
1139 public String getMimeTypeForDownloadedFile(long id) {
1140 Query query = new Query().setFilterById(id);
1141 Cursor cursor = null;
1143 cursor = query(query);
1144 if (cursor == null) {
1147 while (cursor.moveToFirst()) {
1148 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1151 if (cursor != null) {
1155 // downloaded file not found or its status is not 'successfully completed'
1160 * Restart the given downloads, which must have already completed (successfully or not). This
1161 * method will only work when called from within the download manager's process.
1162 * @param ids the IDs of the downloads
1165 public void restartDownload(long... ids) {
1166 Cursor cursor = query(new Query().setFilterById(ids));
1168 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1169 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
1170 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
1171 throw new IllegalArgumentException("Cannot restart incomplete download: "
1172 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
1179 ContentValues values = new ContentValues();
1180 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
1181 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
1182 values.putNull(Downloads.Impl._DATA);
1183 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1184 values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
1185 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1189 * Force the given downloads to proceed even if their size is larger than
1190 * {@link #getMaxBytesOverMobile(Context)}.
1194 public void forceDownload(long... ids) {
1195 ContentValues values = new ContentValues();
1196 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1197 values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
1198 values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1);
1199 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1203 * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1206 * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1207 * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1210 public static Long getMaxBytesOverMobile(Context context) {
1212 return Settings.Global.getLong(context.getContentResolver(),
1213 Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
1214 } catch (SettingNotFoundException exc) {
1220 * Rename the given download if the download has completed
1222 * @param context the {@link Context} to use in case need to update MediaProvider
1223 * @param id the downloaded id
1224 * @param displayName the new name to rename to
1225 * @return true if rename was successful, false otherwise
1228 public boolean rename(Context context, long id, String displayName) {
1229 if (!FileUtils.isValidFatFilename(displayName)) {
1230 throw new SecurityException(displayName + " is not a valid filename");
1233 Query query = new Query().setFilterById(id);
1234 Cursor cursor = null;
1235 String oldDisplayName = null;
1236 String mimeType = null;
1238 cursor = query(query);
1239 if (cursor == null) {
1242 if (cursor.moveToFirst()) {
1243 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1244 if (DownloadManager.STATUS_SUCCESSFUL != status) {
1247 oldDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_TITLE));
1248 mimeType = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1251 if (cursor != null) {
1256 if (oldDisplayName == null || mimeType == null) {
1257 throw new IllegalStateException(
1258 "Document with id " + id + " does not exist");
1261 final File parent = Environment.getExternalStoragePublicDirectory(
1262 Environment.DIRECTORY_DOWNLOADS);
1264 final File before = new File(parent, oldDisplayName);
1265 final File after = new File(parent, displayName);
1267 if (after.exists()) {
1268 throw new IllegalStateException("Already exists " + after);
1270 if (!before.renameTo(after)) {
1271 throw new IllegalStateException("Failed to rename to " + after);
1274 // Update MediaProvider if necessary
1275 if (mimeType.startsWith("image/")) {
1276 context.getContentResolver().delete(Images.Media.EXTERNAL_CONTENT_URI,
1277 Images.Media.DATA + "=?",
1279 before.getAbsolutePath()
1282 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
1283 intent.setData(Uri.fromFile(after));
1284 context.sendBroadcast(intent);
1287 ContentValues values = new ContentValues();
1288 values.put(Downloads.Impl.COLUMN_TITLE, displayName);
1289 values.put(Downloads.Impl._DATA, after.toString());
1290 values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
1293 return (mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
1294 getWhereArgsForIds(ids)) == 1);
1298 * Returns recommended maximum size, in bytes, of downloads that may go over a mobile
1299 * connection; or null if there's no recommended limit. The user will have the option to bypass
1302 * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1303 * @return recommended maximum size, in bytes, of downloads that may go over a mobile
1304 * connection; or null if there's no recommended limit.
1306 public static Long getRecommendedMaxBytesOverMobile(Context context) {
1308 return Settings.Global.getLong(context.getContentResolver(),
1309 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
1310 } catch (SettingNotFoundException exc) {
1316 public static boolean isActiveNetworkExpensive(Context context) {
1317 // TODO: connect to NetworkPolicyManager
1322 public static long getActiveNetworkWarningBytes(Context context) {
1323 // TODO: connect to NetworkPolicyManager
1328 * Adds a file to the downloads database system, so it could appear in Downloads App
1329 * (and thus become eligible for management by the Downloads App).
1331 * It is helpful to make the file scannable by MediaScanner by setting the param
1332 * isMediaScannerScannable to true. It makes the file visible in media managing
1333 * applications such as Gallery App, which could be a useful purpose of using this API.
1335 * @param title the title that would appear for this file in Downloads App.
1336 * @param description the description that would appear for this file in Downloads App.
1337 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1338 * scanned by MediaScanner appear in the applications used to view media (for example,
1340 * @param mimeType mimetype of the file.
1341 * @param path absolute pathname to the file. The file should be world-readable, so that it can
1342 * be managed by the Downloads App and any other app that is used to read it (for example,
1343 * Gallery app to display the file, if the file contents represent a video/image).
1344 * @param length length of the downloaded file
1345 * @param showNotification true if a notification is to be sent, false otherwise
1346 * @return an ID for the download entry added to the downloads app, unique across the system
1347 * This ID is used to make future calls related to this download.
1349 public long addCompletedDownload(String title, String description,
1350 boolean isMediaScannerScannable, String mimeType, String path, long length,
1351 boolean showNotification) {
1352 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1353 length, showNotification, false, null, null);
1357 * Adds a file to the downloads database system, so it could appear in Downloads App
1358 * (and thus become eligible for management by the Downloads App).
1360 * It is helpful to make the file scannable by MediaScanner by setting the param
1361 * isMediaScannerScannable to true. It makes the file visible in media managing
1362 * applications such as Gallery App, which could be a useful purpose of using this API.
1364 * @param title the title that would appear for this file in Downloads App.
1365 * @param description the description that would appear for this file in Downloads App.
1366 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1367 * scanned by MediaScanner appear in the applications used to view media (for example,
1369 * @param mimeType mimetype of the file.
1370 * @param path absolute pathname to the file. The file should be world-readable, so that it can
1371 * be managed by the Downloads App and any other app that is used to read it (for example,
1372 * Gallery app to display the file, if the file contents represent a video/image).
1373 * @param length length of the downloaded file
1374 * @param showNotification true if a notification is to be sent, false otherwise
1375 * @param uri the original HTTP URI of the download
1376 * @param referer the HTTP Referer for the download
1377 * @return an ID for the download entry added to the downloads app, unique across the system
1378 * This ID is used to make future calls related to this download.
1380 public long addCompletedDownload(String title, String description,
1381 boolean isMediaScannerScannable, String mimeType, String path, long length,
1382 boolean showNotification, Uri uri, Uri referer) {
1383 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1384 length, showNotification, false, uri, referer);
1388 public long addCompletedDownload(String title, String description,
1389 boolean isMediaScannerScannable, String mimeType, String path, long length,
1390 boolean showNotification, boolean allowWrite) {
1391 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1392 length, showNotification, allowWrite, null, null);
1396 public long addCompletedDownload(String title, String description,
1397 boolean isMediaScannerScannable, String mimeType, String path, long length,
1398 boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
1399 // make sure the input args are non-null/non-zero
1400 validateArgumentIsNonEmpty("title", title);
1401 validateArgumentIsNonEmpty("description", description);
1402 validateArgumentIsNonEmpty("path", path);
1403 validateArgumentIsNonEmpty("mimeType", mimeType);
1405 throw new IllegalArgumentException(" invalid value for param: totalBytes");
1408 // if there is already an entry with the given path name in downloads.db, return its id
1411 request = new Request(uri);
1413 request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD);
1415 request.setTitle(title)
1416 .setDescription(description)
1417 .setMimeType(mimeType);
1418 if (referer != null) {
1419 request.addRequestHeader("Referer", referer.toString());
1421 ContentValues values = request.toContentValues(null);
1422 values.put(Downloads.Impl.COLUMN_DESTINATION,
1423 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
1424 values.put(Downloads.Impl._DATA, path);
1425 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
1426 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
1427 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
1428 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
1429 Request.SCANNABLE_VALUE_NO);
1430 values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
1431 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
1432 values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0);
1433 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1434 if (downloadUri == null) {
1437 return Long.parseLong(downloadUri.getLastPathSegment());
1440 private static final String NON_DOWNLOADMANAGER_DOWNLOAD =
1441 "non-dwnldmngr-download-dont-retry2download";
1443 private static void validateArgumentIsNonEmpty(String paramName, String val) {
1444 if (TextUtils.isEmpty(val)) {
1445 throw new IllegalArgumentException(paramName + " can't be null");
1450 * Get the DownloadProvider URI for the download with the given ID.
1454 public Uri getDownloadUri(long id) {
1455 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
1459 * Get a parameterized SQL WHERE clause to select a bunch of IDs.
1461 static String getWhereClauseForIds(long[] ids) {
1462 StringBuilder whereClause = new StringBuilder();
1463 whereClause.append("(");
1464 for (int i = 0; i < ids.length; i++) {
1466 whereClause.append("OR ");
1468 whereClause.append(Downloads.Impl._ID);
1469 whereClause.append(" = ? ");
1471 whereClause.append(")");
1472 return whereClause.toString();
1476 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
1478 static String[] getWhereArgsForIds(long[] ids) {
1479 String[] whereArgs = new String[ids.length];
1480 return getWhereArgsForIds(ids, whereArgs);
1484 * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])}
1485 * and write it to the supplied args array.
1487 static String[] getWhereArgsForIds(long[] ids, String[] args) {
1488 assert(args.length >= ids.length);
1489 for (int i = 0; i < ids.length; i++) {
1490 args[i] = Long.toString(ids[i]);
1497 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
1498 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
1499 * Some columns correspond directly to underlying values while others are computed from
1502 private static class CursorTranslator extends CursorWrapper {
1503 private final Uri mBaseUri;
1504 private final boolean mAccessFilename;
1506 public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) {
1509 mAccessFilename = accessFilename;
1513 public int getInt(int columnIndex) {
1514 return (int) getLong(columnIndex);
1518 public long getLong(int columnIndex) {
1519 if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
1520 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1521 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
1522 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1524 return super.getLong(columnIndex);
1529 public String getString(int columnIndex) {
1530 final String columnName = getColumnName(columnIndex);
1531 switch (columnName) {
1532 case COLUMN_LOCAL_URI:
1533 return getLocalUri();
1534 case COLUMN_LOCAL_FILENAME:
1535 if (!mAccessFilename) {
1536 throw new SecurityException(
1537 "COLUMN_LOCAL_FILENAME is deprecated;"
1538 + " use ContentResolver.openFileDescriptor() instead");
1541 return super.getString(columnIndex);
1545 private String getLocalUri() {
1546 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
1547 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
1548 destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
1549 destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
1550 String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
1551 if (localPath == null) {
1554 return Uri.fromFile(new File(localPath)).toString();
1557 // return content URI for cache download
1558 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
1559 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString();
1562 private long getReason(int status) {
1563 switch (translateStatus(status)) {
1565 return getErrorCode(status);
1568 return getPausedReason(status);
1571 return 0; // arbitrary value when status is not an error
1575 private long getPausedReason(int status) {
1577 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1578 return PAUSED_WAITING_TO_RETRY;
1580 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1581 return PAUSED_WAITING_FOR_NETWORK;
1583 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1584 return PAUSED_QUEUED_FOR_WIFI;
1587 return PAUSED_UNKNOWN;
1591 private long getErrorCode(int status) {
1592 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
1593 || (500 <= status && status < 600)) {
1599 case Downloads.Impl.STATUS_FILE_ERROR:
1600 return ERROR_FILE_ERROR;
1602 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
1603 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
1604 return ERROR_UNHANDLED_HTTP_CODE;
1606 case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
1607 return ERROR_HTTP_DATA_ERROR;
1609 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
1610 return ERROR_TOO_MANY_REDIRECTS;
1612 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
1613 return ERROR_INSUFFICIENT_SPACE;
1615 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
1616 return ERROR_DEVICE_NOT_FOUND;
1618 case Downloads.Impl.STATUS_CANNOT_RESUME:
1619 return ERROR_CANNOT_RESUME;
1621 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
1622 return ERROR_FILE_ALREADY_EXISTS;
1625 return ERROR_UNKNOWN;
1629 private int translateStatus(int status) {
1631 case Downloads.Impl.STATUS_PENDING:
1632 return STATUS_PENDING;
1634 case Downloads.Impl.STATUS_RUNNING:
1635 return STATUS_RUNNING;
1637 case Downloads.Impl.STATUS_PAUSED_BY_APP:
1638 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1639 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1640 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1641 return STATUS_PAUSED;
1643 case Downloads.Impl.STATUS_SUCCESS:
1644 return STATUS_SUCCESSFUL;
1647 assert Downloads.Impl.isStatusError(status);
1648 return STATUS_FAILED;