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