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