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