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.content.ContentResolver;
     20 import android.content.ContentUris;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.database.Cursor;
     24 import android.database.CursorWrapper;
     25 import android.net.ConnectivityManager;
     26 import android.net.Uri;
     27 import android.os.Environment;
     28 import android.os.ParcelFileDescriptor;
     29 import android.provider.BaseColumns;
     30 import android.provider.Downloads;
     31 import android.util.Pair;
     32 
     33 import java.io.File;
     34 import java.io.FileNotFoundException;
     35 import java.util.ArrayList;
     36 import java.util.Arrays;
     37 import java.util.HashSet;
     38 import java.util.List;
     39 import java.util.Set;
     40 
     41 /**
     42  * The download manager is a system service that handles long-running HTTP downloads. Clients may
     43  * request that a URI be downloaded to a particular destination file. The download manager will
     44  * conduct the download in the background, taking care of HTTP interactions and retrying downloads
     45  * after failures or across connectivity changes and system reboots.
     46  *
     47  * Instances of this class should be obtained through
     48  * {@link android.content.Context#getSystemService(String)} by passing
     49  * {@link android.content.Context#DOWNLOAD_SERVICE}.
     50  *
     51  * Apps that request downloads through this API should register a broadcast receiver for
     52  * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
     53  * download in a notification or from the downloads UI.
     54  */
     55 public class DownloadManager {
     56     private static final String TAG = "DownloadManager";
     57 
     58     /**
     59      * An identifier for a particular download, unique across the system.  Clients use this ID to
     60      * make subsequent calls related to the download.
     61      */
     62     public final static String COLUMN_ID = BaseColumns._ID;
     63 
     64     /**
     65      * The client-supplied title for this download.  This will be displayed in system notifications.
     66      * Defaults to the empty string.
     67      */
     68     public final static String COLUMN_TITLE = "title";
     69 
     70     /**
     71      * The client-supplied description of this download.  This will be displayed in system
     72      * notifications.  Defaults to the empty string.
     73      */
     74     public final static String COLUMN_DESCRIPTION = "description";
     75 
     76     /**
     77      * URI to be downloaded.
     78      */
     79     public final static String COLUMN_URI = "uri";
     80 
     81     /**
     82      * Internet Media Type of the downloaded file.  If no value is provided upon creation, this will
     83      * initially be null and will be filled in based on the server's response once the download has
     84      * started.
     85      *
     86      * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
     87      */
     88     public final static String COLUMN_MEDIA_TYPE = "media_type";
     89 
     90     /**
     91      * Total size of the download in bytes.  This will initially be -1 and will be filled in once
     92      * the download starts.
     93      */
     94     public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
     95 
     96     /**
     97      * Uri where downloaded file will be stored.  If a destination is supplied by client, that URI
     98      * will be used here.  Otherwise, the value will initially be null and will be filled in with a
     99      * generated URI once the download has started.
    100      */
    101     public final static String COLUMN_LOCAL_URI = "local_uri";
    102 
    103     /**
    104      * Current status of the download, as one of the STATUS_* constants.
    105      */
    106     public final static String COLUMN_STATUS = "status";
    107 
    108     /**
    109      * Provides more detail on the status of the download.  Its meaning depends on the value of
    110      * {@link #COLUMN_STATUS}.
    111      *
    112      * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
    113      * occurred.  If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
    114      * 2616.  Otherwise, it will hold one of the ERROR_* constants.
    115      *
    116      * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
    117      * paused.  It will hold one of the PAUSED_* constants.
    118      *
    119      * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
    120      * column's value is undefined.
    121      *
    122      * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
    123      * status codes</a>
    124      */
    125     public final static String COLUMN_REASON = "reason";
    126 
    127     /**
    128      * Number of bytes download so far.
    129      */
    130     public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
    131 
    132     /**
    133      * Timestamp when the download was last modified, in {@link System#currentTimeMillis
    134      * System.currentTimeMillis()} (wall clock time in UTC).
    135      */
    136     public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
    137 
    138     /**
    139      * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
    140      * used to delete the entries from MediaProvider database when it is deleted from the
    141      * downloaded list.
    142      */
    143     public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
    144 
    145     /**
    146      * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
    147      */
    148     public final static int STATUS_PENDING = 1 << 0;
    149 
    150     /**
    151      * Value of {@link #COLUMN_STATUS} when the download is currently running.
    152      */
    153     public final static int STATUS_RUNNING = 1 << 1;
    154 
    155     /**
    156      * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume.
    157      */
    158     public final static int STATUS_PAUSED = 1 << 2;
    159 
    160     /**
    161      * Value of {@link #COLUMN_STATUS} when the download has successfully completed.
    162      */
    163     public final static int STATUS_SUCCESSFUL = 1 << 3;
    164 
    165     /**
    166      * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried).
    167      */
    168     public final static int STATUS_FAILED = 1 << 4;
    169 
    170 
    171     /**
    172      * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit
    173      * under any other error code.
    174      */
    175     public final static int ERROR_UNKNOWN = 1000;
    176 
    177     /**
    178      * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
    179      * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
    180      * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
    181      */
    182     public final static int ERROR_FILE_ERROR = 1001;
    183 
    184     /**
    185      * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
    186      * can't handle.
    187      */
    188     public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
    189 
    190     /**
    191      * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
    192      * the HTTP level.
    193      */
    194     public final static int ERROR_HTTP_DATA_ERROR = 1004;
    195 
    196     /**
    197      * Value of {@link #COLUMN_REASON} when there were too many redirects.
    198      */
    199     public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
    200 
    201     /**
    202      * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
    203      * this is because the SD card is full.
    204      */
    205     public final static int ERROR_INSUFFICIENT_SPACE = 1006;
    206 
    207     /**
    208      * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
    209      * this is because the SD card is not mounted.
    210      */
    211     public final static int ERROR_DEVICE_NOT_FOUND = 1007;
    212 
    213     /**
    214      * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
    215      * resume the download.
    216      */
    217     public final static int ERROR_CANNOT_RESUME = 1008;
    218 
    219     /**
    220      * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
    221      * download manager will not overwrite an existing file).
    222      */
    223     public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
    224 
    225     /**
    226      * Value of {@link #COLUMN_REASON} when the download is paused because some network error
    227      * occurred and the download manager is waiting before retrying the request.
    228      */
    229     public final static int PAUSED_WAITING_TO_RETRY = 1;
    230 
    231     /**
    232      * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
    233      * proceed.
    234      */
    235     public final static int PAUSED_WAITING_FOR_NETWORK = 2;
    236 
    237     /**
    238      * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
    239      * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
    240      */
    241     public final static int PAUSED_QUEUED_FOR_WIFI = 3;
    242 
    243     /**
    244      * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
    245      */
    246     public final static int PAUSED_UNKNOWN = 4;
    247 
    248     /**
    249      * Broadcast intent action sent by the download manager when a download completes.
    250      */
    251     public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
    252 
    253     /**
    254      * Broadcast intent action sent by the download manager when the user clicks on a running
    255      * download, either from a system notification or from the downloads UI.
    256      */
    257     public final static String ACTION_NOTIFICATION_CLICKED =
    258             "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
    259 
    260     /**
    261      * Intent action to launch an activity to display all downloads.
    262      */
    263     public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
    264 
    265     /**
    266      * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
    267      * long) of the download that just completed.
    268      */
    269     public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
    270 
    271     // this array must contain all public columns
    272     private static final String[] COLUMNS = new String[] {
    273         COLUMN_ID,
    274         COLUMN_MEDIAPROVIDER_URI,
    275         COLUMN_TITLE,
    276         COLUMN_DESCRIPTION,
    277         COLUMN_URI,
    278         COLUMN_MEDIA_TYPE,
    279         COLUMN_TOTAL_SIZE_BYTES,
    280         COLUMN_LOCAL_URI,
    281         COLUMN_STATUS,
    282         COLUMN_REASON,
    283         COLUMN_BYTES_DOWNLOADED_SO_FAR,
    284         COLUMN_LAST_MODIFIED_TIMESTAMP
    285     };
    286 
    287     // columns to request from DownloadProvider
    288     private static final String[] UNDERLYING_COLUMNS = new String[] {
    289         Downloads.Impl._ID,
    290         Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
    291         Downloads.COLUMN_TITLE,
    292         Downloads.COLUMN_DESCRIPTION,
    293         Downloads.COLUMN_URI,
    294         Downloads.COLUMN_MIME_TYPE,
    295         Downloads.COLUMN_TOTAL_BYTES,
    296         Downloads.COLUMN_STATUS,
    297         Downloads.COLUMN_CURRENT_BYTES,
    298         Downloads.COLUMN_LAST_MODIFICATION,
    299         Downloads.COLUMN_DESTINATION,
    300         Downloads.Impl.COLUMN_FILE_NAME_HINT,
    301         Downloads.Impl._DATA,
    302     };
    303 
    304     private static final Set<String> LONG_COLUMNS = new HashSet<String>(
    305             Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_REASON,
    306                           COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP));
    307 
    308     /**
    309      * This class contains all the information necessary to request a new download. The URI is the
    310      * only required parameter.
    311      *
    312      * Note that the default download destination is a shared volume where the system might delete
    313      * your file if it needs to reclaim space for system use. If this is a problem, use a location
    314      * on external storage (see {@link #setDestinationUri(Uri)}.
    315      */
    316     public static class Request {
    317         /**
    318          * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
    319          * {@link ConnectivityManager#TYPE_MOBILE}.
    320          */
    321         public static final int NETWORK_MOBILE = 1 << 0;
    322 
    323         /**
    324          * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
    325          * {@link ConnectivityManager#TYPE_WIFI}.
    326          */
    327         public static final int NETWORK_WIFI = 1 << 1;
    328 
    329         private Uri mUri;
    330         private Uri mDestinationUri;
    331         private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
    332         private CharSequence mTitle;
    333         private CharSequence mDescription;
    334         private boolean mShowNotification = true;
    335         private String mMimeType;
    336         private boolean mRoamingAllowed = true;
    337         private int mAllowedNetworkTypes = ~0; // default to all network types allowed
    338         private boolean mIsVisibleInDownloadsUi = true;
    339 
    340         /**
    341          * @param uri the HTTP URI to download.
    342          */
    343         public Request(Uri uri) {
    344             if (uri == null) {
    345                 throw new NullPointerException();
    346             }
    347             String scheme = uri.getScheme();
    348             if (scheme == null || !scheme.equals("http")) {
    349                 throw new IllegalArgumentException("Can only download HTTP URIs: " + uri);
    350             }
    351             mUri = uri;
    352         }
    353 
    354         /**
    355          * Set the local destination for the downloaded file. Must be a file URI to a path on
    356          * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE
    357          * permission.
    358          *
    359          * By default, downloads are saved to a generated filename in the shared download cache and
    360          * may be deleted by the system at any time to reclaim space.
    361          *
    362          * @return this object
    363          */
    364         public Request setDestinationUri(Uri uri) {
    365             mDestinationUri = uri;
    366             return this;
    367         }
    368 
    369         /**
    370          * Set the local destination for the downloaded file to a path within the application's
    371          * external files directory (as returned by {@link Context#getExternalFilesDir(String)}.
    372          *
    373          * @param context the {@link Context} to use in determining the external files directory
    374          * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)}
    375          * @param subPath the path within the external directory, including the destination filename
    376          * @return this object
    377          */
    378         public Request setDestinationInExternalFilesDir(Context context, String dirType,
    379                 String subPath) {
    380             setDestinationFromBase(context.getExternalFilesDir(dirType), subPath);
    381             return this;
    382         }
    383 
    384         /**
    385          * Set the local destination for the downloaded file to a path within the public external
    386          * storage directory (as returned by
    387          * {@link Environment#getExternalStoragePublicDirectory(String)}.
    388          *
    389          * @param dirType the directory type to pass to
    390          *        {@link Environment#getExternalStoragePublicDirectory(String)}
    391          * @param subPath the path within the external directory, including the destination filename
    392          * @return this object
    393          */
    394         public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
    395             setDestinationFromBase(Environment.getExternalStoragePublicDirectory(dirType), subPath);
    396             return this;
    397         }
    398 
    399         private void setDestinationFromBase(File base, String subPath) {
    400             if (subPath == null) {
    401                 throw new NullPointerException("subPath cannot be null");
    402             }
    403             mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
    404         }
    405 
    406         /**
    407          * Add an HTTP header to be included with the download request.  The header will be added to
    408          * the end of the list.
    409          * @param header HTTP header name
    410          * @param value header value
    411          * @return this object
    412          * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1
    413          *      Message Headers</a>
    414          */
    415         public Request addRequestHeader(String header, String value) {
    416             if (header == null) {
    417                 throw new NullPointerException("header cannot be null");
    418             }
    419             if (header.contains(":")) {
    420                 throw new IllegalArgumentException("header may not contain ':'");
    421             }
    422             if (value == null) {
    423                 value = "";
    424             }
    425             mRequestHeaders.add(Pair.create(header, value));
    426             return this;
    427         }
    428 
    429         /**
    430          * Set the title of this download, to be displayed in notifications (if enabled).  If no
    431          * title is given, a default one will be assigned based on the download filename, once the
    432          * download starts.
    433          * @return this object
    434          */
    435         public Request setTitle(CharSequence title) {
    436             mTitle = title;
    437             return this;
    438         }
    439 
    440         /**
    441          * Set a description of this download, to be displayed in notifications (if enabled)
    442          * @return this object
    443          */
    444         public Request setDescription(CharSequence description) {
    445             mDescription = description;
    446             return this;
    447         }
    448 
    449         /**
    450          * Set the MIME content type of this download.  This will override the content type declared
    451          * in the server's response.
    452          * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1
    453          *      Media Types</a>
    454          * @return this object
    455          */
    456         public Request setMimeType(String mimeType) {
    457             mMimeType = mimeType;
    458             return this;
    459         }
    460 
    461         /**
    462          * Control whether a system notification is posted by the download manager while this
    463          * download is running. If enabled, the download manager posts notifications about downloads
    464          * through the system {@link android.app.NotificationManager}. By default, a notification is
    465          * shown.
    466          *
    467          * If set to false, this requires the permission
    468          * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
    469          *
    470          * @param show whether the download manager should show a notification for this download.
    471          * @return this object
    472          */
    473         public Request setShowRunningNotification(boolean show) {
    474             mShowNotification = show;
    475             return this;
    476         }
    477 
    478         /**
    479          * Restrict the types of networks over which this download may proceed.  By default, all
    480          * network types are allowed.
    481          * @param flags any combination of the NETWORK_* bit flags.
    482          * @return this object
    483          */
    484         public Request setAllowedNetworkTypes(int flags) {
    485             mAllowedNetworkTypes = flags;
    486             return this;
    487         }
    488 
    489         /**
    490          * Set whether this download may proceed over a roaming connection.  By default, roaming is
    491          * allowed.
    492          * @param allowed whether to allow a roaming connection to be used
    493          * @return this object
    494          */
    495         public Request setAllowedOverRoaming(boolean allowed) {
    496             mRoamingAllowed = allowed;
    497             return this;
    498         }
    499 
    500         /**
    501          * Set whether this download should be displayed in the system's Downloads UI. True by
    502          * default.
    503          * @param isVisible whether to display this download in the Downloads UI
    504          * @return this object
    505          */
    506         public Request setVisibleInDownloadsUi(boolean isVisible) {
    507             mIsVisibleInDownloadsUi = isVisible;
    508             return this;
    509         }
    510 
    511         /**
    512          * @return ContentValues to be passed to DownloadProvider.insert()
    513          */
    514         ContentValues toContentValues(String packageName) {
    515             ContentValues values = new ContentValues();
    516             assert mUri != null;
    517             values.put(Downloads.COLUMN_URI, mUri.toString());
    518             values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
    519             values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE, packageName);
    520 
    521             if (mDestinationUri != null) {
    522                 values.put(Downloads.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
    523                 values.put(Downloads.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
    524             } else {
    525                 values.put(Downloads.COLUMN_DESTINATION,
    526                            Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE);
    527             }
    528 
    529             if (!mRequestHeaders.isEmpty()) {
    530                 encodeHttpHeaders(values);
    531             }
    532 
    533             putIfNonNull(values, Downloads.COLUMN_TITLE, mTitle);
    534             putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription);
    535             putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMimeType);
    536 
    537             values.put(Downloads.COLUMN_VISIBILITY,
    538                     mShowNotification ? Downloads.VISIBILITY_VISIBLE
    539                             : Downloads.VISIBILITY_HIDDEN);
    540 
    541             values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
    542             values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
    543             values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
    544 
    545             return values;
    546         }
    547 
    548         private void encodeHttpHeaders(ContentValues values) {
    549             int index = 0;
    550             for (Pair<String, String> header : mRequestHeaders) {
    551                 String headerString = header.first + ": " + header.second;
    552                 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
    553                 index++;
    554             }
    555         }
    556 
    557         private void putIfNonNull(ContentValues contentValues, String key, Object value) {
    558             if (value != null) {
    559                 contentValues.put(key, value.toString());
    560             }
    561         }
    562     }
    563 
    564     /**
    565      * This class may be used to filter download manager queries.
    566      */
    567     public static class Query {
    568         /**
    569          * Constant for use with {@link #orderBy}
    570          * @hide
    571          */
    572         public static final int ORDER_ASCENDING = 1;
    573 
    574         /**
    575          * Constant for use with {@link #orderBy}
    576          * @hide
    577          */
    578         public static final int ORDER_DESCENDING = 2;
    579 
    580         private long[] mIds = null;
    581         private Integer mStatusFlags = null;
    582         private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
    583         private int mOrderDirection = ORDER_DESCENDING;
    584         private boolean mOnlyIncludeVisibleInDownloadsUi = false;
    585 
    586         /**
    587          * Include only the downloads with the given IDs.
    588          * @return this object
    589          */
    590         public Query setFilterById(long... ids) {
    591             mIds = ids;
    592             return this;
    593         }
    594 
    595         /**
    596          * Include only downloads with status matching any the given status flags.
    597          * @param flags any combination of the STATUS_* bit flags
    598          * @return this object
    599          */
    600         public Query setFilterByStatus(int flags) {
    601             mStatusFlags = flags;
    602             return this;
    603         }
    604 
    605         /**
    606          * Controls whether this query includes downloads not visible in the system's Downloads UI.
    607          * @param value if true, this query will only include downloads that should be displayed in
    608          *            the system's Downloads UI; if false (the default), this query will include
    609          *            both visible and invisible downloads.
    610          * @return this object
    611          * @hide
    612          */
    613         public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
    614             mOnlyIncludeVisibleInDownloadsUi = value;
    615             return this;
    616         }
    617 
    618         /**
    619          * Change the sort order of the returned Cursor.
    620          *
    621          * @param column one of the COLUMN_* constants; currently, only
    622          *         {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
    623          *         supported.
    624          * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
    625          * @return this object
    626          * @hide
    627          */
    628         public Query orderBy(String column, int direction) {
    629             if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
    630                 throw new IllegalArgumentException("Invalid direction: " + direction);
    631             }
    632 
    633             if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
    634                 mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
    635             } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
    636                 mOrderByColumn = Downloads.COLUMN_TOTAL_BYTES;
    637             } else {
    638                 throw new IllegalArgumentException("Cannot order by " + column);
    639             }
    640             mOrderDirection = direction;
    641             return this;
    642         }
    643 
    644         /**
    645          * Run this query using the given ContentResolver.
    646          * @param projection the projection to pass to ContentResolver.query()
    647          * @return the Cursor returned by ContentResolver.query()
    648          */
    649         Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
    650             Uri uri = baseUri;
    651             List<String> selectionParts = new ArrayList<String>();
    652             String[] selectionArgs = null;
    653 
    654             if (mIds != null) {
    655                 selectionParts.add(getWhereClauseForIds(mIds));
    656                 selectionArgs = getWhereArgsForIds(mIds);
    657             }
    658 
    659             if (mStatusFlags != null) {
    660                 List<String> parts = new ArrayList<String>();
    661                 if ((mStatusFlags & STATUS_PENDING) != 0) {
    662                     parts.add(statusClause("=", Downloads.STATUS_PENDING));
    663                 }
    664                 if ((mStatusFlags & STATUS_RUNNING) != 0) {
    665                     parts.add(statusClause("=", Downloads.STATUS_RUNNING));
    666                 }
    667                 if ((mStatusFlags & STATUS_PAUSED) != 0) {
    668                     parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
    669                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
    670                     parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
    671                     parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
    672                 }
    673                 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
    674                     parts.add(statusClause("=", Downloads.STATUS_SUCCESS));
    675                 }
    676                 if ((mStatusFlags & STATUS_FAILED) != 0) {
    677                     parts.add("(" + statusClause(">=", 400)
    678                               + " AND " + statusClause("<", 600) + ")");
    679                 }
    680                 selectionParts.add(joinStrings(" OR ", parts));
    681             }
    682 
    683             if (mOnlyIncludeVisibleInDownloadsUi) {
    684                 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
    685             }
    686 
    687             // only return rows which are not marked 'deleted = 1'
    688             selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
    689 
    690             String selection = joinStrings(" AND ", selectionParts);
    691             String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
    692             String orderBy = mOrderByColumn + " " + orderDirection;
    693 
    694             return resolver.query(uri, projection, selection, selectionArgs, orderBy);
    695         }
    696 
    697         private String joinStrings(String joiner, Iterable<String> parts) {
    698             StringBuilder builder = new StringBuilder();
    699             boolean first = true;
    700             for (String part : parts) {
    701                 if (!first) {
    702                     builder.append(joiner);
    703                 }
    704                 builder.append(part);
    705                 first = false;
    706             }
    707             return builder.toString();
    708         }
    709 
    710         private String statusClause(String operator, int value) {
    711             return Downloads.COLUMN_STATUS + operator + "'" + value + "'";
    712         }
    713     }
    714 
    715     private ContentResolver mResolver;
    716     private String mPackageName;
    717     private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
    718 
    719     /**
    720      * @hide
    721      */
    722     public DownloadManager(ContentResolver resolver, String packageName) {
    723         mResolver = resolver;
    724         mPackageName = packageName;
    725     }
    726 
    727     /**
    728      * Makes this object access the download provider through /all_downloads URIs rather than
    729      * /my_downloads URIs, for clients that have permission to do so.
    730      * @hide
    731      */
    732     public void setAccessAllDownloads(boolean accessAllDownloads) {
    733         if (accessAllDownloads) {
    734             mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
    735         } else {
    736             mBaseUri = Downloads.Impl.CONTENT_URI;
    737         }
    738     }
    739 
    740     /**
    741      * Enqueue a new download.  The download will start automatically once the download manager is
    742      * ready to execute it and connectivity is available.
    743      *
    744      * @param request the parameters specifying this download
    745      * @return an ID for the download, unique across the system.  This ID is used to make future
    746      * calls related to this download.
    747      */
    748     public long enqueue(Request request) {
    749         ContentValues values = request.toContentValues(mPackageName);
    750         Uri downloadUri = mResolver.insert(Downloads.CONTENT_URI, values);
    751         long id = Long.parseLong(downloadUri.getLastPathSegment());
    752         return id;
    753     }
    754 
    755     /**
    756      * Marks the specified download as 'to be deleted'. This is done when a completed download
    757      * is to be removed but the row was stored without enough info to delete the corresponding
    758      * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
    759      *
    760      * @param ids the IDs of the downloads to be marked 'deleted'
    761      * @return the number of downloads actually updated
    762      * @hide
    763      */
    764     public int markRowDeleted(long... ids) {
    765         if (ids == null || ids.length == 0) {
    766             // called with nothing to remove!
    767             throw new IllegalArgumentException("input param 'ids' can't be null");
    768         }
    769         ContentValues values = new ContentValues();
    770         values.put(Downloads.Impl.COLUMN_DELETED, 1);
    771         return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
    772                 getWhereArgsForIds(ids));
    773     }
    774 
    775     /**
    776      * Cancel downloads and remove them from the download manager.  Each download will be stopped if
    777      * it was running, and it will no longer be accessible through the download manager.  If a file
    778      * was already downloaded to external storage, it will not be deleted.
    779      *
    780      * @param ids the IDs of the downloads to remove
    781      * @return the number of downloads actually removed
    782      */
    783     public int remove(long... ids) {
    784         if (ids == null || ids.length == 0) {
    785             // called with nothing to remove!
    786             throw new IllegalArgumentException("input param 'ids' can't be null");
    787         }
    788         return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
    789     }
    790 
    791     /**
    792      * Query the download manager about downloads that have been requested.
    793      * @param query parameters specifying filters for this query
    794      * @return a Cursor over the result set of downloads, with columns consisting of all the
    795      * COLUMN_* constants.
    796      */
    797     public Cursor query(Query query) {
    798         Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
    799         if (underlyingCursor == null) {
    800             return null;
    801         }
    802         return new CursorTranslator(underlyingCursor, mBaseUri);
    803     }
    804 
    805     /**
    806      * Open a downloaded file for reading.  The download must have completed.
    807      * @param id the ID of the download
    808      * @return a read-only {@link ParcelFileDescriptor}
    809      * @throws FileNotFoundException if the destination file does not already exist
    810      */
    811     public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
    812         return mResolver.openFileDescriptor(getDownloadUri(id), "r");
    813     }
    814 
    815     /**
    816      * Restart the given downloads, which must have already completed (successfully or not).  This
    817      * method will only work when called from within the download manager's process.
    818      * @param ids the IDs of the downloads
    819      * @hide
    820      */
    821     public void restartDownload(long... ids) {
    822         Cursor cursor = query(new Query().setFilterById(ids));
    823         try {
    824             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    825                 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
    826                 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
    827                     throw new IllegalArgumentException("Cannot restart incomplete download: "
    828                             + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
    829                 }
    830             }
    831         } finally {
    832             cursor.close();
    833         }
    834 
    835         ContentValues values = new ContentValues();
    836         values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
    837         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
    838         values.putNull(Downloads.Impl._DATA);
    839         values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
    840         mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
    841     }
    842 
    843     /**
    844      * Get the DownloadProvider URI for the download with the given ID.
    845      */
    846     Uri getDownloadUri(long id) {
    847         return ContentUris.withAppendedId(mBaseUri, id);
    848     }
    849 
    850     /**
    851      * Get a parameterized SQL WHERE clause to select a bunch of IDs.
    852      */
    853     static String getWhereClauseForIds(long[] ids) {
    854         StringBuilder whereClause = new StringBuilder();
    855         whereClause.append("(");
    856         for (int i = 0; i < ids.length; i++) {
    857             if (i > 0) {
    858                 whereClause.append("OR ");
    859             }
    860             whereClause.append(Downloads.Impl._ID);
    861             whereClause.append(" = ? ");
    862         }
    863         whereClause.append(")");
    864         return whereClause.toString();
    865     }
    866 
    867     /**
    868      * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
    869      */
    870     static String[] getWhereArgsForIds(long[] ids) {
    871         String[] whereArgs = new String[ids.length];
    872         for (int i = 0; i < ids.length; i++) {
    873             whereArgs[i] = Long.toString(ids[i]);
    874         }
    875         return whereArgs;
    876     }
    877 
    878     /**
    879      * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
    880      * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
    881      * Some columns correspond directly to underlying values while others are computed from
    882      * underlying data.
    883      */
    884     private static class CursorTranslator extends CursorWrapper {
    885         private Uri mBaseUri;
    886 
    887         public CursorTranslator(Cursor cursor, Uri baseUri) {
    888             super(cursor);
    889             mBaseUri = baseUri;
    890         }
    891 
    892         @Override
    893         public int getColumnIndex(String columnName) {
    894             return Arrays.asList(COLUMNS).indexOf(columnName);
    895         }
    896 
    897         @Override
    898         public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
    899             int index = getColumnIndex(columnName);
    900             if (index == -1) {
    901                 throw new IllegalArgumentException("No such column: " + columnName);
    902             }
    903             return index;
    904         }
    905 
    906         @Override
    907         public String getColumnName(int columnIndex) {
    908             int numColumns = COLUMNS.length;
    909             if (columnIndex < 0 || columnIndex >= numColumns) {
    910                 throw new IllegalArgumentException("Invalid column index " + columnIndex + ", "
    911                                                    + numColumns + " columns exist");
    912             }
    913             return COLUMNS[columnIndex];
    914         }
    915 
    916         @Override
    917         public String[] getColumnNames() {
    918             String[] returnColumns = new String[COLUMNS.length];
    919             System.arraycopy(COLUMNS, 0, returnColumns, 0, COLUMNS.length);
    920             return returnColumns;
    921         }
    922 
    923         @Override
    924         public int getColumnCount() {
    925             return COLUMNS.length;
    926         }
    927 
    928         @Override
    929         public byte[] getBlob(int columnIndex) {
    930             throw new UnsupportedOperationException();
    931         }
    932 
    933         @Override
    934         public double getDouble(int columnIndex) {
    935             return getLong(columnIndex);
    936         }
    937 
    938         private boolean isLongColumn(String column) {
    939             return LONG_COLUMNS.contains(column);
    940         }
    941 
    942         @Override
    943         public float getFloat(int columnIndex) {
    944             return (float) getDouble(columnIndex);
    945         }
    946 
    947         @Override
    948         public int getInt(int columnIndex) {
    949             return (int) getLong(columnIndex);
    950         }
    951 
    952         @Override
    953         public long getLong(int columnIndex) {
    954             return translateLong(getColumnName(columnIndex));
    955         }
    956 
    957         @Override
    958         public short getShort(int columnIndex) {
    959             return (short) getLong(columnIndex);
    960         }
    961 
    962         @Override
    963         public String getString(int columnIndex) {
    964             return translateString(getColumnName(columnIndex));
    965         }
    966 
    967         private String translateString(String column) {
    968             if (isLongColumn(column)) {
    969                 return Long.toString(translateLong(column));
    970             }
    971             if (column.equals(COLUMN_TITLE)) {
    972                 return getUnderlyingString(Downloads.COLUMN_TITLE);
    973             }
    974             if (column.equals(COLUMN_DESCRIPTION)) {
    975                 return getUnderlyingString(Downloads.COLUMN_DESCRIPTION);
    976             }
    977             if (column.equals(COLUMN_URI)) {
    978                 return getUnderlyingString(Downloads.COLUMN_URI);
    979             }
    980             if (column.equals(COLUMN_MEDIA_TYPE)) {
    981                 return getUnderlyingString(Downloads.COLUMN_MIME_TYPE);
    982             }
    983             if (column.equals(COLUMN_MEDIAPROVIDER_URI)) {
    984                 return getUnderlyingString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
    985             }
    986 
    987             assert column.equals(COLUMN_LOCAL_URI);
    988             return getLocalUri();
    989         }
    990 
    991         private String getLocalUri() {
    992             long destinationType = getUnderlyingLong(Downloads.Impl.COLUMN_DESTINATION);
    993             if (destinationType == Downloads.Impl.DESTINATION_FILE_URI) {
    994                 // return client-provided file URI for external download
    995                 return getUnderlyingString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
    996             }
    997 
    998             if (destinationType == Downloads.Impl.DESTINATION_EXTERNAL) {
    999                 // return stored destination for legacy external download
   1000                 String localPath = getUnderlyingString(Downloads.Impl._DATA);
   1001                 if (localPath == null) {
   1002                     return null;
   1003                 }
   1004                 return Uri.fromFile(new File(localPath)).toString();
   1005             }
   1006 
   1007             // return content URI for cache download
   1008             long downloadId = getUnderlyingLong(Downloads.Impl._ID);
   1009             return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
   1010         }
   1011 
   1012         private long translateLong(String column) {
   1013             if (!isLongColumn(column)) {
   1014                 // mimic behavior of underlying cursor -- most likely, throw NumberFormatException
   1015                 return Long.valueOf(translateString(column));
   1016             }
   1017 
   1018             if (column.equals(COLUMN_ID)) {
   1019                 return getUnderlyingLong(Downloads.Impl._ID);
   1020             }
   1021             if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
   1022                 return getUnderlyingLong(Downloads.COLUMN_TOTAL_BYTES);
   1023             }
   1024             if (column.equals(COLUMN_STATUS)) {
   1025                 return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
   1026             }
   1027             if (column.equals(COLUMN_REASON)) {
   1028                 return getReason((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
   1029             }
   1030             if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
   1031                 return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES);
   1032             }
   1033             assert column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP);
   1034             return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION);
   1035         }
   1036 
   1037         private long getReason(int status) {
   1038             switch (translateStatus(status)) {
   1039                 case STATUS_FAILED:
   1040                     return getErrorCode(status);
   1041 
   1042                 case STATUS_PAUSED:
   1043                     return getPausedReason(status);
   1044 
   1045                 default:
   1046                     return 0; // arbitrary value when status is not an error
   1047             }
   1048         }
   1049 
   1050         private long getPausedReason(int status) {
   1051             switch (status) {
   1052                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
   1053                     return PAUSED_WAITING_TO_RETRY;
   1054 
   1055                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
   1056                     return PAUSED_WAITING_FOR_NETWORK;
   1057 
   1058                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
   1059                     return PAUSED_QUEUED_FOR_WIFI;
   1060 
   1061                 default:
   1062                     return PAUSED_UNKNOWN;
   1063             }
   1064         }
   1065 
   1066         private long getErrorCode(int status) {
   1067             if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
   1068                     || (500 <= status && status < 600)) {
   1069                 // HTTP status code
   1070                 return status;
   1071             }
   1072 
   1073             switch (status) {
   1074                 case Downloads.STATUS_FILE_ERROR:
   1075                     return ERROR_FILE_ERROR;
   1076 
   1077                 case Downloads.STATUS_UNHANDLED_HTTP_CODE:
   1078                 case Downloads.STATUS_UNHANDLED_REDIRECT:
   1079                     return ERROR_UNHANDLED_HTTP_CODE;
   1080 
   1081                 case Downloads.STATUS_HTTP_DATA_ERROR:
   1082                     return ERROR_HTTP_DATA_ERROR;
   1083 
   1084                 case Downloads.STATUS_TOO_MANY_REDIRECTS:
   1085                     return ERROR_TOO_MANY_REDIRECTS;
   1086 
   1087                 case Downloads.STATUS_INSUFFICIENT_SPACE_ERROR:
   1088                     return ERROR_INSUFFICIENT_SPACE;
   1089 
   1090                 case Downloads.STATUS_DEVICE_NOT_FOUND_ERROR:
   1091                     return ERROR_DEVICE_NOT_FOUND;
   1092 
   1093                 case Downloads.Impl.STATUS_CANNOT_RESUME:
   1094                     return ERROR_CANNOT_RESUME;
   1095 
   1096                 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
   1097                     return ERROR_FILE_ALREADY_EXISTS;
   1098 
   1099                 default:
   1100                     return ERROR_UNKNOWN;
   1101             }
   1102         }
   1103 
   1104         private long getUnderlyingLong(String column) {
   1105             return super.getLong(super.getColumnIndex(column));
   1106         }
   1107 
   1108         private String getUnderlyingString(String column) {
   1109             return super.getString(super.getColumnIndex(column));
   1110         }
   1111 
   1112         private int translateStatus(int status) {
   1113             switch (status) {
   1114                 case Downloads.STATUS_PENDING:
   1115                     return STATUS_PENDING;
   1116 
   1117                 case Downloads.STATUS_RUNNING:
   1118                     return STATUS_RUNNING;
   1119 
   1120                 case Downloads.Impl.STATUS_PAUSED_BY_APP:
   1121                 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
   1122                 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
   1123                 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
   1124                     return STATUS_PAUSED;
   1125 
   1126                 case Downloads.STATUS_SUCCESS:
   1127                     return STATUS_SUCCESSFUL;
   1128 
   1129                 default:
   1130                     assert Downloads.isStatusError(status);
   1131                     return STATUS_FAILED;
   1132             }
   1133         }
   1134     }
   1135 }
   1136