Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2008 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.net;
     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.net.Uri;
     25 import android.os.ParcelFileDescriptor;
     26 import android.os.SystemClock;
     27 import android.provider.BaseColumns;
     28 import android.util.Log;
     29 
     30 import java.io.File;
     31 import java.io.FileNotFoundException;
     32 import java.io.IOException;
     33 import java.io.File;
     34 import java.io.InputStream;
     35 
     36 /**
     37  * The Download Manager
     38  *
     39  * @hide
     40  */
     41 public final class Downloads {
     42 
     43 
     44     /**
     45      * Download status codes
     46      */
     47 
     48     /**
     49      * This download hasn't started yet
     50      */
     51     public static final int STATUS_PENDING = 190;
     52 
     53     /**
     54      * This download has started
     55      */
     56     public static final int STATUS_RUNNING = 192;
     57 
     58     /**
     59      * This download has successfully completed.
     60      * Warning: there might be other status values that indicate success
     61      * in the future.
     62      * Use isSucccess() to capture the entire category.
     63      */
     64     public static final int STATUS_SUCCESS = 200;
     65 
     66     /**
     67      * This download can't be performed because the content type cannot be
     68      * handled.
     69      */
     70     public static final int STATUS_NOT_ACCEPTABLE = 406;
     71 
     72     /**
     73      * This download has completed with an error.
     74      * Warning: there will be other status values that indicate errors in
     75      * the future. Use isStatusError() to capture the entire category.
     76      */
     77     public static final int STATUS_UNKNOWN_ERROR = 491;
     78 
     79     /**
     80      * This download couldn't be completed because of an HTTP
     81      * redirect response that the download manager couldn't
     82      * handle.
     83      */
     84     public static final int STATUS_UNHANDLED_REDIRECT = 493;
     85 
     86     /**
     87      * This download couldn't be completed due to insufficient storage
     88      * space.  Typically, this is because the SD card is full.
     89      */
     90     public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
     91 
     92     /**
     93      * This download couldn't be completed because no external storage
     94      * device was found.  Typically, this is because the SD card is not
     95      * mounted.
     96      */
     97     public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
     98 
     99     /**
    100      * Returns whether the status is a success (i.e. 2xx).
    101      */
    102     public static boolean isStatusSuccess(int status) {
    103         return (status >= 200 && status < 300);
    104     }
    105 
    106     /**
    107      * Returns whether the status is an error (i.e. 4xx or 5xx).
    108      */
    109     public static boolean isStatusError(int status) {
    110         return (status >= 400 && status < 600);
    111     }
    112 
    113     /**
    114      * Download destinations
    115      */
    116 
    117     /**
    118      * This download will be saved to the external storage. This is the
    119      * default behavior, and should be used for any file that the user
    120      * can freely access, copy, delete. Even with that destination,
    121      * unencrypted DRM files are saved in secure internal storage.
    122      * Downloads to the external destination only write files for which
    123      * there is a registered handler. The resulting files are accessible
    124      * by filename to all applications.
    125      */
    126     public static final int DOWNLOAD_DESTINATION_EXTERNAL = 1;
    127 
    128     /**
    129      * This download will be saved to the download manager's private
    130      * partition. This is the behavior used by applications that want to
    131      * download private files that are used and deleted soon after they
    132      * get downloaded. All file types are allowed, and only the initiating
    133      * application can access the file (indirectly through a content
    134      * provider). This requires the
    135      * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
    136      */
    137     public static final int DOWNLOAD_DESTINATION_CACHE = 2;
    138 
    139     /**
    140      * This download will be saved to the download manager's private
    141      * partition and will be purged as necessary to make space. This is
    142      * for private files (similar to CACHE_PARTITION) that aren't deleted
    143      * immediately after they are used, and are kept around by the download
    144      * manager as long as space is available.
    145      */
    146     public static final int DOWNLOAD_DESTINATION_CACHE_PURGEABLE = 3;
    147 
    148 
    149     /**
    150      * An invalid download id
    151      */
    152     public static final long DOWNLOAD_ID_INVALID = -1;
    153 
    154 
    155     /**
    156      * Broadcast Action: this is sent by the download manager to the app
    157      * that had initiated a download when that download completes. The
    158      * download's content: uri is specified in the intent's data.
    159      */
    160     public static final String ACTION_DOWNLOAD_COMPLETED =
    161             "android.intent.action.DOWNLOAD_COMPLETED";
    162 
    163     /**
    164      * If extras are specified when requesting a download they will be provided in the intent that
    165      * is sent to the specified class and package when a download has finished.
    166      * <P>Type: TEXT</P>
    167      * <P>Owner can Init</P>
    168      */
    169     public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
    170 
    171 
    172     /**
    173      * Status class for a download
    174      */
    175     public static final class StatusInfo {
    176         public boolean completed = false;
    177         /** The filename of the active download. */
    178         public String filename = null;
    179         /** An opaque id for the download */
    180         public long id = DOWNLOAD_ID_INVALID;
    181         /** An opaque status code for the download */
    182         public int statusCode = -1;
    183         /** Approximate number of bytes downloaded so far, for debugging purposes. */
    184         public long bytesSoFar = -1;
    185 
    186         /**
    187          * Returns whether the download is completed
    188          * @return a boolean whether the download is complete.
    189          */
    190         public boolean isComplete() {
    191             return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
    192         }
    193 
    194         /**
    195          * Returns whether the download is successful
    196          * @return a boolean whether the download is successful.
    197          */
    198         public boolean isSuccessful() {
    199             return android.provider.Downloads.Impl.isStatusSuccess(statusCode);
    200         }
    201     }
    202 
    203     /**
    204      * Class to access initiate and query download by server uri
    205      */
    206     public static final class ByUri extends DownloadBase {
    207         /** @hide */
    208         private ByUri() {}
    209 
    210         /**
    211          * Query where clause by app data.
    212          * @hide
    213          */
    214         private static final String QUERY_WHERE_APP_DATA_CLAUSE =
    215                 android.provider.Downloads.Impl.COLUMN_APP_DATA + "=?";
    216 
    217         /**
    218          * Gets a Cursor pointing to the download(s) of the current system update.
    219          * @hide
    220          */
    221         private static final Cursor getCurrentOtaDownloads(Context context, String url) {
    222             return context.getContentResolver().query(
    223                     android.provider.Downloads.Impl.CONTENT_URI,
    224                     DOWNLOADS_PROJECTION,
    225                     QUERY_WHERE_APP_DATA_CLAUSE,
    226                     new String[] {url},
    227                     null);
    228         }
    229 
    230         /**
    231          * Returns a StatusInfo with the result of trying to download the
    232          * given URL.  Returns null if no attempts have been made.
    233          */
    234         public static final StatusInfo getStatus(
    235                 Context context,
    236                 String url,
    237                 long redownload_threshold) {
    238             StatusInfo result = null;
    239             boolean hasFailedDownload = false;
    240             long failedDownloadModificationTime = 0;
    241             Cursor c = getCurrentOtaDownloads(context, url);
    242             try {
    243                 while (c != null && c.moveToNext()) {
    244                     if (result == null) {
    245                         result = new StatusInfo();
    246                     }
    247                     int status = getStatusOfDownload(c, redownload_threshold);
    248                     if (status == STATUS_DOWNLOADING_UPDATE ||
    249                         status == STATUS_DOWNLOADED_UPDATE) {
    250                         result.completed = (status == STATUS_DOWNLOADED_UPDATE);
    251                         result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
    252                         result.id = c.getLong(DOWNLOADS_COLUMN_ID);
    253                         result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
    254                         result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
    255                         return result;
    256                     }
    257 
    258                     long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
    259                     if (hasFailedDownload &&
    260                         modTime < failedDownloadModificationTime) {
    261                         // older than the one already in result; skip it.
    262                         continue;
    263                     }
    264 
    265                     hasFailedDownload = true;
    266                     failedDownloadModificationTime = modTime;
    267                     result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
    268                     result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
    269                 }
    270             } finally {
    271                 if (c != null) {
    272                     c.close();
    273                 }
    274             }
    275             return result;
    276         }
    277 
    278         /**
    279          * Query where clause for general querying.
    280          */
    281         private static final String QUERY_WHERE_CLAUSE =
    282                 android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " +
    283                 android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?";
    284 
    285         /**
    286          * Delete all the downloads for a package/class pair.
    287          */
    288         public static final void removeAllDownloadsByPackage(
    289                 Context context,
    290                 String notification_package,
    291                 String notification_class) {
    292             context.getContentResolver().delete(
    293                     android.provider.Downloads.Impl.CONTENT_URI,
    294                     QUERY_WHERE_CLAUSE,
    295                     new String[] { notification_package, notification_class });
    296         }
    297 
    298         /**
    299          * The column for the id in the Cursor returned by
    300          * getProgressCursor()
    301          */
    302         public static final int getProgressColumnId() {
    303             return 0;
    304         }
    305 
    306         /**
    307          * The column for the current byte count in the Cursor returned by
    308          * getProgressCursor()
    309          */
    310         public static final int getProgressColumnCurrentBytes() {
    311             return 1;
    312         }
    313 
    314         /**
    315          * The column for the total byte count in the Cursor returned by
    316          * getProgressCursor()
    317          */
    318         public static final int getProgressColumnTotalBytes() {
    319             return 2;
    320         }
    321 
    322         /** @hide */
    323         private static final String[] PROJECTION = {
    324             BaseColumns._ID,
    325             android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
    326             android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES
    327         };
    328 
    329         /**
    330          * Returns a Cursor representing the progress of the download identified by the ID.
    331          */
    332         public static final Cursor getProgressCursor(Context context, long id) {
    333             Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI,
    334                     String.valueOf(id));
    335             return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null);
    336         }
    337     }
    338 
    339     /**
    340      * Class to access downloads by opaque download id
    341      */
    342     public static final class ById extends DownloadBase {
    343         /** @hide */
    344         private ById() {}
    345 
    346         /**
    347          * Get the mime tupe of the download specified by the download id
    348          */
    349         public static String getMimeTypeForId(Context context, long downloadId) {
    350             ContentResolver cr = context.getContentResolver();
    351 
    352             String mimeType = null;
    353             Cursor downloadCursor = null;
    354 
    355             try {
    356                 Uri downloadUri = getDownloadUri(downloadId);
    357 
    358                 downloadCursor = cr.query(
    359                         downloadUri, new String[]{android.provider.Downloads.Impl.COLUMN_MIME_TYPE},
    360                         null, null, null);
    361                 if (downloadCursor.moveToNext()) {
    362                     mimeType = downloadCursor.getString(0);
    363                 }
    364             } finally {
    365                 if (downloadCursor != null) downloadCursor.close();
    366             }
    367             return mimeType;
    368         }
    369 
    370         /**
    371          * Delete a download by Id
    372          */
    373         public static void deleteDownload(Context context, long downloadId) {
    374             ContentResolver cr = context.getContentResolver();
    375 
    376             String mimeType = null;
    377 
    378             Uri downloadUri = getDownloadUri(downloadId);
    379 
    380             cr.delete(downloadUri, null, null);
    381         }
    382 
    383         /**
    384          * Open a filedescriptor to a particular download
    385          */
    386         public static ParcelFileDescriptor openDownload(
    387                 Context context, long downloadId, String mode)
    388             throws FileNotFoundException
    389         {
    390             ContentResolver cr = context.getContentResolver();
    391 
    392             String mimeType = null;
    393 
    394             Uri downloadUri = getDownloadUri(downloadId);
    395 
    396             return cr.openFileDescriptor(downloadUri, mode);
    397         }
    398 
    399         /**
    400          * Open a stream to a particular download
    401          */
    402         public static InputStream openDownloadStream(Context context, long downloadId)
    403                 throws FileNotFoundException, IOException
    404         {
    405             ContentResolver cr = context.getContentResolver();
    406 
    407             String mimeType = null;
    408 
    409             Uri downloadUri = getDownloadUri(downloadId);
    410 
    411             return cr.openInputStream(downloadUri);
    412         }
    413 
    414         private static Uri getDownloadUri(long downloadId) {
    415             return Uri.parse(android.provider.Downloads.Impl.CONTENT_URI + "/" + downloadId);
    416         }
    417 
    418         /**
    419          * Returns a StatusInfo with the result of trying to download the
    420          * given URL.  Returns null if no attempts have been made.
    421          */
    422         public static final StatusInfo getStatus(
    423                 Context context,
    424                 long downloadId) {
    425             StatusInfo result = null;
    426             boolean hasFailedDownload = false;
    427             long failedDownloadModificationTime = 0;
    428 
    429             Uri downloadUri = getDownloadUri(downloadId);
    430 
    431             ContentResolver cr = context.getContentResolver();
    432 
    433             Cursor c = cr.query(downloadUri, DOWNLOADS_PROJECTION, null /* selection */,
    434                     null /* selection args */, null /* sort order */);
    435             try {
    436                 if (c == null || !c.moveToNext()) {
    437                     return result;
    438                 }
    439 
    440                 if (result == null) {
    441                     result = new StatusInfo();
    442                 }
    443                 int status = getStatusOfDownload(c,0);
    444                 if (status == STATUS_DOWNLOADING_UPDATE ||
    445                         status == STATUS_DOWNLOADED_UPDATE) {
    446                     result.completed = (status == STATUS_DOWNLOADED_UPDATE);
    447                     result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
    448                     result.id = c.getLong(DOWNLOADS_COLUMN_ID);
    449                     result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
    450                     result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
    451                     return result;
    452                 }
    453 
    454                 long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
    455 
    456                 result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
    457                 result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
    458             } finally {
    459                 if (c != null) {
    460                     c.close();
    461                 }
    462             }
    463             return result;
    464         }
    465     }
    466 
    467 
    468     /**
    469      * Base class with common functionality for the various download classes
    470      */
    471     public static class DownloadBase {
    472         /** @hide */
    473         DownloadBase() {}
    474 
    475         /**
    476           * Initiate a download where the download will be tracked by its URI.
    477           */
    478         public static long startDownloadByUri(
    479                 Context context,
    480                 String url,
    481                 String cookieData,
    482                 boolean showDownload,
    483                 int downloadDestination,
    484                 boolean allowRoaming,
    485                 boolean skipIntegrityCheck,
    486                 String title,
    487                 String notification_package,
    488                 String notification_class,
    489                 String notification_extras) {
    490             ContentResolver cr = context.getContentResolver();
    491 
    492             // Tell download manager to start downloading update.
    493             ContentValues values = new ContentValues();
    494             values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
    495             values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
    496             values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
    497                        showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
    498                        : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
    499             if (title != null) {
    500                 values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
    501             }
    502             values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
    503 
    504 
    505             // NOTE:  destination should be seperated from whether the download
    506             // can happen when roaming
    507             int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
    508             switch (downloadDestination) {
    509                 case DOWNLOAD_DESTINATION_EXTERNAL:
    510                     destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
    511                     break;
    512                 case DOWNLOAD_DESTINATION_CACHE:
    513                     if (allowRoaming) {
    514                         destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
    515                     } else {
    516                         destination =
    517                                 android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
    518                     }
    519                     break;
    520                 case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
    521                     destination =
    522                             android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
    523                     break;
    524             }
    525             values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
    526             values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
    527                     skipIntegrityCheck);  // Don't check ETag
    528             if (notification_package != null && notification_class != null) {
    529                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
    530                         notification_package);
    531                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
    532                         notification_class);
    533 
    534                 if (notification_extras != null) {
    535                     values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
    536                             notification_extras);
    537                 }
    538             }
    539 
    540             Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
    541 
    542             long downloadId = DOWNLOAD_ID_INVALID;
    543             if (downloadUri != null) {
    544                 downloadId = Long.parseLong(downloadUri.getLastPathSegment());
    545             }
    546             return downloadId;
    547         }
    548     }
    549 
    550     /** @hide */
    551     private static final int STATUS_INVALID = 0;
    552     /** @hide */
    553     private static final int STATUS_DOWNLOADING_UPDATE = 3;
    554     /** @hide */
    555     private static final int STATUS_DOWNLOADED_UPDATE = 4;
    556 
    557     /**
    558      * Column projection for the query to the download manager. This must match
    559      * with the constants DOWNLOADS_COLUMN_*.
    560      * @hide
    561      */
    562     private static final String[] DOWNLOADS_PROJECTION = {
    563             BaseColumns._ID,
    564             android.provider.Downloads.Impl.COLUMN_APP_DATA,
    565             android.provider.Downloads.Impl.COLUMN_STATUS,
    566             android.provider.Downloads.Impl._DATA,
    567             android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
    568             android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
    569     };
    570 
    571     /**
    572      * The column index for the ID.
    573      * @hide
    574      */
    575     private static final int DOWNLOADS_COLUMN_ID = 0;
    576     /**
    577      * The column index for the URI.
    578      * @hide
    579      */
    580     private static final int DOWNLOADS_COLUMN_URI = 1;
    581     /**
    582      * The column index for the status code.
    583      * @hide
    584      */
    585     private static final int DOWNLOADS_COLUMN_STATUS = 2;
    586     /**
    587      * The column index for the filename.
    588      * @hide
    589      */
    590     private static final int DOWNLOADS_COLUMN_FILENAME = 3;
    591     /**
    592      * The column index for the last modification time.
    593      * @hide
    594      */
    595     private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
    596     /**
    597      * The column index for the number of bytes downloaded so far.
    598      * @hide
    599      */
    600     private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
    601 
    602     /**
    603      * Gets the status of a download.
    604      *
    605      * @param c A Cursor pointing to a download.  The URL column is assumed to be valid.
    606      * @return The status of the download.
    607      * @hide
    608      */
    609     private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
    610         int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
    611         long realtime = SystemClock.elapsedRealtime();
    612 
    613         // TODO(dougz): special handling of 503, 404?  (eg, special
    614         // explanatory messages to user)
    615 
    616         if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
    617             // Check if it's stuck
    618             long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
    619             long now = System.currentTimeMillis();
    620             if (now < modified || now - modified > redownload_threshold) {
    621                 return STATUS_INVALID;
    622             }
    623 
    624             return STATUS_DOWNLOADING_UPDATE;
    625         }
    626 
    627         if (android.provider.Downloads.Impl.isStatusError(status)) {
    628             return STATUS_INVALID;
    629         }
    630 
    631         String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
    632         if (filename == null) {
    633             return STATUS_INVALID;
    634         }
    635 
    636         return STATUS_DOWNLOADED_UPDATE;
    637     }
    638 
    639 
    640     /**
    641      * @hide
    642      */
    643     private Downloads() {}
    644 }
    645