• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import android.annotation.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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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          * All non-visible downloads that are not modified in the last 7 days will be deleted during
497          * idle runs.
498          *
499          * @param uri a file {@link Uri} indicating the destination for the downloaded file.
500          * @return this object
501          */
setDestinationUri(Uri uri)502         public Request setDestinationUri(Uri uri) {
503             mDestinationUri = uri;
504             return this;
505         }
506 
507         /**
508          * Set the local destination for the downloaded file to a path within
509          * the application's external files directory (as returned by
510          * {@link Context#getExternalFilesDir(String)}.
511          * <p>
512          * The downloaded file is not scanned by MediaScanner. But it can be
513          * made scannable by calling {@link #allowScanningByMediaScanner()}.
514          *
515          * @param context the {@link Context} to use in determining the external
516          *            files directory
517          * @param dirType the directory type to pass to
518          *            {@link Context#getExternalFilesDir(String)}
519          * @param subPath the path within the external directory, including the
520          *            destination filename
521          * @return this object
522          * @throws IllegalStateException If the external storage directory
523          *             cannot be found or created.
524          */
setDestinationInExternalFilesDir(Context context, String dirType, String subPath)525         public Request setDestinationInExternalFilesDir(Context context, String dirType,
526                 String subPath) {
527             final File file = context.getExternalFilesDir(dirType);
528             if (file == null) {
529                 throw new IllegalStateException("Failed to get external storage files directory");
530             } else if (file.exists()) {
531                 if (!file.isDirectory()) {
532                     throw new IllegalStateException(file.getAbsolutePath() +
533                             " already exists and is not a directory");
534                 }
535             } else {
536                 if (!file.mkdirs()) {
537                     throw new IllegalStateException("Unable to create directory: "+
538                             file.getAbsolutePath());
539                 }
540             }
541             setDestinationFromBase(file, subPath);
542             return this;
543         }
544 
545         /**
546          * Set the local destination for the downloaded file to a path within
547          * the public external storage directory (as returned by
548          * {@link Environment#getExternalStoragePublicDirectory(String)}).
549          * <p>
550          * The downloaded file is not scanned by MediaScanner. But it can be
551          * made scannable by calling {@link #allowScanningByMediaScanner()}.
552          *
553          * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
554          * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE}
555          * permission is not needed and the {@code dirType} must be one of the known public
556          * directories like {@link Environment#DIRECTORY_DOWNLOADS},
557          * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES}, etc.
558          *
559          * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)}
560          * @param subPath the path within the external directory, including the
561          *            destination filename
562          * @return this object
563          * @throws IllegalStateException If the external storage directory
564          *             cannot be found or created.
565          */
setDestinationInExternalPublicDir(String dirType, String subPath)566         public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
567             File file = Environment.getExternalStoragePublicDirectory(dirType);
568             if (file == null) {
569                 throw new IllegalStateException("Failed to get external storage public directory");
570             }
571 
572             final Context context = AppGlobals.getInitialApplication();
573             if (context.getApplicationInfo().targetSdkVersion
574                     >= Build.VERSION_CODES.Q || !Environment.isExternalStorageLegacy()) {
575                 try (ContentProviderClient client = context.getContentResolver()
576                         .acquireContentProviderClient(Downloads.Impl.AUTHORITY)) {
577                     final Bundle extras = new Bundle();
578                     extras.putString(Downloads.DIR_TYPE, dirType);
579                     client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras);
580                 } catch (RemoteException e) {
581                     throw new IllegalStateException(
582                         "Unable to create directory: " + file.getAbsolutePath(),
583                         e);
584                 }
585             } else {
586                 if (file.exists()) {
587                     if (!file.isDirectory()) {
588                         throw new IllegalStateException(file.getAbsolutePath()
589                                 + " already exists and is not a directory");
590                     }
591                 } else if (!file.mkdirs()) {
592                     throw new IllegalStateException("Unable to create directory: "
593                             + file.getAbsolutePath());
594                 }
595             }
596             setDestinationFromBase(file, subPath);
597             return this;
598         }
599 
setDestinationFromBase(File base, String subPath)600         private void setDestinationFromBase(File base, String subPath) {
601             if (subPath == null) {
602                 throw new NullPointerException("subPath cannot be null");
603             }
604             mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
605         }
606 
607         /**
608          * If the file to be downloaded is to be scanned by MediaScanner, this method
609          * should be called before {@link DownloadManager#enqueue(Request)} is called.
610          *
611          * @deprecated Starting in Q, this value is ignored. Files downloaded to
612          * directories owned by applications (e.g. {@link Context#getExternalFilesDir(String)})
613          * will not be scanned by MediaScanner and the rest will be scanned.
614          */
615         @Deprecated
allowScanningByMediaScanner()616         public void allowScanningByMediaScanner() {
617             mScannable = true;
618         }
619 
620         /**
621          * Add an HTTP header to be included with the download request.  The header will be added to
622          * the end of the list.
623          * @param header HTTP header name
624          * @param value header value
625          * @return this object
626          * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1
627          *      Message Headers</a>
628          */
addRequestHeader(String header, String value)629         public Request addRequestHeader(String header, String value) {
630             if (header == null) {
631                 throw new NullPointerException("header cannot be null");
632             }
633             if (header.contains(":")) {
634                 throw new IllegalArgumentException("header may not contain ':'");
635             }
636             if (value == null) {
637                 value = "";
638             }
639             mRequestHeaders.add(Pair.create(header, value));
640             return this;
641         }
642 
643         /**
644          * Set the title of this download, to be displayed in notifications (if enabled).  If no
645          * title is given, a default one will be assigned based on the download filename, once the
646          * download starts.
647          * @return this object
648          */
setTitle(CharSequence title)649         public Request setTitle(CharSequence title) {
650             mTitle = title;
651             return this;
652         }
653 
654         /**
655          * Set a description of this download, to be displayed in notifications (if enabled)
656          * @return this object
657          */
setDescription(CharSequence description)658         public Request setDescription(CharSequence description) {
659             mDescription = description;
660             return this;
661         }
662 
663         /**
664          * Set the MIME content type of this download.  This will override the content type declared
665          * in the server's response.
666          * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1
667          *      Media Types</a>
668          * @return this object
669          */
setMimeType(String mimeType)670         public Request setMimeType(String mimeType) {
671             mMimeType = mimeType;
672             return this;
673         }
674 
675         /**
676          * Control whether a system notification is posted by the download manager while this
677          * download is running. If enabled, the download manager posts notifications about downloads
678          * through the system {@link android.app.NotificationManager}. By default, a notification is
679          * shown.
680          *
681          * If set to false, this requires the permission
682          * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
683          *
684          * @param show whether the download manager should show a notification for this download.
685          * @return this object
686          * @deprecated use {@link #setNotificationVisibility(int)}
687          */
688         @Deprecated
setShowRunningNotification(boolean show)689         public Request setShowRunningNotification(boolean show) {
690             return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) :
691                     setNotificationVisibility(VISIBILITY_HIDDEN);
692         }
693 
694         /**
695          * Control whether a system notification is posted by the download manager while this
696          * download is running or when it is completed.
697          * If enabled, the download manager posts notifications about downloads
698          * through the system {@link android.app.NotificationManager}.
699          * By default, a notification is shown only when the download is in progress.
700          *<p>
701          * It can take the following values: {@link #VISIBILITY_HIDDEN},
702          * {@link #VISIBILITY_VISIBLE},
703          * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}.
704          *<p>
705          * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission
706          * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
707          *
708          * @param visibility the visibility setting value
709          * @return this object
710          */
setNotificationVisibility(int visibility)711         public Request setNotificationVisibility(int visibility) {
712             mNotificationVisibility = visibility;
713             return this;
714         }
715 
716         /**
717          * Restrict the types of networks over which this download may proceed.
718          * By default, all network types are allowed. Consider using
719          * {@link #setAllowedOverMetered(boolean)} instead, since it's more
720          * flexible.
721          * <p>
722          * As of {@link android.os.Build.VERSION_CODES#N}, setting only the
723          * {@link #NETWORK_WIFI} flag here is equivalent to calling
724          * {@link #setAllowedOverMetered(boolean)} with {@code false}.
725          *
726          * @param flags any combination of the NETWORK_* bit flags.
727          * @return this object
728          */
setAllowedNetworkTypes(int flags)729         public Request setAllowedNetworkTypes(int flags) {
730             mAllowedNetworkTypes = flags;
731             return this;
732         }
733 
734         /**
735          * Set whether this download may proceed over a roaming connection.  By default, roaming is
736          * allowed.
737          * @param allowed whether to allow a roaming connection to be used
738          * @return this object
739          */
setAllowedOverRoaming(boolean allowed)740         public Request setAllowedOverRoaming(boolean allowed) {
741             mRoamingAllowed = allowed;
742             return this;
743         }
744 
745         /**
746          * Set whether this download may proceed over a metered network
747          * connection. By default, metered networks are allowed.
748          *
749          * @see ConnectivityManager#isActiveNetworkMetered()
750          */
setAllowedOverMetered(boolean allow)751         public Request setAllowedOverMetered(boolean allow) {
752             mMeteredAllowed = allow;
753             return this;
754         }
755 
756         /**
757          * Specify that to run this download, the device needs to be plugged in.
758          * This defaults to false.
759          *
760          * @param requiresCharging Whether or not the device is plugged in.
761          * @see android.app.job.JobInfo.Builder#setRequiresCharging(boolean)
762          */
setRequiresCharging(boolean requiresCharging)763         public Request setRequiresCharging(boolean requiresCharging) {
764             if (requiresCharging) {
765                 mFlags |= Downloads.Impl.FLAG_REQUIRES_CHARGING;
766             } else {
767                 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_CHARGING;
768             }
769             return this;
770         }
771 
772         /**
773          * Specify that to run, the download needs the device to be in idle
774          * mode. This defaults to false.
775          * <p>
776          * Idle mode is a loose definition provided by the system, which means
777          * that the device is not in use, and has not been in use for some time.
778          *
779          * @param requiresDeviceIdle Whether or not the device need be within an
780          *            idle maintenance window.
781          * @see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)
782          */
setRequiresDeviceIdle(boolean requiresDeviceIdle)783         public Request setRequiresDeviceIdle(boolean requiresDeviceIdle) {
784             if (requiresDeviceIdle) {
785                 mFlags |= Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
786             } else {
787                 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
788             }
789             return this;
790         }
791 
792         /**
793          * Set whether this download should be displayed in the system's Downloads UI. True by
794          * default.
795          * @param isVisible whether to display this download in the Downloads UI
796          * @return this object
797          *
798          * @deprecated Starting in Q, this value is ignored. Only files downloaded to
799          * public Downloads directory (as returned by
800          * {@link Environment#getExternalStoragePublicDirectory(String)} with
801          * {@link Environment#DIRECTORY_DOWNLOADS}) will be visible in system's Downloads UI
802          * and the rest will not be visible. All non-visible downloads that are not modified
803          * in the last 7 days will be deleted during idle runs.
804          *
805          * (e.g. {@link Context#getExternalFilesDir(String)}) will not be visible.
806          */
807         @Deprecated
setVisibleInDownloadsUi(boolean isVisible)808         public Request setVisibleInDownloadsUi(boolean isVisible) {
809             mIsVisibleInDownloadsUi = isVisible;
810             return this;
811         }
812 
813         /**
814          * @return ContentValues to be passed to DownloadProvider.insert()
815          */
toContentValues(String packageName)816         ContentValues toContentValues(String packageName) {
817             ContentValues values = new ContentValues();
818             assert mUri != null;
819             values.put(Downloads.Impl.COLUMN_URI, mUri.toString());
820             values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
821             values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName);
822 
823             if (mDestinationUri != null) {
824                 values.put(Downloads.Impl.COLUMN_DESTINATION,
825                         Downloads.Impl.DESTINATION_FILE_URI);
826                 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT,
827                         mDestinationUri.toString());
828             } else {
829                 values.put(Downloads.Impl.COLUMN_DESTINATION,
830                         Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
831             }
832             // is the file supposed to be media-scannable?
833             values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES :
834                     SCANNABLE_VALUE_NO);
835 
836             if (!mRequestHeaders.isEmpty()) {
837                 encodeHttpHeaders(values);
838             }
839 
840             putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
841             putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
842             putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
843 
844             values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
845             values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
846             values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
847             values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed);
848             values.put(Downloads.Impl.COLUMN_FLAGS, mFlags);
849             values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
850 
851             return values;
852         }
853 
encodeHttpHeaders(ContentValues values)854         private void encodeHttpHeaders(ContentValues values) {
855             int index = 0;
856             for (Pair<String, String> header : mRequestHeaders) {
857                 String headerString = header.first + ": " + header.second;
858                 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
859                 index++;
860             }
861         }
862 
putIfNonNull(ContentValues contentValues, String key, Object value)863         private void putIfNonNull(ContentValues contentValues, String key, Object value) {
864             if (value != null) {
865                 contentValues.put(key, value.toString());
866             }
867         }
868     }
869 
870     /**
871      * This class may be used to filter download manager queries.
872      */
873     public static class Query {
874         /**
875          * Constant for use with {@link #orderBy}
876          * @hide
877          */
878         public static final int ORDER_ASCENDING = 1;
879 
880         /**
881          * Constant for use with {@link #orderBy}
882          * @hide
883          */
884         public static final int ORDER_DESCENDING = 2;
885 
886         private long[] mIds = null;
887         private Integer mStatusFlags = null;
888         private String mFilterString = null;
889         private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
890         private int mOrderDirection = ORDER_DESCENDING;
891         private boolean mOnlyIncludeVisibleInDownloadsUi = false;
892 
893         /**
894          * Include only the downloads with the given IDs.
895          * @return this object
896          */
setFilterById(long... ids)897         public Query setFilterById(long... ids) {
898             mIds = ids;
899             return this;
900         }
901 
902         /**
903          *
904          * Include only the downloads that contains the given string in its name.
905          * @return this object
906          * @hide
907          */
setFilterByString(@ullable String filter)908         public Query setFilterByString(@Nullable String filter) {
909             mFilterString = filter;
910             return this;
911         }
912 
913         /**
914          * Include only downloads with status matching any the given status flags.
915          * @param flags any combination of the STATUS_* bit flags
916          * @return this object
917          */
setFilterByStatus(int flags)918         public Query setFilterByStatus(int flags) {
919             mStatusFlags = flags;
920             return this;
921         }
922 
923         /**
924          * Controls whether this query includes downloads not visible in the system's Downloads UI.
925          * @param value if true, this query will only include downloads that should be displayed in
926          *            the system's Downloads UI; if false (the default), this query will include
927          *            both visible and invisible downloads.
928          * @return this object
929          * @hide
930          */
931         @UnsupportedAppUsage
setOnlyIncludeVisibleInDownloadsUi(boolean value)932         public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
933             mOnlyIncludeVisibleInDownloadsUi = value;
934             return this;
935         }
936 
937         /**
938          * Change the sort order of the returned Cursor.
939          *
940          * @param column one of the COLUMN_* constants; currently, only
941          *         {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
942          *         supported.
943          * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
944          * @return this object
945          * @hide
946          */
947         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
orderBy(String column, int direction)948         public Query orderBy(String column, int direction) {
949             if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
950                 throw new IllegalArgumentException("Invalid direction: " + direction);
951             }
952 
953             if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
954                 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
955             } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
956                 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
957             } else {
958                 throw new IllegalArgumentException("Cannot order by " + column);
959             }
960             mOrderDirection = direction;
961             return this;
962         }
963 
964         /**
965          * Run this query using the given ContentResolver.
966          * @param projection the projection to pass to ContentResolver.query()
967          * @return the Cursor returned by ContentResolver.query()
968          */
runQuery(ContentResolver resolver, String[] projection, Uri baseUri)969         Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
970             Uri uri = baseUri;
971             List<String> selectionParts = new ArrayList<String>();
972             String[] selectionArgs = null;
973 
974             int whereArgsCount = (mIds == null) ? 0 : mIds.length;
975             whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1;
976             selectionArgs = new String[whereArgsCount];
977 
978             if (whereArgsCount > 0) {
979                 if (mIds != null) {
980                     selectionParts.add(getWhereClauseForIds(mIds));
981                     getWhereArgsForIds(mIds, selectionArgs);
982                 }
983 
984                 if (mFilterString != null) {
985                     selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?");
986                     selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%";
987                 }
988             }
989 
990             if (mStatusFlags != null) {
991                 List<String> parts = new ArrayList<String>();
992                 if ((mStatusFlags & STATUS_PENDING) != 0) {
993                     parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING));
994                 }
995                 if ((mStatusFlags & STATUS_RUNNING) != 0) {
996                     parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING));
997                 }
998                 if ((mStatusFlags & STATUS_PAUSED) != 0) {
999                     parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
1000                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
1001                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
1002                     parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
1003                 }
1004                 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
1005                     parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS));
1006                 }
1007                 if ((mStatusFlags & STATUS_FAILED) != 0) {
1008                     parts.add("(" + statusClause(">=", 400)
1009                               + " AND " + statusClause("<", 600) + ")");
1010                 }
1011                 selectionParts.add(joinStrings(" OR ", parts));
1012             }
1013 
1014             if (mOnlyIncludeVisibleInDownloadsUi) {
1015                 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
1016             }
1017 
1018             // only return rows which are not marked 'deleted = 1'
1019             selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
1020 
1021             String selection = joinStrings(" AND ", selectionParts);
1022             String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
1023             String orderBy = mOrderByColumn + " " + orderDirection;
1024 
1025             return resolver.query(uri, projection, selection, selectionArgs, orderBy);
1026         }
1027 
joinStrings(String joiner, Iterable<String> parts)1028         private String joinStrings(String joiner, Iterable<String> parts) {
1029             StringBuilder builder = new StringBuilder();
1030             boolean first = true;
1031             for (String part : parts) {
1032                 if (!first) {
1033                     builder.append(joiner);
1034                 }
1035                 builder.append(part);
1036                 first = false;
1037             }
1038             return builder.toString();
1039         }
1040 
statusClause(String operator, int value)1041         private String statusClause(String operator, int value) {
1042             return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'";
1043         }
1044     }
1045 
1046     private final ContentResolver mResolver;
1047     private final String mPackageName;
1048 
1049     private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
1050     private boolean mAccessFilename;
1051 
1052     /**
1053      * @hide
1054      */
DownloadManager(Context context)1055     public DownloadManager(Context context) {
1056         mResolver = context.getContentResolver();
1057         mPackageName = context.getPackageName();
1058 
1059         // Callers can access filename columns when targeting old platform
1060         // versions; otherwise we throw telling them it's deprecated.
1061         mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N;
1062     }
1063 
1064     /**
1065      * Makes this object access the download provider through /all_downloads URIs rather than
1066      * /my_downloads URIs, for clients that have permission to do so.
1067      * @hide
1068      */
1069     @UnsupportedAppUsage
1070     public void setAccessAllDownloads(boolean accessAllDownloads) {
1071         if (accessAllDownloads) {
1072             mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
1073         } else {
1074             mBaseUri = Downloads.Impl.CONTENT_URI;
1075         }
1076     }
1077 
1078     /** {@hide} */
1079     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1080     public void setAccessFilename(boolean accessFilename) {
1081         mAccessFilename = accessFilename;
1082     }
1083 
1084     /**
1085      * Notify {@link DownloadManager} that the given {@link MediaStore} items
1086      * were just deleted so that {@link DownloadManager} internal data
1087      * structures can be cleaned up.
1088      *
1089      * @param idToMime map from {@link BaseColumns#_ID} to
1090      *            {@link ContentResolver#getType(Uri)}.
1091      * @hide
1092      */
1093     @SystemApi
1094     @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
1095     public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
1096         try (ContentProviderClient client = mResolver
1097                 .acquireUnstableContentProviderClient(mBaseUri)) {
1098            final Bundle callExtras = new Bundle();
1099            final long[] ids = new long[idToMime.size()];
1100            final String[] mimeTypes = new String[idToMime.size()];
1101            for (int i = idToMime.size() - 1; i >= 0; --i) {
1102                ids[i] = idToMime.keyAt(i);
1103                mimeTypes[i] = idToMime.valueAt(i);
1104            }
1105            callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
1106            callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
1107                    mimeTypes);
1108            client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
1109                    null, callExtras);
1110         } catch (RemoteException e) {
1111             // Should not happen
1112         }
1113     }
1114 
1115     /**
1116      * Enqueue a new download.  The download will start automatically once the download manager is
1117      * ready to execute it and connectivity is available.
1118      *
1119      * @param request the parameters specifying this download
1120      * @return an ID for the download, unique across the system.  This ID is used to make
1121      * future calls related to this download. Returns -1 if the operation fails.
1122      */
enqueue(Request request)1123     public long enqueue(Request request) {
1124         ContentValues values = request.toContentValues(mPackageName);
1125         Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1126         if (downloadUri == null) {
1127             // If insert fails due to RemoteException, it would return a null uri.
1128             return -1;
1129         }
1130 
1131         long id = Long.parseLong(downloadUri.getLastPathSegment());
1132         return id;
1133     }
1134 
1135     /**
1136      * Marks the specified download as 'to be deleted'. This is done when a completed download
1137      * is to be removed but the row was stored without enough info to delete the corresponding
1138      * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
1139      *
1140      * @param ids the IDs of the downloads to be marked 'deleted'
1141      * @return the number of downloads actually updated
1142      * @hide
1143      */
markRowDeleted(long... ids)1144     public int markRowDeleted(long... ids) {
1145         if (ids == null || ids.length == 0) {
1146             // called with nothing to remove!
1147             throw new IllegalArgumentException("input param 'ids' can't be null");
1148         }
1149         return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1150     }
1151 
1152     /**
1153      * Cancel downloads and remove them from the download manager.  Each download will be stopped if
1154      * it was running, and it will no longer be accessible through the download manager.
1155      * If there is a downloaded file, partial or complete, it is deleted.
1156      *
1157      * @param ids the IDs of the downloads to remove
1158      * @return the number of downloads actually removed
1159      */
remove(long... ids)1160     public int remove(long... ids) {
1161         return markRowDeleted(ids);
1162     }
1163 
1164     /**
1165      * Query the download manager about downloads that have been requested.
1166      * @param query parameters specifying filters for this query
1167      * @return a Cursor over the result set of downloads, with columns consisting of all the
1168      * COLUMN_* constants.
1169      */
query(Query query)1170     public Cursor query(Query query) {
1171         return query(query, UNDERLYING_COLUMNS);
1172     }
1173 
1174     /** @hide */
query(Query query, String[] projection)1175     public Cursor query(Query query, String[] projection) {
1176         Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri);
1177         if (underlyingCursor == null) {
1178             return null;
1179         }
1180         return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename);
1181     }
1182 
1183     /**
1184      * Open a downloaded file for reading.  The download must have completed.
1185      * @param id the ID of the download
1186      * @return a read-only {@link ParcelFileDescriptor}
1187      * @throws FileNotFoundException if the destination file does not already exist
1188      */
openDownloadedFile(long id)1189     public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
1190         return mResolver.openFileDescriptor(getDownloadUri(id), "r");
1191     }
1192 
1193     /**
1194      * Returns the {@link Uri} of the given downloaded file id, if the file is
1195      * downloaded successfully. Otherwise, null is returned.
1196      *
1197      * @param id the id of the downloaded file.
1198      * @return the {@link Uri} of the given downloaded file id, if download was
1199      *         successful. null otherwise.
1200      */
getUriForDownloadedFile(long id)1201     public Uri getUriForDownloadedFile(long id) {
1202         // to check if the file is in cache, get its destination from the database
1203         Query query = new Query().setFilterById(id);
1204         Cursor cursor = null;
1205         try {
1206             cursor = query(query);
1207             if (cursor == null) {
1208                 return null;
1209             }
1210             if (cursor.moveToFirst()) {
1211                 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1212                 if (DownloadManager.STATUS_SUCCESSFUL == status) {
1213                     return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
1214                 }
1215             }
1216         } finally {
1217             if (cursor != null) {
1218                 cursor.close();
1219             }
1220         }
1221         // downloaded file not found or its status is not 'successfully completed'
1222         return null;
1223     }
1224 
1225     /**
1226      * Returns the media type of the given downloaded file id, if the file was
1227      * downloaded successfully. Otherwise, null is returned.
1228      *
1229      * @param id the id of the downloaded file.
1230      * @return the media type of the given downloaded file id, if download was successful. null
1231      * otherwise.
1232      */
getMimeTypeForDownloadedFile(long id)1233     public String getMimeTypeForDownloadedFile(long id) {
1234         Query query = new Query().setFilterById(id);
1235         Cursor cursor = null;
1236         try {
1237             cursor = query(query);
1238             if (cursor == null) {
1239                 return null;
1240             }
1241             while (cursor.moveToFirst()) {
1242                 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE));
1243             }
1244         } finally {
1245             if (cursor != null) {
1246                 cursor.close();
1247             }
1248         }
1249         // downloaded file not found or its status is not 'successfully completed'
1250         return null;
1251     }
1252 
1253     /**
1254      * Restart the given downloads, which must have already completed (successfully or not).  This
1255      * method will only work when called from within the download manager's process.
1256      * @param ids the IDs of the downloads
1257      * @hide
1258      */
1259     @UnsupportedAppUsage
restartDownload(long... ids)1260     public void restartDownload(long... ids) {
1261         Cursor cursor = query(new Query().setFilterById(ids));
1262         try {
1263             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
1264                 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
1265                 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
1266                     throw new IllegalArgumentException("Cannot restart incomplete download: "
1267                             + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
1268                 }
1269             }
1270         } finally {
1271             cursor.close();
1272         }
1273 
1274         ContentValues values = new ContentValues();
1275         values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
1276         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
1277         values.putNull(Downloads.Impl._DATA);
1278         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1279         values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
1280         mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1281     }
1282 
1283     /**
1284      * Force the given downloads to proceed even if their size is larger than
1285      * {@link #getMaxBytesOverMobile(Context)}.
1286      *
1287      * @hide
1288      */
forceDownload(long... ids)1289     public void forceDownload(long... ids) {
1290         ContentValues values = new ContentValues();
1291         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
1292         values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
1293         values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1);
1294         mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
1295     }
1296 
1297     /**
1298      * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1299      * there's no limit
1300      *
1301      * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1302      * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if
1303      * there's no limit
1304      */
getMaxBytesOverMobile(Context context)1305     public static Long getMaxBytesOverMobile(Context context) {
1306         try {
1307             return Settings.Global.getLong(context.getContentResolver(),
1308                     Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
1309         } catch (SettingNotFoundException exc) {
1310             return null;
1311         }
1312     }
1313 
1314     /**
1315      * Rename the given download if the download has completed
1316      *
1317      * @param context the {@link Context} to use in case need to update MediaProvider
1318      * @param id the downloaded id
1319      * @param displayName the new name to rename to
1320      * @return true if rename was successful, false otherwise
1321      * @hide
1322      */
rename(Context context, long id, String displayName)1323     public boolean rename(Context context, long id, String displayName) {
1324         if (!FileUtils.isValidFatFilename(displayName)) {
1325             throw new SecurityException(displayName + " is not a valid filename");
1326         }
1327 
1328         final String filePath;
1329         final Query query = new Query().setFilterById(id);
1330         try (Cursor cursor = query(query)) {
1331             if (cursor == null) {
1332                 throw new IllegalStateException("Missing cursor for download id=" + id);
1333             }
1334             if (cursor.moveToFirst()) {
1335                 final int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS));
1336                 if (status != DownloadManager.STATUS_SUCCESSFUL) {
1337                     throw new IllegalStateException("Download is not completed yet: "
1338                             + DatabaseUtils.dumpCurrentRowToString(cursor));
1339                 }
1340                 filePath = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME));
1341                 if (filePath == null) {
1342                     throw new IllegalStateException("Download doesn't have a valid file path: "
1343                             + DatabaseUtils.dumpCurrentRowToString(cursor));
1344                 } else if (!new File(filePath).exists()) {
1345                     throw new IllegalStateException("Downloaded file doesn't exist anymore: "
1346                             + DatabaseUtils.dumpCurrentRowToString(cursor));
1347                 }
1348             } else {
1349                 throw new IllegalStateException("Missing download id=" + id);
1350             }
1351         }
1352 
1353         final File before = new File(filePath);
1354         final File after = new File(before.getParentFile(), displayName);
1355 
1356         if (after.exists()) {
1357             throw new IllegalStateException("File already exists: " + after);
1358         }
1359         if (!before.renameTo(after)) {
1360             throw new IllegalStateException(
1361                     "Failed to rename file from " + before + " to " + after);
1362         }
1363 
1364         // TODO: DownloadProvider.update() should take care of updating corresponding
1365         // MediaProvider entries.
1366         MediaStore.scanFile(mResolver, before);
1367         MediaStore.scanFile(mResolver, after);
1368 
1369         final ContentValues values = new ContentValues();
1370         values.put(Downloads.Impl.COLUMN_TITLE, displayName);
1371         values.put(Downloads.Impl._DATA, after.toString());
1372         values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
1373         final long[] ids = { id };
1374 
1375         return mResolver.update(
1376                 mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)) == 1;
1377     }
1378 
1379     /**
1380      * Returns recommended maximum size, in bytes, of downloads that may go over a mobile
1381      * connection; or null if there's no recommended limit.  The user will have the option to bypass
1382      * this limit.
1383      *
1384      * @param context the {@link Context} to use for accessing the {@link ContentResolver}
1385      * @return recommended maximum size, in bytes, of downloads that may go over a mobile
1386      * connection; or null if there's no recommended limit.
1387      */
getRecommendedMaxBytesOverMobile(Context context)1388     public static Long getRecommendedMaxBytesOverMobile(Context context) {
1389         try {
1390             return Settings.Global.getLong(context.getContentResolver(),
1391                     Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
1392         } catch (SettingNotFoundException exc) {
1393             return null;
1394         }
1395     }
1396 
1397     /** {@hide} */
isActiveNetworkExpensive(Context context)1398     public static boolean isActiveNetworkExpensive(Context context) {
1399         // TODO: connect to NetworkPolicyManager
1400         return false;
1401     }
1402 
1403     /** {@hide} */
getActiveNetworkWarningBytes(Context context)1404     public static long getActiveNetworkWarningBytes(Context context) {
1405         // TODO: connect to NetworkPolicyManager
1406         return -1;
1407     }
1408 
1409     /**
1410      * Adds a file to the downloads database system, so it could appear in Downloads App
1411      * (and thus become eligible for management by the Downloads App).
1412      * <p>
1413      * It is helpful to make the file scannable by MediaScanner by setting the param
1414      * isMediaScannerScannable to true. It makes the file visible in media managing
1415      * applications such as Gallery App, which could be a useful purpose of using this API.
1416      *
1417      * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
1418      * {@code path} must be within directories owned by the application
1419      * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
1420      * the legacy storage model (see
1421      * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
1422      * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
1423      * Downloads directory (as returned by
1424      * {@link Environment#getExternalStoragePublicDirectory(String)} with
1425      * {@link Environment#DIRECTORY_DOWNLOADS}).
1426      *
1427      * @param title the title that would appear for this file in Downloads App.
1428      * @param description the description that would appear for this file in Downloads App.
1429      * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1430      * scanned by MediaScanner appear in the applications used to view media (for example,
1431      * Gallery app).
1432      * @param mimeType mimetype of the file.
1433      * @param path absolute pathname to the file. The file should be world-readable, so that it can
1434      * be managed by the Downloads App and any other app that is used to read it (for example,
1435      * Gallery app to display the file, if the file contents represent a video/image).
1436      * @param length length of the downloaded file
1437      * @param showNotification true if a notification is to be sent, false otherwise
1438      * @return  an ID for the download entry added to the downloads app, unique across the system
1439      * This ID is used to make future calls related to this download.
1440      *
1441      * @deprecated Apps should instead contribute files to
1442      * {@link android.provider.MediaStore.Downloads} collection to make them available to user
1443      * as part of Downloads.
1444      */
1445     @Deprecated
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification)1446     public long addCompletedDownload(String title, String description,
1447             boolean isMediaScannerScannable, String mimeType, String path, long length,
1448             boolean showNotification) {
1449         return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1450                 length, showNotification, false, null, null);
1451     }
1452 
1453     /**
1454      * Adds a file to the downloads database system, so it could appear in Downloads App
1455      * (and thus become eligible for management by the Downloads App).
1456      * <p>
1457      * It is helpful to make the file scannable by MediaScanner by setting the param
1458      * isMediaScannerScannable to true. It makes the file visible in media managing
1459      * applications such as Gallery App, which could be a useful purpose of using this API.
1460      *
1461      * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
1462      * {@code path} must be within directories owned by the application
1463      * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
1464      * the legacy storage model (see
1465      * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
1466      * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
1467      * Downloads directory (as returned by
1468      * {@link Environment#getExternalStoragePublicDirectory(String)} with
1469      * {@link Environment#DIRECTORY_DOWNLOADS}).
1470      *
1471      * @param title the title that would appear for this file in Downloads App.
1472      * @param description the description that would appear for this file in Downloads App.
1473      * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
1474      * scanned by MediaScanner appear in the applications used to view media (for example,
1475      * Gallery app).
1476      * @param mimeType mimetype of the file.
1477      * @param path absolute pathname to the file. The file should be world-readable, so that it can
1478      * be managed by the Downloads App and any other app that is used to read it (for example,
1479      * Gallery app to display the file, if the file contents represent a video/image).
1480      * @param length length of the downloaded file
1481      * @param showNotification true if a notification is to be sent, false otherwise
1482      * @param uri the original HTTP URI of the download
1483      * @param referer the HTTP Referer for the download
1484      * @return  an ID for the download entry added to the downloads app, unique across the system
1485      * This ID is used to make future calls related to this download.
1486      *
1487      * @deprecated Apps should instead contribute files to
1488      * {@link android.provider.MediaStore.Downloads} collection to make them available to user
1489      * as part of Downloads.
1490      */
1491     @Deprecated
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, Uri uri, Uri referer)1492     public long addCompletedDownload(String title, String description,
1493             boolean isMediaScannerScannable, String mimeType, String path, long length,
1494             boolean showNotification, Uri uri, Uri referer) {
1495         return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1496                 length, showNotification, false, uri, referer);
1497     }
1498 
1499     /**
1500      * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
1501      * {@code path} must be within directories owned by the application
1502      * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
1503      * the legacy storage model (see
1504      * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
1505      * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
1506      * Downloads directory (as returned by
1507      * {@link Environment#getExternalStoragePublicDirectory(String)} with
1508      * {@link Environment#DIRECTORY_DOWNLOADS}).
1509      *
1510      * @deprecated Apps should instead contribute files to
1511      * {@link android.provider.MediaStore.Downloads} collection to make them available to user
1512      * as part of Downloads.
1513      *
1514      * {@hide}
1515      */
1516     @Deprecated
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, boolean allowWrite)1517     public long addCompletedDownload(String title, String description,
1518             boolean isMediaScannerScannable, String mimeType, String path, long length,
1519             boolean showNotification, boolean allowWrite) {
1520         return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path,
1521                 length, showNotification, allowWrite, null, null);
1522     }
1523 
1524     /**
1525      * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
1526      * {@code path} must be within directories owned by the application
1527      * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
1528      * the legacy storage model (see
1529      * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
1530      * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
1531      * Downloads directory (as returned by
1532      * {@link Environment#getExternalStoragePublicDirectory(String)} with
1533      * {@link Environment#DIRECTORY_DOWNLOADS}).
1534      *
1535      * {@hide}
1536      *
1537      * @deprecated Apps should instead contribute files to
1538      * {@link android.provider.MediaStore.Downloads} collection to make them available to user
1539      * as part of Downloads.
1540      */
1541     @Deprecated
addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, boolean allowWrite, Uri uri, Uri referer)1542     public long addCompletedDownload(String title, String description,
1543             boolean isMediaScannerScannable, String mimeType, String path, long length,
1544             boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
1545         // make sure the input args are non-null/non-zero
1546         validateArgumentIsNonEmpty("title", title);
1547         validateArgumentIsNonEmpty("description", description);
1548         validateArgumentIsNonEmpty("path", path);
1549         validateArgumentIsNonEmpty("mimeType", mimeType);
1550         if (length < 0) {
1551             throw new IllegalArgumentException(" invalid value for param: totalBytes");
1552         }
1553 
1554         // if there is already an entry with the given path name in downloads.db, return its id
1555         Request request;
1556         if (uri != null) {
1557             request = new Request(uri);
1558         } else {
1559             request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD);
1560         }
1561         request.setTitle(title)
1562                 .setDescription(description)
1563                 .setMimeType(mimeType);
1564         if (referer != null) {
1565             request.addRequestHeader("Referer", referer.toString());
1566         }
1567         ContentValues values = request.toContentValues(null);
1568         values.put(Downloads.Impl.COLUMN_DESTINATION,
1569                 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD);
1570         values.put(Downloads.Impl._DATA, path);
1571         values.put(Downloads.Impl.COLUMN_MIME_TYPE, resolveMimeType(new File(path)));
1572         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
1573         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length);
1574         values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED,
1575                 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES :
1576                         Request.SCANNABLE_VALUE_NO);
1577         values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ?
1578                 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN);
1579         values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0);
1580         Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
1581         if (downloadUri == null) {
1582             return -1;
1583         }
1584         return Long.parseLong(downloadUri.getLastPathSegment());
1585     }
1586 
1587     /**
1588      * Shamelessly borrowed from
1589      * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/MimeUtils.java}
1590      *
1591      * @hide
1592      */
resolveMimeType(@onNull File file)1593     private static @NonNull String resolveMimeType(@NonNull File file) {
1594         final String extension = extractFileExtension(file.getPath());
1595         if (extension == null) return ClipDescription.MIMETYPE_UNKNOWN;
1596 
1597         final String mimeType = MimeTypeMap.getSingleton()
1598                 .getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT));
1599         if (mimeType == null) return ClipDescription.MIMETYPE_UNKNOWN;
1600 
1601         return mimeType;
1602     }
1603 
1604     /**
1605      * Shamelessly borrowed from
1606      * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java}
1607      *
1608      * @hide
1609      */
extractDisplayName(@ullable String data)1610     private static @Nullable String extractDisplayName(@Nullable String data) {
1611         if (data == null) return null;
1612         if (data.indexOf('/') == -1) {
1613             return data;
1614         }
1615         if (data.endsWith("/")) {
1616             data = data.substring(0, data.length() - 1);
1617         }
1618         return data.substring(data.lastIndexOf('/') + 1);
1619     }
1620 
1621     /**
1622      * Shamelessly borrowed from
1623      * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java}
1624      *
1625      * @hide
1626      */
extractFileExtension(@ullable String data)1627     private static @Nullable String extractFileExtension(@Nullable String data) {
1628         if (data == null) return null;
1629         data = extractDisplayName(data);
1630 
1631         final int lastDot = data.lastIndexOf('.');
1632         if (lastDot == -1) {
1633             return null;
1634         } else {
1635             return data.substring(lastDot + 1);
1636         }
1637     }
1638 
1639     private static final String NON_DOWNLOADMANAGER_DOWNLOAD =
1640             "non-dwnldmngr-download-dont-retry2download";
1641 
validateArgumentIsNonEmpty(String paramName, String val)1642     private static void validateArgumentIsNonEmpty(String paramName, String val) {
1643         if (TextUtils.isEmpty(val)) {
1644             throw new IllegalArgumentException(paramName + " can't be null");
1645         }
1646     }
1647 
1648     /**
1649      * Get the DownloadProvider URI for the download with the given ID.
1650      *
1651      * @hide
1652      */
getDownloadUri(long id)1653     public Uri getDownloadUri(long id) {
1654         return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id);
1655     }
1656 
1657     /**
1658      * Get a parameterized SQL WHERE clause to select a bunch of IDs.
1659      */
1660     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getWhereClauseForIds(long[] ids)1661     static String getWhereClauseForIds(long[] ids) {
1662         StringBuilder whereClause = new StringBuilder();
1663         whereClause.append("(");
1664         for (int i = 0; i < ids.length; i++) {
1665             if (i > 0) {
1666                 whereClause.append("OR ");
1667             }
1668             whereClause.append(Downloads.Impl._ID);
1669             whereClause.append(" = ? ");
1670         }
1671         whereClause.append(")");
1672         return whereClause.toString();
1673     }
1674 
1675     /**
1676      * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
1677      */
1678     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getWhereArgsForIds(long[] ids)1679     static String[] getWhereArgsForIds(long[] ids) {
1680         String[] whereArgs = new String[ids.length];
1681         return getWhereArgsForIds(ids, whereArgs);
1682     }
1683 
1684     /**
1685      * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])}
1686      * and write it to the supplied args array.
1687      */
getWhereArgsForIds(long[] ids, String[] args)1688     static String[] getWhereArgsForIds(long[] ids, String[] args) {
1689         assert(args.length >= ids.length);
1690         for (int i = 0; i < ids.length; i++) {
1691             args[i] = Long.toString(ids[i]);
1692         }
1693         return args;
1694     }
1695 
1696 
1697     /**
1698      * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
1699      * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
1700      * Some columns correspond directly to underlying values while others are computed from
1701      * underlying data.
1702      */
1703     private static class CursorTranslator extends CursorWrapper {
1704         private final Uri mBaseUri;
1705         private final boolean mAccessFilename;
1706 
CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename)1707         public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) {
1708             super(cursor);
1709             mBaseUri = baseUri;
1710             mAccessFilename = accessFilename;
1711         }
1712 
1713         @Override
getInt(int columnIndex)1714         public int getInt(int columnIndex) {
1715             return (int) getLong(columnIndex);
1716         }
1717 
1718         @Override
getLong(int columnIndex)1719         public long getLong(int columnIndex) {
1720             if (getColumnName(columnIndex).equals(COLUMN_REASON)) {
1721                 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1722             } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) {
1723                 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS)));
1724             } else {
1725                 return super.getLong(columnIndex);
1726             }
1727         }
1728 
1729         @Override
getString(int columnIndex)1730         public String getString(int columnIndex) {
1731             final String columnName = getColumnName(columnIndex);
1732             switch (columnName) {
1733                 case COLUMN_LOCAL_URI:
1734                     return getLocalUri();
1735                 case COLUMN_LOCAL_FILENAME:
1736                     if (!mAccessFilename) {
1737                         throw new SecurityException(
1738                                 "COLUMN_LOCAL_FILENAME is deprecated;"
1739                                         + " use ContentResolver.openFileDescriptor() instead");
1740                     }
1741                 default:
1742                     return super.getString(columnIndex);
1743             }
1744         }
1745 
getLocalUri()1746         private String getLocalUri() {
1747             long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION));
1748             if (destinationType == Downloads.Impl.DESTINATION_FILE_URI ||
1749                     destinationType == Downloads.Impl.DESTINATION_EXTERNAL ||
1750                     destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
1751                 String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME));
1752                 if (localPath == null) {
1753                     return null;
1754                 }
1755                 return Uri.fromFile(new File(localPath)).toString();
1756             }
1757 
1758             // return content URI for cache download
1759             long downloadId = getLong(getColumnIndex(Downloads.Impl._ID));
1760             return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString();
1761         }
1762 
getReason(int status)1763         private long getReason(int status) {
1764             switch (translateStatus(status)) {
1765                 case STATUS_FAILED:
1766                     return getErrorCode(status);
1767 
1768                 case STATUS_PAUSED:
1769                     return getPausedReason(status);
1770 
1771                 default:
1772                     return 0; // arbitrary value when status is not an error
1773             }
1774         }
1775 
getPausedReason(int status)1776         private long getPausedReason(int status) {
1777             switch (status) {
1778                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1779                     return PAUSED_WAITING_TO_RETRY;
1780 
1781                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1782                     return PAUSED_WAITING_FOR_NETWORK;
1783 
1784                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1785                     return PAUSED_QUEUED_FOR_WIFI;
1786 
1787                 default:
1788                     return PAUSED_UNKNOWN;
1789             }
1790         }
1791 
getErrorCode(int status)1792         private long getErrorCode(int status) {
1793             if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
1794                     || (500 <= status && status < 600)) {
1795                 // HTTP status code
1796                 return status;
1797             }
1798 
1799             switch (status) {
1800                 case Downloads.Impl.STATUS_FILE_ERROR:
1801                     return ERROR_FILE_ERROR;
1802 
1803                 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
1804                 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
1805                     return ERROR_UNHANDLED_HTTP_CODE;
1806 
1807                 case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
1808                     return ERROR_HTTP_DATA_ERROR;
1809 
1810                 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
1811                     return ERROR_TOO_MANY_REDIRECTS;
1812 
1813                 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
1814                     return ERROR_INSUFFICIENT_SPACE;
1815 
1816                 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
1817                     return ERROR_DEVICE_NOT_FOUND;
1818 
1819                 case Downloads.Impl.STATUS_CANNOT_RESUME:
1820                     return ERROR_CANNOT_RESUME;
1821 
1822                 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
1823                     return ERROR_FILE_ALREADY_EXISTS;
1824 
1825                 default:
1826                     return ERROR_UNKNOWN;
1827             }
1828         }
1829 
translateStatus(int status)1830         private int translateStatus(int status) {
1831             switch (status) {
1832                 case Downloads.Impl.STATUS_PENDING:
1833                     return STATUS_PENDING;
1834 
1835                 case Downloads.Impl.STATUS_RUNNING:
1836                     return STATUS_RUNNING;
1837 
1838                 case Downloads.Impl.STATUS_PAUSED_BY_APP:
1839                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1840                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1841                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1842                     return STATUS_PAUSED;
1843 
1844                 case Downloads.Impl.STATUS_SUCCESS:
1845                     return STATUS_SUCCESSFUL;
1846 
1847                 default:
1848                     assert Downloads.Impl.isStatusError(status);
1849                     return STATUS_FAILED;
1850             }
1851         }
1852     }
1853 }
1854