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