• 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.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 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 {@link Uri} for the given downloaded file id, if the file is
1015      * downloaded successfully. otherwise, null is returned.
1016      *<p>
1017      * If the specified downloaded file is in external storage (for example, /sdcard dir),
1018      * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds
1019      * to the filepath on sdcard.
1020      *
1021      * @param id the id of the downloaded file.
1022      * @return the {@link Uri} for the given downloaded file id, if download was successful. null
1023      * otherwise.
1024      */
getUriForDownloadedFile(long id)1025     public Uri getUriForDownloadedFile(long id) {
1026         // to check if the file is in cache, get its destination from the database
1027         Query query = new Query().setFilterById(id);
1028         Cursor cursor = null;
1029         try {
1030             cursor = query(query);
1031             if (cursor == null) {
1032                 return null;
1033             }
1034             if (cursor.moveToFirst()) {
1035                 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1036                 if (DownloadManager.STATUS_SUCCESSFUL == status) {
1037                     int indx = cursor.getColumnIndexOrThrow(
1038                             Downloads.Impl.COLUMN_DESTINATION);
1039                     int destination = cursor.getInt(indx);
1040                     // TODO: if we ever add API to DownloadManager to let the caller specify
1041                     // non-external storage for a downloaded file, then the following code
1042                     // should also check for that destination.
1043                     if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION ||
1044                             destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION ||
1045                             destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING ||
1046                             destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) {
1047                         // return private uri
1048                         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id);
1049                     } else {
1050                         // return public uri
1051                         String path = cursor.getString(
1052                                 cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME));
1053                         return Uri.fromFile(new File(path));
1054                     }
1055                 }
1056             }
1057         } finally {
1058             if (cursor != null) {
1059                 cursor.close();
1060             }
1061         }
1062         // downloaded file not found or its status is not 'successfully completed'
1063         return null;
1064     }
1065 
1066     /**
1067      * Returns {@link Uri} for the given downloaded file id, if the file is
1068      * downloaded successfully. otherwise, null is returned.
1069      *<p>
1070      * If the specified downloaded file is in external storage (for example, /sdcard dir),
1071      * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds
1072      * to the filepath on sdcard.
1073      *
1074      * @param id the id of the downloaded file.
1075      * @return the {@link Uri} for the given downloaded file id, if download was successful. null
1076      * otherwise.
1077      */
getMimeTypeForDownloadedFile(long id)1078     public String getMimeTypeForDownloadedFile(long id) {
1079         Query query = new Query().setFilterById(id);
1080         Cursor cursor = null;
1081         try {
1082             cursor = query(query);
1083             if (cursor == null) {
1084                 return null;
1085             }
1086             while (cursor.moveToFirst()) {
1087                 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1088             }
1089         } finally {
1090             if (cursor != null) {
1091                 cursor.close();
1092             }
1093         }
1094         // downloaded file not found or its status is not 'successfully completed'
1095         return null;
1096     }
1097 
1098     /**
1099      * Restart the given downloads, which must have already completed (successfully or not).  This
1100      * method will only work when called from within the download manager's process.
1101      * @param ids the IDs of the downloads
1102      * @hide
1103      */
restartDownload(long... ids)1104     public void restartDownload(long... ids) {
1105         Cursor cursor = query(new Query().setFilterById(ids));
1106         try {
1107             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1108                 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
1109                 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
1110                     throw new IllegalArgumentException("Cannot restart incomplete download: "
1111                             + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
1112                 }
1113             }
1114         } finally {
1115             cursor.close();
1116         }
1117 
1118         ContentValues values = new ContentValues();
1119         values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
1120         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
1121         values.putNull(Downloads.Impl._DATA);
1122         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1123         values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
1124         mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1125     }
1126 
1127     /**
1128      * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1129      * there's no limit
1130      *
1131      * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1132      * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1133      * there's no limit
1134      */
getMaxBytesOverMobile(Context context)1135     public static Long getMaxBytesOverMobile(Context context) {
1136         try {
1137             return Settings.Global.getLong(context.getContentResolver(),
1138                     Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
1139         } catch (SettingNotFoundException exc) {
1140             return null;
1141         }
1142     }
1143 
1144     /**
1145      * Returns recommended maximum size, in bytes, of downloads that may go over a mobile
1146      * connection; or null if there's no recommended limit.  The user will have the option to bypass
1147      * this limit.
1148      *
1149      * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1150      * @return recommended maximum size, in bytes, of downloads that may go over a mobile
1151      * connection; or null if there's no recommended limit.
1152      */
getRecommendedMaxBytesOverMobile(Context context)1153     public static Long getRecommendedMaxBytesOverMobile(Context context) {
1154         try {
1155             return Settings.Global.getLong(context.getContentResolver(),
1156                     Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
1157         } catch (SettingNotFoundException exc) {
1158             return null;
1159         }
1160     }
1161 
1162     /** {@hide} */
isActiveNetworkExpensive(Context context)1163     public static boolean isActiveNetworkExpensive(Context context) {
1164         // TODO: connect to NetworkPolicyManager
1165         return false;
1166     }
1167 
1168     /** {@hide} */
getActiveNetworkWarningBytes(Context context)1169     public static long getActiveNetworkWarningBytes(Context context) {
1170         // TODO: connect to NetworkPolicyManager
1171         return -1;
1172     }
1173 
1174     /**
1175      * Adds a file to the downloads database system, so it could appear in Downloads App
1176      * (and thus become eligible for management by the Downloads App).
1177      * <p>
1178      * It is helpful to make the file scannable by MediaScanner by setting the param
1179      * isMediaScannerScannable to true. It makes the file visible in media managing
1180      * applications such as Gallery App, which could be a useful purpose of using this API.
1181      *
1182      * @param title the title that would appear for this file in Downloads App.
1183      * @param description the description that would appear for this file in Downloads App.
1184      * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1185      * scanned by MediaScanner appear in the applications used to view media (for example,
1186      * Gallery app).
1187      * @param mimeType mimetype of the file.
1188      * @param path absolute pathname to the file. The file should be world-readable, so that it can
1189      * be managed by the Downloads App and any other app that is used to read it (for example,
1190      * Gallery app to display the file, if the file contents represent a video/image).
1191      * @param length length of the downloaded file
1192      * @param showNotification true if a notification is to be sent, false otherwise
1193      * @return  an ID for the download entry added to the downloads app, unique across the system
1194      * This ID is used to make future calls related to this download.
1195      */
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification)1196     public long addCompletedDownload(String title, String description,
1197             boolean isMediaScannerScannable, String mimeType, String path, long length,
1198             boolean showNotification) {
1199         return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1200                 length, showNotification, false);
1201     }
1202 
1203     /** {@hide} */
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, boolean allowWrite)1204     public long addCompletedDownload(String title, String description,
1205             boolean isMediaScannerScannable, String mimeType, String path, long length,
1206             boolean showNotification, boolean allowWrite) {
1207         // make sure the input args are non-null/non-zero
1208         validateArgumentIsNonEmpty("title", title);
1209         validateArgumentIsNonEmpty("description", description);
1210         validateArgumentIsNonEmpty("path", path);
1211         validateArgumentIsNonEmpty("mimeType", mimeType);
1212         if (length < 0) {
1213             throw new IllegalArgumentException(" invalid value for param: totalBytes");
1214         }
1215 
1216         // if there is already an entry with the given path name in downloads.db, return its id
1217         Request request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD)
1218                 .setTitle(title)
1219                 .setDescription(description)
1220                 .setMimeType(mimeType);
1221         ContentValues values = request.toContentValues(null);
1222         values.put(Downloads.Impl.COLUMN_DESTINATION,
1223                 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
1224         values.put(Downloads.Impl._DATA, path);
1225         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
1226         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
1227         values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
1228                 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
1229                         Request.SCANNABLE_VALUE_NO);
1230         values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
1231                 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
1232         values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0);
1233         Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1234         if (downloadUri == null) {
1235             return -1;
1236         }
1237         return Long.parseLong(downloadUri.getLastPathSegment());
1238     }
1239 
1240     private static final String NON_DOWNLOADMANAGER_DOWNLOAD =
1241             "non-dwnldmngr-download-dont-retry2download";
1242 
validateArgumentIsNonEmpty(String paramName, String val)1243     private static void validateArgumentIsNonEmpty(String paramName, String val) {
1244         if (TextUtils.isEmpty(val)) {
1245             throw new IllegalArgumentException(paramName + " can't be null");
1246         }
1247     }
1248 
1249     /**
1250      * Get the DownloadProvider URI for the download with the given ID.
1251      *
1252      * @hide
1253      */
getDownloadUri(long id)1254     public Uri getDownloadUri(long id) {
1255         return ContentUris.withAppendedId(mBaseUri, id);
1256     }
1257 
1258     /**
1259      * Get a parameterized SQL WHERE clause to select a bunch of IDs.
1260      */
getWhereClauseForIds(long[] ids)1261     static String getWhereClauseForIds(long[] ids) {
1262         StringBuilder whereClause = new StringBuilder();
1263         whereClause.append("(");
1264         for (int i = 0; i < ids.length; i++) {
1265             if (i > 0) {
1266                 whereClause.append("OR ");
1267             }
1268             whereClause.append(Downloads.Impl._ID);
1269             whereClause.append(" = ? ");
1270         }
1271         whereClause.append(")");
1272         return whereClause.toString();
1273     }
1274 
1275     /**
1276      * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
1277      */
getWhereArgsForIds(long[] ids)1278     static String[] getWhereArgsForIds(long[] ids) {
1279         String[] whereArgs = new String[ids.length];
1280         for (int i = 0; i < ids.length; i++) {
1281             whereArgs[i] = Long.toString(ids[i]);
1282         }
1283         return whereArgs;
1284     }
1285 
1286     /**
1287      * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
1288      * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
1289      * Some columns correspond directly to underlying values while others are computed from
1290      * underlying data.
1291      */
1292     private static class CursorTranslator extends CursorWrapper {
1293         private Uri mBaseUri;
1294 
CursorTranslator(Cursor cursor, Uri baseUri)1295         public CursorTranslator(Cursor cursor, Uri baseUri) {
1296             super(cursor);
1297             mBaseUri = baseUri;
1298         }
1299 
1300         @Override
getInt(int columnIndex)1301         public int getInt(int columnIndex) {
1302             return (int) getLong(columnIndex);
1303         }
1304 
1305         @Override
getLong(int columnIndex)1306         public long getLong(int columnIndex) {
1307             if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
1308                 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1309             } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
1310                 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1311             } else {
1312                 return super.getLong(columnIndex);
1313             }
1314         }
1315 
1316         @Override
getString(int columnIndex)1317         public String getString(int columnIndex) {
1318             return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() :
1319                     super.getString(columnIndex);
1320         }
1321 
getLocalUri()1322         private String getLocalUri() {
1323             long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
1324             if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
1325                     destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
1326                     destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
1327                 String localPath = getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
1328                 if (localPath == null) {
1329                     return null;
1330                 }
1331                 return Uri.fromFile(new File(localPath)).toString();
1332             }
1333 
1334             // return content URI for cache download
1335             long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
1336             return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
1337         }
1338 
getReason(int status)1339         private long getReason(int status) {
1340             switch (translateStatus(status)) {
1341                 case STATUS_FAILED:
1342                     return getErrorCode(status);
1343 
1344                 case STATUS_PAUSED:
1345                     return getPausedReason(status);
1346 
1347                 default:
1348                     return 0; // arbitrary value when status is not an error
1349             }
1350         }
1351 
getPausedReason(int status)1352         private long getPausedReason(int status) {
1353             switch (status) {
1354                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1355                     return PAUSED_WAITING_TO_RETRY;
1356 
1357                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1358                     return PAUSED_WAITING_FOR_NETWORK;
1359 
1360                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1361                     return PAUSED_QUEUED_FOR_WIFI;
1362 
1363                 default:
1364                     return PAUSED_UNKNOWN;
1365             }
1366         }
1367 
getErrorCode(int status)1368         private long getErrorCode(int status) {
1369             if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
1370                     || (500 <= status && status < 600)) {
1371                 // HTTP status code
1372                 return status;
1373             }
1374 
1375             switch (status) {
1376                 case Downloads.Impl.STATUS_FILE_ERROR:
1377                     return ERROR_FILE_ERROR;
1378 
1379                 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
1380                 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
1381                     return ERROR_UNHANDLED_HTTP_CODE;
1382 
1383                 case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
1384                     return ERROR_HTTP_DATA_ERROR;
1385 
1386                 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
1387                     return ERROR_TOO_MANY_REDIRECTS;
1388 
1389                 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
1390                     return ERROR_INSUFFICIENT_SPACE;
1391 
1392                 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
1393                     return ERROR_DEVICE_NOT_FOUND;
1394 
1395                 case Downloads.Impl.STATUS_CANNOT_RESUME:
1396                     return ERROR_CANNOT_RESUME;
1397 
1398                 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
1399                     return ERROR_FILE_ALREADY_EXISTS;
1400 
1401                 default:
1402                     return ERROR_UNKNOWN;
1403             }
1404         }
1405 
translateStatus(int status)1406         private int translateStatus(int status) {
1407             switch (status) {
1408                 case Downloads.Impl.STATUS_PENDING:
1409                     return STATUS_PENDING;
1410 
1411                 case Downloads.Impl.STATUS_RUNNING:
1412                     return STATUS_RUNNING;
1413 
1414                 case Downloads.Impl.STATUS_PAUSED_BY_APP:
1415                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1416                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1417                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1418                     return STATUS_PAUSED;
1419 
1420                 case Downloads.Impl.STATUS_SUCCESS:
1421                     return STATUS_SUCCESSFUL;
1422 
1423                 default:
1424                     assert Downloads.Impl.isStatusError(status);
1425                     return STATUS_FAILED;
1426             }
1427         }
1428     }
1429 }
1430