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