• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package android.app;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.database.CursorWrapper;
25 import android.net.ConnectivityManager;
26 import android.net.NetworkPolicyManager;
27 import android.net.Uri;
28 import android.os.Environment;
29 import android.os.ParcelFileDescriptor;
30 import android.provider.Downloads;
31 import android.provider.Settings;
32 import android.provider.Settings.SettingNotFoundException;
33 import android.text.TextUtils;
34 import android.util.Pair;
35 
36 import java.io.File;
37 import java.io.FileNotFoundException;
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 /**
42  * The download manager is a system service that handles long-running HTTP downloads. Clients may
43  * request that a URI be downloaded to a particular destination file. The download manager will
44  * conduct the download in the background, taking care of HTTP interactions and retrying downloads
45  * after failures or across connectivity changes and system reboots.
46  *
47  * Instances of this class should be obtained through
48  * {@link android.content.Context#getSystemService(String)} by passing
49  * {@link android.content.Context#DOWNLOAD_SERVICE}.
50  *
51  * Apps that request downloads through this API should register a broadcast receiver for
52  * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
53  * download in a notification or from the downloads UI.
54  *
55  * Note that the application must have the {@link android.Manifest.permission#INTERNET}
56  * permission to use this class.
57  */
58 public class DownloadManager {
59 
60     /**
61      * An identifier for a particular download, unique across the system.  Clients use this ID to
62      * make subsequent calls related to the download.
63      */
64     public final static String COLUMN_ID = Downloads.Impl._ID;
65 
66     /**
67      * The client-supplied title for this download.  This will be displayed in system notifications.
68      * Defaults to the empty string.
69      */
70     public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE;
71 
72     /**
73      * The client-supplied description of this download.  This will be displayed in system
74      * notifications.  Defaults to the empty string.
75      */
76     public final static String COLUMN_DESCRIPTION = Downloads.Impl.COLUMN_DESCRIPTION;
77 
78     /**
79      * URI to be downloaded.
80      */
81     public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI;
82 
83     /**
84      * Internet Media Type of the downloaded file.  If no value is provided upon creation, this will
85      * initially be null and will be filled in based on the server's response once the download has
86      * started.
87      *
88      * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
89      */
90     public final static String COLUMN_MEDIA_TYPE = "media_type";
91 
92     /**
93      * Total size of the download in bytes.  This will initially be -1 and will be filled in once
94      * the download starts.
95      */
96     public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
97 
98     /**
99      * Uri where downloaded file will be stored.  If a destination is supplied by client, that URI
100      * will be used here.  Otherwise, the value will initially be null and will be filled in with a
101      * generated URI once the download has started.
102      */
103     public final static String COLUMN_LOCAL_URI = "local_uri";
104 
105     /**
106      * The pathname of the file where the download is stored.
107      */
108     public final static String COLUMN_LOCAL_FILENAME = "local_filename";
109 
110     /**
111      * Current status of the download, as one of the STATUS_* constants.
112      */
113     public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
114 
115     /**
116      * Provides more detail on the status of the download.  Its meaning depends on the value of
117      * {@link #COLUMN_STATUS}.
118      *
119      * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
120      * occurred.  If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
121      * 2616.  Otherwise, it will hold one of the ERROR_* constants.
122      *
123      * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
124      * paused.  It will hold one of the PAUSED_* constants.
125      *
126      * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
127      * column's value is undefined.
128      *
129      * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
130      * status codes</a>
131      */
132     public final static String COLUMN_REASON = "reason";
133 
134     /**
135      * Number of bytes download so far.
136      */
137     public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
138 
139     /**
140      * Timestamp when the download was last modified, in {@link System#currentTimeMillis
141      * System.currentTimeMillis()} (wall clock time in UTC).
142      */
143     public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
144 
145     /**
146      * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
147      * used to delete the entries from MediaProvider database when it is deleted from the
148      * downloaded list.
149      */
150     public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI;
151 
152     /**
153      * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
154      */
155     public final static int STATUS_PENDING = 1 << 0;
156 
157     /**
158      * Value of {@link #COLUMN_STATUS} when the download is currently running.
159      */
160     public final static int STATUS_RUNNING = 1 << 1;
161 
162     /**
163      * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume.
164      */
165     public final static int STATUS_PAUSED = 1 << 2;
166 
167     /**
168      * Value of {@link #COLUMN_STATUS} when the download has successfully completed.
169      */
170     public final static int STATUS_SUCCESSFUL = 1 << 3;
171 
172     /**
173      * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried).
174      */
175     public final static int STATUS_FAILED = 1 << 4;
176 
177     /**
178      * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit
179      * under any other error code.
180      */
181     public final static int ERROR_UNKNOWN = 1000;
182 
183     /**
184      * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
185      * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
186      * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
187      */
188     public final static int ERROR_FILE_ERROR = 1001;
189 
190     /**
191      * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
192      * can't handle.
193      */
194     public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
195 
196     /**
197      * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
198      * the HTTP level.
199      */
200     public final static int ERROR_HTTP_DATA_ERROR = 1004;
201 
202     /**
203      * Value of {@link #COLUMN_REASON} when there were too many redirects.
204      */
205     public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
206 
207     /**
208      * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
209      * this is because the SD card is full.
210      */
211     public final static int ERROR_INSUFFICIENT_SPACE = 1006;
212 
213     /**
214      * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
215      * this is because the SD card is not mounted.
216      */
217     public final static int ERROR_DEVICE_NOT_FOUND = 1007;
218 
219     /**
220      * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
221      * resume the download.
222      */
223     public final static int ERROR_CANNOT_RESUME = 1008;
224 
225     /**
226      * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
227      * download manager will not overwrite an existing file).
228      */
229     public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
230 
231     /**
232      * Value of {@link #COLUMN_REASON} when the download has failed because of
233      * {@link NetworkPolicyManager} controls on the requesting application.
234      *
235      * @hide
236      */
237     public final static int ERROR_BLOCKED = 1010;
238 
239     /**
240      * Value of {@link #COLUMN_REASON} when the download is paused because some network error
241      * occurred and the download manager is waiting before retrying the request.
242      */
243     public final static int PAUSED_WAITING_TO_RETRY = 1;
244 
245     /**
246      * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
247      * proceed.
248      */
249     public final static int PAUSED_WAITING_FOR_NETWORK = 2;
250 
251     /**
252      * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
253      * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
254      */
255     public final static int PAUSED_QUEUED_FOR_WIFI = 3;
256 
257     /**
258      * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
259      */
260     public final static int PAUSED_UNKNOWN = 4;
261 
262     /**
263      * Broadcast intent action sent by the download manager when a download completes.
264      */
265     public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
266 
267     /**
268      * Broadcast intent action sent by the download manager when the user clicks on a running
269      * download, either from a system notification or from the downloads UI.
270      */
271     public final static String ACTION_NOTIFICATION_CLICKED =
272             "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
273 
274     /**
275      * Intent action to launch an activity to display all downloads.
276      */
277     public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
278 
279     /**
280      * Intent extra included with {@link #ACTION_VIEW_DOWNLOADS} to start DownloadApp in
281      * sort-by-size mode.
282      */
283     public final static String INTENT_EXTRAS_SORT_BY_SIZE =
284             "android.app.DownloadManager.extra_sortBySize";
285 
286     /**
287      * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
288      * long) of the download that just completed.
289      */
290     public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
291 
292     /**
293      * When clicks on multiple notifications are received, the following
294      * provides an array of download ids corresponding to the download notification that was
295      * clicked. It can be retrieved by the receiver of this
296      * Intent using {@link android.content.Intent#getLongArrayExtra(String)}.
297      */
298     public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids";
299 
300     /**
301      * columns to request from DownloadProvider.
302      * @hide
303      */
304     public static final String[] UNDERLYING_COLUMNS = new String[] {
305         Downloads.Impl._ID,
306         Downloads.Impl._DATA + " AS " + COLUMN_LOCAL_FILENAME,
307         Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
308         Downloads.Impl.COLUMN_DESTINATION,
309         Downloads.Impl.COLUMN_TITLE,
310         Downloads.Impl.COLUMN_DESCRIPTION,
311         Downloads.Impl.COLUMN_URI,
312         Downloads.Impl.COLUMN_STATUS,
313         Downloads.Impl.COLUMN_FILE_NAME_HINT,
314         Downloads.Impl.COLUMN_MIME_TYPE + " AS " + COLUMN_MEDIA_TYPE,
315         Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + COLUMN_TOTAL_SIZE_BYTES,
316         Downloads.Impl.COLUMN_LAST_MODIFICATION + " AS " + COLUMN_LAST_MODIFIED_TIMESTAMP,
317         Downloads.Impl.COLUMN_CURRENT_BYTES + " AS " + COLUMN_BYTES_DOWNLOADED_SO_FAR,
318         /* add the following 'computed' columns to the cursor.
319          * they are not 'returned' by the database, but their inclusion
320          * eliminates need to have lot of methods in CursorTranslator
321          */
322         "'placeholder' AS " + COLUMN_LOCAL_URI,
323         "'placeholder' AS " + COLUMN_REASON
324     };
325 
326     /**
327      * This class contains all the information necessary to request a new download. The URI is the
328      * only required parameter.
329      *
330      * Note that the default download destination is a shared volume where the system might delete
331      * your file if it needs to reclaim space for system use. If this is a problem, use a location
332      * on external storage (see {@link #setDestinationUri(Uri)}.
333      */
334     public static class Request {
335         /**
336          * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
337          * {@link ConnectivityManager#TYPE_MOBILE}.
338          */
339         public static final int NETWORK_MOBILE = 1 << 0;
340 
341         /**
342          * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
343          * {@link ConnectivityManager#TYPE_WIFI}.
344          */
345         public static final int NETWORK_WIFI = 1 << 1;
346 
347         private Uri mUri;
348         private Uri mDestinationUri;
349         private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
350         private CharSequence mTitle;
351         private CharSequence mDescription;
352         private String mMimeType;
353         private int mAllowedNetworkTypes = ~0; // default to all network types allowed
354         private boolean mRoamingAllowed = true;
355         private boolean mMeteredAllowed = true;
356         private boolean mIsVisibleInDownloadsUi = true;
357         private boolean mScannable = false;
358         private boolean mUseSystemCache = false;
359         /** if a file is designated as a MediaScanner scannable file, the following value is
360          * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
361          */
362         private static final int SCANNABLE_VALUE_YES = 0;
363         // value of 1 is stored in the above column by DownloadProvider after it is scanned by
364         // MediaScanner
365         /** if a file is designated as a file that should not be scanned by MediaScanner,
366          * the following value is stored in the database column
367          * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
368          */
369         private static final int SCANNABLE_VALUE_NO = 2;
370 
371         /**
372          * This download is visible but only shows in the notifications
373          * while it's in progress.
374          */
375         public static final int VISIBILITY_VISIBLE = 0;
376 
377         /**
378          * This download is visible and shows in the notifications while
379          * in progress and after completion.
380          */
381         public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
382 
383         /**
384          * This download doesn't show in the UI or in the notifications.
385          */
386         public static final int VISIBILITY_HIDDEN = 2;
387 
388         /**
389          * This download shows in the notifications after completion ONLY.
390          * It is usuable only with
391          * {@link DownloadManager#addCompletedDownload(String, String,
392          * boolean, String, String, long, boolean)}.
393          */
394         public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3;
395 
396         /** can take any of the following values: {@link #VISIBILITY_HIDDEN}
397          * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE},
398          * {@link #VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION}
399          */
400         private int mNotificationVisibility = VISIBILITY_VISIBLE;
401 
402         /**
403          * @param uri the HTTP URI to download.
404          */
Request(Uri uri)405         public Request(Uri uri) {
406             if (uri == null) {
407                 throw new NullPointerException();
408             }
409             String scheme = uri.getScheme();
410             if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
411                 throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
412             }
413             mUri = uri;
414         }
415 
Request(String uriString)416         Request(String uriString) {
417             mUri = Uri.parse(uriString);
418         }
419 
420         /**
421          * Set the local destination for the downloaded file. Must be a file URI to a path on
422          * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE
423          * permission.
424          * <p>
425          * The downloaded file is not scanned by MediaScanner.
426          * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
427          * <p>
428          * By default, downloads are saved to a generated filename in the shared download cache and
429          * may be deleted by the system at any time to reclaim space.
430          *
431          * @return this object
432          */
setDestinationUri(Uri uri)433         public Request setDestinationUri(Uri uri) {
434             mDestinationUri = uri;
435             return this;
436         }
437 
438         /**
439          * Set the local destination for the downloaded file to the system cache dir (/cache).
440          * This is only available to System apps with the permission
441          * {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}.
442          * <p>
443          * The downloaded file is not scanned by MediaScanner.
444          * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
445          * <p>
446          * Files downloaded to /cache may be deleted by the system at any time to reclaim space.
447          *
448          * @return this object
449          * @hide
450          */
setDestinationToSystemCache()451         public Request setDestinationToSystemCache() {
452             mUseSystemCache = true;
453             return this;
454         }
455 
456         /**
457          * Set the local destination for the downloaded file to a path within the application's
458          * external files directory (as returned by {@link Context#getExternalFilesDir(String)}.
459          * <p>
460          * The downloaded file is not scanned by MediaScanner.
461          * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
462          *
463          * @param context the {@link Context} to use in determining the external files directory
464          * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)}
465          * @param subPath the path within the external directory, including the destination filename
466          * @return this object
467          */
setDestinationInExternalFilesDir(Context context, String dirType, String subPath)468         public Request setDestinationInExternalFilesDir(Context context, String dirType,
469                 String subPath) {
470             setDestinationFromBase(context.getExternalFilesDir(dirType), subPath);
471             return this;
472         }
473 
474         /**
475          * Set the local destination for the downloaded file to a path within the public external
476          * storage directory (as returned by
477          * {@link Environment#getExternalStoragePublicDirectory(String)}.
478          *<p>
479          * The downloaded file is not scanned by MediaScanner.
480          * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
481          *
482          * @param dirType the directory type to pass to
483          *        {@link Environment#getExternalStoragePublicDirectory(String)}
484          * @param subPath the path within the external directory, including the destination filename
485          * @return this object
486          */
setDestinationInExternalPublicDir(String dirType, String subPath)487         public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
488             File file = Environment.getExternalStoragePublicDirectory(dirType);
489             if (file.exists()) {
490                 if (!file.isDirectory()) {
491                     throw new IllegalStateException(file.getAbsolutePath() +
492                             " already exists and is not a directory");
493                 }
494             } else {
495                 if (!file.mkdir()) {
496                     throw new IllegalStateException("Unable to create directory: "+
497                             file.getAbsolutePath());
498                 }
499             }
500             setDestinationFromBase(file, subPath);
501             return this;
502         }
503 
setDestinationFromBase(File base, String subPath)504         private void setDestinationFromBase(File base, String subPath) {
505             if (subPath == null) {
506                 throw new NullPointerException("subPath cannot be null");
507             }
508             mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
509         }
510 
511         /**
512          * If the file to be downloaded is to be scanned by MediaScanner, this method
513          * should be called before {@link DownloadManager#enqueue(Request)} is called.
514          */
allowScanningByMediaScanner()515         public void allowScanningByMediaScanner() {
516             mScannable = true;
517         }
518 
519         /**
520          * Add an HTTP header to be included with the download request.  The header will be added to
521          * the end of the list.
522          * @param header HTTP header name
523          * @param value header value
524          * @return this object
525          * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1
526          *      Message Headers</a>
527          */
addRequestHeader(String header, String value)528         public Request addRequestHeader(String header, String value) {
529             if (header == null) {
530                 throw new NullPointerException("header cannot be null");
531             }
532             if (header.contains(":")) {
533                 throw new IllegalArgumentException("header may not contain ':'");
534             }
535             if (value == null) {
536                 value = "";
537             }
538             mRequestHeaders.add(Pair.create(header, value));
539             return this;
540         }
541 
542         /**
543          * Set the title of this download, to be displayed in notifications (if enabled).  If no
544          * title is given, a default one will be assigned based on the download filename, once the
545          * download starts.
546          * @return this object
547          */
setTitle(CharSequence title)548         public Request setTitle(CharSequence title) {
549             mTitle = title;
550             return this;
551         }
552 
553         /**
554          * Set a description of this download, to be displayed in notifications (if enabled)
555          * @return this object
556          */
setDescription(CharSequence description)557         public Request setDescription(CharSequence description) {
558             mDescription = description;
559             return this;
560         }
561 
562         /**
563          * Set the MIME content type of this download.  This will override the content type declared
564          * in the server's response.
565          * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1
566          *      Media Types</a>
567          * @return this object
568          */
setMimeType(String mimeType)569         public Request setMimeType(String mimeType) {
570             mMimeType = mimeType;
571             return this;
572         }
573 
574         /**
575          * Control whether a system notification is posted by the download manager while this
576          * download is running. If enabled, the download manager posts notifications about downloads
577          * through the system {@link android.app.NotificationManager}. By default, a notification is
578          * shown.
579          *
580          * If set to false, this requires the permission
581          * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
582          *
583          * @param show whether the download manager should show a notification for this download.
584          * @return this object
585          * @deprecated use {@link #setNotificationVisibility(int)}
586          */
587         @Deprecated
setShowRunningNotification(boolean show)588         public Request setShowRunningNotification(boolean show) {
589             return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) :
590                     setNotificationVisibility(VISIBILITY_HIDDEN);
591         }
592 
593         /**
594          * Control whether a system notification is posted by the download manager while this
595          * download is running or when it is completed.
596          * If enabled, the download manager posts notifications about downloads
597          * through the system {@link android.app.NotificationManager}.
598          * By default, a notification is shown only when the download is in progress.
599          *<p>
600          * It can take the following values: {@link #VISIBILITY_HIDDEN},
601          * {@link #VISIBILITY_VISIBLE},
602          * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}.
603          *<p>
604          * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission
605          * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
606          *
607          * @param visibility the visibility setting value
608          * @return this object
609          */
setNotificationVisibility(int visibility)610         public Request setNotificationVisibility(int visibility) {
611             mNotificationVisibility = visibility;
612             return this;
613         }
614 
615         /**
616          * Restrict the types of networks over which this download may proceed.
617          * By default, all network types are allowed. Consider using
618          * {@link #setAllowedOverMetered(boolean)} instead, since it's more
619          * flexible.
620          *
621          * @param flags any combination of the NETWORK_* bit flags.
622          * @return this object
623          */
setAllowedNetworkTypes(int flags)624         public Request setAllowedNetworkTypes(int flags) {
625             mAllowedNetworkTypes = flags;
626             return this;
627         }
628 
629         /**
630          * Set whether this download may proceed over a roaming connection.  By default, roaming is
631          * allowed.
632          * @param allowed whether to allow a roaming connection to be used
633          * @return this object
634          */
setAllowedOverRoaming(boolean allowed)635         public Request setAllowedOverRoaming(boolean allowed) {
636             mRoamingAllowed = allowed;
637             return this;
638         }
639 
640         /**
641          * Set whether this download may proceed over a metered network
642          * connection. By default, metered networks are allowed.
643          *
644          * @see ConnectivityManager#isActiveNetworkMetered()
645          */
setAllowedOverMetered(boolean allow)646         public Request setAllowedOverMetered(boolean allow) {
647             mMeteredAllowed = allow;
648             return this;
649         }
650 
651         /**
652          * Set whether this download should be displayed in the system's Downloads UI. True by
653          * default.
654          * @param isVisible whether to display this download in the Downloads UI
655          * @return this object
656          */
setVisibleInDownloadsUi(boolean isVisible)657         public Request setVisibleInDownloadsUi(boolean isVisible) {
658             mIsVisibleInDownloadsUi = isVisible;
659             return this;
660         }
661 
662         /**
663          * @return ContentValues to be passed to DownloadProvider.insert()
664          */
toContentValues(String packageName)665         ContentValues toContentValues(String packageName) {
666             ContentValues values = new ContentValues();
667             assert mUri != null;
668             values.put(Downloads.Impl.COLUMN_URI, mUri.toString());
669             values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
670             values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName);
671 
672             if (mDestinationUri != null) {
673                 values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
674                 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
675             } else {
676                 values.put(Downloads.Impl.COLUMN_DESTINATION,
677                            (this.mUseSystemCache) ?
678                                    Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION :
679                                    Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
680             }
681             // is the file supposed to be media-scannable?
682             values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES :
683                     SCANNABLE_VALUE_NO);
684 
685             if (!mRequestHeaders.isEmpty()) {
686                 encodeHttpHeaders(values);
687             }
688 
689             putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
690             putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
691             putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
692 
693             values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
694             values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
695             values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
696             values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed);
697             values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
698 
699             return values;
700         }
701 
encodeHttpHeaders(ContentValues values)702         private void encodeHttpHeaders(ContentValues values) {
703             int index = 0;
704             for (Pair<String, String> header : mRequestHeaders) {
705                 String headerString = header.first + ": " + header.second;
706                 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
707                 index++;
708             }
709         }
710 
putIfNonNull(ContentValues contentValues, String key, Object value)711         private void putIfNonNull(ContentValues contentValues, String key, Object value) {
712             if (value != null) {
713                 contentValues.put(key, value.toString());
714             }
715         }
716     }
717 
718     /**
719      * This class may be used to filter download manager queries.
720      */
721     public static class Query {
722         /**
723          * Constant for use with {@link #orderBy}
724          * @hide
725          */
726         public static final int ORDER_ASCENDING = 1;
727 
728         /**
729          * Constant for use with {@link #orderBy}
730          * @hide
731          */
732         public static final int ORDER_DESCENDING = 2;
733 
734         private long[] mIds = null;
735         private Integer mStatusFlags = null;
736         private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
737         private int mOrderDirection = ORDER_DESCENDING;
738         private boolean mOnlyIncludeVisibleInDownloadsUi = false;
739 
740         /**
741          * Include only the downloads with the given IDs.
742          * @return this object
743          */
setFilterById(long... ids)744         public Query setFilterById(long... ids) {
745             mIds = ids;
746             return this;
747         }
748 
749         /**
750          * Include only downloads with status matching any the given status flags.
751          * @param flags any combination of the STATUS_* bit flags
752          * @return this object
753          */
setFilterByStatus(int flags)754         public Query setFilterByStatus(int flags) {
755             mStatusFlags = flags;
756             return this;
757         }
758 
759         /**
760          * Controls whether this query includes downloads not visible in the system's Downloads UI.
761          * @param value if true, this query will only include downloads that should be displayed in
762          *            the system's Downloads UI; if false (the default), this query will include
763          *            both visible and invisible downloads.
764          * @return this object
765          * @hide
766          */
setOnlyIncludeVisibleInDownloadsUi(boolean value)767         public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
768             mOnlyIncludeVisibleInDownloadsUi = value;
769             return this;
770         }
771 
772         /**
773          * Change the sort order of the returned Cursor.
774          *
775          * @param column one of the COLUMN_* constants; currently, only
776          *         {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
777          *         supported.
778          * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
779          * @return this object
780          * @hide
781          */
orderBy(String column, int direction)782         public Query orderBy(String column, int direction) {
783             if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
784                 throw new IllegalArgumentException("Invalid direction: " + direction);
785             }
786 
787             if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
788                 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
789             } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
790                 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
791             } else {
792                 throw new IllegalArgumentException("Cannot order by " + column);
793             }
794             mOrderDirection = direction;
795             return this;
796         }
797 
798         /**
799          * Run this query using the given ContentResolver.
800          * @param projection the projection to pass to ContentResolver.query()
801          * @return the Cursor returned by ContentResolver.query()
802          */
runQuery(ContentResolver resolver, String[] projection, Uri baseUri)803         Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
804             Uri uri = baseUri;
805             List<String> selectionParts = new ArrayList<String>();
806             String[] selectionArgs = null;
807 
808             if (mIds != null) {
809                 selectionParts.add(getWhereClauseForIds(mIds));
810                 selectionArgs = getWhereArgsForIds(mIds);
811             }
812 
813             if (mStatusFlags != null) {
814                 List<String> parts = new ArrayList<String>();
815                 if ((mStatusFlags & STATUS_PENDING) != 0) {
816                     parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING));
817                 }
818                 if ((mStatusFlags & STATUS_RUNNING) != 0) {
819                     parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING));
820                 }
821                 if ((mStatusFlags & STATUS_PAUSED) != 0) {
822                     parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
823                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
824                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
825                     parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
826                 }
827                 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
828                     parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS));
829                 }
830                 if ((mStatusFlags & STATUS_FAILED) != 0) {
831                     parts.add("(" + statusClause(">=", 400)
832                               + " AND " + statusClause("<", 600) + ")");
833                 }
834                 selectionParts.add(joinStrings(" OR ", parts));
835             }
836 
837             if (mOnlyIncludeVisibleInDownloadsUi) {
838                 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
839             }
840 
841             // only return rows which are not marked 'deleted = 1'
842             selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
843 
844             String selection = joinStrings(" AND ", selectionParts);
845             String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
846             String orderBy = mOrderByColumn + " " + orderDirection;
847 
848             return resolver.query(uri, projection, selection, selectionArgs, orderBy);
849         }
850 
joinStrings(String joiner, Iterable<String> parts)851         private String joinStrings(String joiner, Iterable<String> parts) {
852             StringBuilder builder = new StringBuilder();
853             boolean first = true;
854             for (String part : parts) {
855                 if (!first) {
856                     builder.append(joiner);
857                 }
858                 builder.append(part);
859                 first = false;
860             }
861             return builder.toString();
862         }
863 
statusClause(String operator, int value)864         private String statusClause(String operator, int value) {
865             return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'";
866         }
867     }
868 
869     private ContentResolver mResolver;
870     private String mPackageName;
871     private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
872 
873     /**
874      * @hide
875      */
DownloadManager(ContentResolver resolver, String packageName)876     public DownloadManager(ContentResolver resolver, String packageName) {
877         mResolver = resolver;
878         mPackageName = packageName;
879     }
880 
881     /**
882      * Makes this object access the download provider through /all_downloads URIs rather than
883      * /my_downloads URIs, for clients that have permission to do so.
884      * @hide
885      */
setAccessAllDownloads(boolean accessAllDownloads)886     public void setAccessAllDownloads(boolean accessAllDownloads) {
887         if (accessAllDownloads) {
888             mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
889         } else {
890             mBaseUri = Downloads.Impl.CONTENT_URI;
891         }
892     }
893 
894     /**
895      * Enqueue a new download.  The download will start automatically once the download manager is
896      * ready to execute it and connectivity is available.
897      *
898      * @param request the parameters specifying this download
899      * @return an ID for the download, unique across the system.  This ID is used to make future
900      * calls related to this download.
901      */
enqueue(Request request)902     public long enqueue(Request request) {
903         ContentValues values = request.toContentValues(mPackageName);
904         Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
905         long id = Long.parseLong(downloadUri.getLastPathSegment());
906         return id;
907     }
908 
909     /**
910      * Marks the specified download as 'to be deleted'. This is done when a completed download
911      * is to be removed but the row was stored without enough info to delete the corresponding
912      * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
913      *
914      * @param ids the IDs of the downloads to be marked 'deleted'
915      * @return the number of downloads actually updated
916      * @hide
917      */
markRowDeleted(long... ids)918     public int markRowDeleted(long... ids) {
919         if (ids == null || ids.length == 0) {
920             // called with nothing to remove!
921             throw new IllegalArgumentException("input param 'ids' can't be null");
922         }
923         ContentValues values = new ContentValues();
924         values.put(Downloads.Impl.COLUMN_DELETED, 1);
925         // if only one id is passed in, then include it in the uri itself.
926         // this will eliminate a full database scan in the download service.
927         if (ids.length == 1) {
928             return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values,
929                     null, null);
930         }
931         return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
932                 getWhereArgsForIds(ids));
933     }
934 
935     /**
936      * Cancel downloads and remove them from the download manager.  Each download will be stopped if
937      * it was running, and it will no longer be accessible through the download manager.
938      * If there is a downloaded file, partial or complete, it is deleted.
939      *
940      * @param ids the IDs of the downloads to remove
941      * @return the number of downloads actually removed
942      */
remove(long... ids)943     public int remove(long... ids) {
944         return markRowDeleted(ids);
945     }
946 
947     /**
948      * Query the download manager about downloads that have been requested.
949      * @param query parameters specifying filters for this query
950      * @return a Cursor over the result set of downloads, with columns consisting of all the
951      * COLUMN_* constants.
952      */
query(Query query)953     public Cursor query(Query query) {
954         Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
955         if (underlyingCursor == null) {
956             return null;
957         }
958         return new CursorTranslator(underlyingCursor, mBaseUri);
959     }
960 
961     /**
962      * Open a downloaded file for reading.  The download must have completed.
963      * @param id the ID of the download
964      * @return a read-only {@link ParcelFileDescriptor}
965      * @throws FileNotFoundException if the destination file does not already exist
966      */
openDownloadedFile(long id)967     public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
968         return mResolver.openFileDescriptor(getDownloadUri(id), "r");
969     }
970 
971     /**
972      * Returns {@link Uri} for the given downloaded file id, if the file is
973      * downloaded successfully. otherwise, null is returned.
974      *<p>
975      * If the specified downloaded file is in external storage (for example, /sdcard dir),
976      * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds
977      * to the filepath on sdcard.
978      *
979      * @param id the id of the downloaded file.
980      * @return the {@link Uri} for the given downloaded file id, if download was successful. null
981      * otherwise.
982      */
getUriForDownloadedFile(long id)983     public Uri getUriForDownloadedFile(long id) {
984         // to check if the file is in cache, get its destination from the database
985         Query query = new Query().setFilterById(id);
986         Cursor cursor = null;
987         try {
988             cursor = query(query);
989             if (cursor == null) {
990                 return null;
991             }
992             if (cursor.moveToFirst()) {
993                 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
994                 if (DownloadManager.STATUS_SUCCESSFUL == status) {
995                     int indx = cursor.getColumnIndexOrThrow(
996                             Downloads.Impl.COLUMN_DESTINATION);
997                     int destination = cursor.getInt(indx);
998                     // TODO: if we ever add API to DownloadManager to let the caller specify
999                     // non-external storage for a downloaded file, then the following code
1000                     // should also check for that destination.
1001                     if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION ||
1002                             destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION ||
1003                             destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING ||
1004                             destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) {
1005                         // return private uri
1006                         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id);
1007                     } else {
1008                         // return public uri
1009                         String path = cursor.getString(
1010                                 cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME));
1011                         return Uri.fromFile(new File(path));
1012                     }
1013                 }
1014             }
1015         } finally {
1016             if (cursor != null) {
1017                 cursor.close();
1018             }
1019         }
1020         // downloaded file not found or its status is not 'successfully completed'
1021         return null;
1022     }
1023 
1024     /**
1025      * Returns {@link Uri} for the given downloaded file id, if the file is
1026      * downloaded successfully. otherwise, null is returned.
1027      *<p>
1028      * If the specified downloaded file is in external storage (for example, /sdcard dir),
1029      * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds
1030      * to the filepath on sdcard.
1031      *
1032      * @param id the id of the downloaded file.
1033      * @return the {@link Uri} for the given downloaded file id, if download was successful. null
1034      * otherwise.
1035      */
getMimeTypeForDownloadedFile(long id)1036     public String getMimeTypeForDownloadedFile(long id) {
1037         Query query = new Query().setFilterById(id);
1038         Cursor cursor = null;
1039         try {
1040             cursor = query(query);
1041             if (cursor == null) {
1042                 return null;
1043             }
1044             while (cursor.moveToFirst()) {
1045                 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1046             }
1047         } finally {
1048             if (cursor != null) {
1049                 cursor.close();
1050             }
1051         }
1052         // downloaded file not found or its status is not 'successfully completed'
1053         return null;
1054     }
1055 
1056     /**
1057      * Restart the given downloads, which must have already completed (successfully or not).  This
1058      * method will only work when called from within the download manager's process.
1059      * @param ids the IDs of the downloads
1060      * @hide
1061      */
restartDownload(long... ids)1062     public void restartDownload(long... ids) {
1063         Cursor cursor = query(new Query().setFilterById(ids));
1064         try {
1065             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1066                 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
1067                 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
1068                     throw new IllegalArgumentException("Cannot restart incomplete download: "
1069                             + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
1070                 }
1071             }
1072         } finally {
1073             cursor.close();
1074         }
1075 
1076         ContentValues values = new ContentValues();
1077         values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
1078         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
1079         values.putNull(Downloads.Impl._DATA);
1080         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1081         mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1082     }
1083 
1084     /**
1085      * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1086      * there's no limit
1087      *
1088      * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1089      * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1090      * there's no limit
1091      */
getMaxBytesOverMobile(Context context)1092     public static Long getMaxBytesOverMobile(Context context) {
1093         try {
1094             return Settings.Secure.getLong(context.getContentResolver(),
1095                     Settings.Secure.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
1096         } catch (SettingNotFoundException exc) {
1097             return null;
1098         }
1099     }
1100 
1101     /**
1102      * Returns recommended maximum size, in bytes, of downloads that may go over a mobile
1103      * connection; or null if there's no recommended limit.  The user will have the option to bypass
1104      * this limit.
1105      *
1106      * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1107      * @return recommended maximum size, in bytes, of downloads that may go over a mobile
1108      * connection; or null if there's no recommended limit.
1109      */
getRecommendedMaxBytesOverMobile(Context context)1110     public static Long getRecommendedMaxBytesOverMobile(Context context) {
1111         try {
1112             return Settings.Secure.getLong(context.getContentResolver(),
1113                     Settings.Secure.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
1114         } catch (SettingNotFoundException exc) {
1115             return null;
1116         }
1117     }
1118 
1119     /** {@hide} */
isActiveNetworkExpensive(Context context)1120     public static boolean isActiveNetworkExpensive(Context context) {
1121         // TODO: connect to NetworkPolicyManager
1122         return false;
1123     }
1124 
1125     /** {@hide} */
getActiveNetworkWarningBytes(Context context)1126     public static long getActiveNetworkWarningBytes(Context context) {
1127         // TODO: connect to NetworkPolicyManager
1128         return -1;
1129     }
1130 
1131     /**
1132      * Adds a file to the downloads database system, so it could appear in Downloads App
1133      * (and thus become eligible for management by the Downloads App).
1134      * <p>
1135      * It is helpful to make the file scannable by MediaScanner by setting the param
1136      * isMediaScannerScannable to true. It makes the file visible in media managing
1137      * applications such as Gallery App, which could be a useful purpose of using this API.
1138      *
1139      * @param title the title that would appear for this file in Downloads App.
1140      * @param description the description that would appear for this file in Downloads App.
1141      * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1142      * scanned by MediaScanner appear in the applications used to view media (for example,
1143      * Gallery app).
1144      * @param mimeType mimetype of the file.
1145      * @param path absolute pathname to the file. The file should be world-readable, so that it can
1146      * be managed by the Downloads App and any other app that is used to read it (for example,
1147      * Gallery app to display the file, if the file contents represent a video/image).
1148      * @param length length of the downloaded file
1149      * @param showNotification true if a notification is to be sent, false otherwise
1150      * @return  an ID for the download entry added to the downloads app, unique across the system
1151      * This ID is used to make future calls related to this download.
1152      */
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification)1153     public long addCompletedDownload(String title, String description,
1154             boolean isMediaScannerScannable, String mimeType, String path, long length,
1155             boolean showNotification) {
1156         // make sure the input args are non-null/non-zero
1157         validateArgumentIsNonEmpty("title", title);
1158         validateArgumentIsNonEmpty("description", description);
1159         validateArgumentIsNonEmpty("path", path);
1160         validateArgumentIsNonEmpty("mimeType", mimeType);
1161         if (length < 0) {
1162             throw new IllegalArgumentException(" invalid value for param: totalBytes");
1163         }
1164 
1165         // if there is already an entry with the given path name in downloads.db, return its id
1166         Request request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD)
1167                 .setTitle(title)
1168                 .setDescription(description)
1169                 .setMimeType(mimeType);
1170         ContentValues values = request.toContentValues(null);
1171         values.put(Downloads.Impl.COLUMN_DESTINATION,
1172                 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
1173         values.put(Downloads.Impl._DATA, path);
1174         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
1175         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
1176         values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
1177                 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
1178                         Request.SCANNABLE_VALUE_NO);
1179         values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
1180                 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
1181         Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1182         if (downloadUri == null) {
1183             return -1;
1184         }
1185         return Long.parseLong(downloadUri.getLastPathSegment());
1186     }
1187     private static final String NON_DOWNLOADMANAGER_DOWNLOAD =
1188             "non-dwnldmngr-download-dont-retry2download";
1189 
validateArgumentIsNonEmpty(String paramName, String val)1190     private static void validateArgumentIsNonEmpty(String paramName, String val) {
1191         if (TextUtils.isEmpty(val)) {
1192             throw new IllegalArgumentException(paramName + " can't be null");
1193         }
1194     }
1195 
1196     /**
1197      * Get the DownloadProvider URI for the download with the given ID.
1198      */
getDownloadUri(long id)1199     Uri getDownloadUri(long id) {
1200         return ContentUris.withAppendedId(mBaseUri, id);
1201     }
1202 
1203     /**
1204      * Get a parameterized SQL WHERE clause to select a bunch of IDs.
1205      */
getWhereClauseForIds(long[] ids)1206     static String getWhereClauseForIds(long[] ids) {
1207         StringBuilder whereClause = new StringBuilder();
1208         whereClause.append("(");
1209         for (int i = 0; i < ids.length; i++) {
1210             if (i > 0) {
1211                 whereClause.append("OR ");
1212             }
1213             whereClause.append(Downloads.Impl._ID);
1214             whereClause.append(" = ? ");
1215         }
1216         whereClause.append(")");
1217         return whereClause.toString();
1218     }
1219 
1220     /**
1221      * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
1222      */
getWhereArgsForIds(long[] ids)1223     static String[] getWhereArgsForIds(long[] ids) {
1224         String[] whereArgs = new String[ids.length];
1225         for (int i = 0; i < ids.length; i++) {
1226             whereArgs[i] = Long.toString(ids[i]);
1227         }
1228         return whereArgs;
1229     }
1230 
1231     /**
1232      * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
1233      * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
1234      * Some columns correspond directly to underlying values while others are computed from
1235      * underlying data.
1236      */
1237     private static class CursorTranslator extends CursorWrapper {
1238         private Uri mBaseUri;
1239 
CursorTranslator(Cursor cursor, Uri baseUri)1240         public CursorTranslator(Cursor cursor, Uri baseUri) {
1241             super(cursor);
1242             mBaseUri = baseUri;
1243         }
1244 
1245         @Override
getInt(int columnIndex)1246         public int getInt(int columnIndex) {
1247             return (int) getLong(columnIndex);
1248         }
1249 
1250         @Override
getLong(int columnIndex)1251         public long getLong(int columnIndex) {
1252             if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
1253                 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1254             } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
1255                 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1256             } else {
1257                 return super.getLong(columnIndex);
1258             }
1259         }
1260 
1261         @Override
getString(int columnIndex)1262         public String getString(int columnIndex) {
1263             return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() :
1264                     super.getString(columnIndex);
1265         }
1266 
getLocalUri()1267         private String getLocalUri() {
1268             long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
1269             if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
1270                     destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
1271                     destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
1272                 String localPath = getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
1273                 if (localPath == null) {
1274                     return null;
1275                 }
1276                 return Uri.fromFile(new File(localPath)).toString();
1277             }
1278 
1279             // return content URI for cache download
1280             long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
1281             return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
1282         }
1283 
getReason(int status)1284         private long getReason(int status) {
1285             switch (translateStatus(status)) {
1286                 case STATUS_FAILED:
1287                     return getErrorCode(status);
1288 
1289                 case STATUS_PAUSED:
1290                     return getPausedReason(status);
1291 
1292                 default:
1293                     return 0; // arbitrary value when status is not an error
1294             }
1295         }
1296 
getPausedReason(int status)1297         private long getPausedReason(int status) {
1298             switch (status) {
1299                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1300                     return PAUSED_WAITING_TO_RETRY;
1301 
1302                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1303                     return PAUSED_WAITING_FOR_NETWORK;
1304 
1305                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1306                     return PAUSED_QUEUED_FOR_WIFI;
1307 
1308                 default:
1309                     return PAUSED_UNKNOWN;
1310             }
1311         }
1312 
getErrorCode(int status)1313         private long getErrorCode(int status) {
1314             if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
1315                     || (500 <= status && status < 600)) {
1316                 // HTTP status code
1317                 return status;
1318             }
1319 
1320             switch (status) {
1321                 case Downloads.Impl.STATUS_FILE_ERROR:
1322                     return ERROR_FILE_ERROR;
1323 
1324                 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
1325                 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
1326                     return ERROR_UNHANDLED_HTTP_CODE;
1327 
1328                 case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
1329                     return ERROR_HTTP_DATA_ERROR;
1330 
1331                 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
1332                     return ERROR_TOO_MANY_REDIRECTS;
1333 
1334                 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
1335                     return ERROR_INSUFFICIENT_SPACE;
1336 
1337                 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
1338                     return ERROR_DEVICE_NOT_FOUND;
1339 
1340                 case Downloads.Impl.STATUS_CANNOT_RESUME:
1341                     return ERROR_CANNOT_RESUME;
1342 
1343                 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
1344                     return ERROR_FILE_ALREADY_EXISTS;
1345 
1346                 default:
1347                     return ERROR_UNKNOWN;
1348             }
1349         }
1350 
translateStatus(int status)1351         private int translateStatus(int status) {
1352             switch (status) {
1353                 case Downloads.Impl.STATUS_PENDING:
1354                     return STATUS_PENDING;
1355 
1356                 case Downloads.Impl.STATUS_RUNNING:
1357                     return STATUS_RUNNING;
1358 
1359                 case Downloads.Impl.STATUS_PAUSED_BY_APP:
1360                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1361                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1362                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1363                     return STATUS_PAUSED;
1364 
1365                 case Downloads.Impl.STATUS_SUCCESS:
1366                     return STATUS_SUCCESSFUL;
1367 
1368                 default:
1369                     assert Downloads.Impl.isStatusError(status);
1370                     return STATUS_FAILED;
1371             }
1372         }
1373     }
1374 }
1375