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