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.isStatusCompleted(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(
    434                     downloadUri, DOWNLOADS_PROJECTION, null /* selection */, null /* selection args */,
    435                     null /* sort order */);
    436             try {
    437                 if (!c.moveToNext()) {
    438                     return result;
    439                 }
    440 
    441                 if (result == null) {
    442                     result = new StatusInfo();
    443                 }
    444                 int status = getStatusOfDownload(c,0);
    445                 if (status == STATUS_DOWNLOADING_UPDATE ||
    446                         status == STATUS_DOWNLOADED_UPDATE) {
    447                     result.completed = (status == STATUS_DOWNLOADED_UPDATE);
    448                     result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
    449                     result.id = c.getLong(DOWNLOADS_COLUMN_ID);
    450                     result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
    451                     result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
    452                     return result;
    453                 }
    454 
    455                 long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
    456 
    457                 result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
    458                 result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
    459             } finally {
    460                 if (c != null) {
    461                     c.close();
    462                 }
    463             }
    464             return result;
    465         }
    466     }
    467 
    468 
    469     /**
    470      * Base class with common functionality for the various download classes
    471      */
    472     public static class DownloadBase {
    473         /** @hide */
    474         DownloadBase() {}
    475 
    476         /**
    477           * Initiate a download where the download will be tracked by its URI.
    478           */
    479         public static long startDownloadByUri(
    480                 Context context,
    481                 String url,
    482                 String cookieData,
    483                 boolean showDownload,
    484                 int downloadDestination,
    485                 boolean allowRoaming,
    486                 boolean skipIntegrityCheck,
    487                 String title,
    488                 String notification_package,
    489                 String notification_class,
    490                 String notification_extras) {
    491             ContentResolver cr = context.getContentResolver();
    492 
    493             // Tell download manager to start downloading update.
    494             ContentValues values = new ContentValues();
    495             values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
    496             values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
    497             values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
    498                        showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
    499                        : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
    500             if (title != null) {
    501                 values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
    502             }
    503             values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
    504 
    505 
    506             // NOTE:  destination should be seperated from whether the download
    507             // can happen when roaming
    508             int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
    509             switch (downloadDestination) {
    510                 case DOWNLOAD_DESTINATION_EXTERNAL:
    511                     destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
    512                     break;
    513                 case DOWNLOAD_DESTINATION_CACHE:
    514                     if (allowRoaming) {
    515                         destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
    516                     } else {
    517                         destination =
    518                                 android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
    519                     }
    520                     break;
    521                 case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
    522                     destination =
    523                             android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
    524                     break;
    525             }
    526             values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
    527             values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
    528                     skipIntegrityCheck);  // Don't check ETag
    529             if (notification_package != null && notification_class != null) {
    530                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
    531                         notification_package);
    532                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
    533                         notification_class);
    534 
    535                 if (notification_extras != null) {
    536                     values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
    537                             notification_extras);
    538                 }
    539             }
    540 
    541             Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
    542 
    543             long downloadId = DOWNLOAD_ID_INVALID;
    544             if (downloadUri != null) {
    545                 downloadId = Long.parseLong(downloadUri.getLastPathSegment());
    546             }
    547             return downloadId;
    548         }
    549     }
    550 
    551     /** @hide */
    552     private static final int STATUS_INVALID = 0;
    553     /** @hide */
    554     private static final int STATUS_DOWNLOADING_UPDATE = 3;
    555     /** @hide */
    556     private static final int STATUS_DOWNLOADED_UPDATE = 4;
    557 
    558     /**
    559      * Column projection for the query to the download manager. This must match
    560      * with the constants DOWNLOADS_COLUMN_*.
    561      * @hide
    562      */
    563     private static final String[] DOWNLOADS_PROJECTION = {
    564             BaseColumns._ID,
    565             android.provider.Downloads.Impl.COLUMN_APP_DATA,
    566             android.provider.Downloads.Impl.COLUMN_STATUS,
    567             android.provider.Downloads.Impl._DATA,
    568             android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
    569             android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
    570     };
    571 
    572     /**
    573      * The column index for the ID.
    574      * @hide
    575      */
    576     private static final int DOWNLOADS_COLUMN_ID = 0;
    577     /**
    578      * The column index for the URI.
    579      * @hide
    580      */
    581     private static final int DOWNLOADS_COLUMN_URI = 1;
    582     /**
    583      * The column index for the status code.
    584      * @hide
    585      */
    586     private static final int DOWNLOADS_COLUMN_STATUS = 2;
    587     /**
    588      * The column index for the filename.
    589      * @hide
    590      */
    591     private static final int DOWNLOADS_COLUMN_FILENAME = 3;
    592     /**
    593      * The column index for the last modification time.
    594      * @hide
    595      */
    596     private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
    597     /**
    598      * The column index for the number of bytes downloaded so far.
    599      * @hide
    600      */
    601     private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
    602 
    603     /**
    604      * Gets the status of a download.
    605      *
    606      * @param c A Cursor pointing to a download.  The URL column is assumed to be valid.
    607      * @return The status of the download.
    608      * @hide
    609      */
    610     private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
    611         int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
    612         long realtime = SystemClock.elapsedRealtime();
    613 
    614         // TODO(dougz): special handling of 503, 404?  (eg, special
    615         // explanatory messages to user)
    616 
    617         if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
    618             // Check if it's stuck
    619             long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
    620             long now = System.currentTimeMillis();
    621             if (now < modified || now - modified > redownload_threshold) {
    622                 return STATUS_INVALID;
    623             }
    624 
    625             return STATUS_DOWNLOADING_UPDATE;
    626         }
    627 
    628         if (android.provider.Downloads.Impl.isStatusError(status)) {
    629             return STATUS_INVALID;
    630         }
    631 
    632         String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
    633         if (filename == null) {
    634             return STATUS_INVALID;
    635         }
    636 
    637         return STATUS_DOWNLOADED_UPDATE;
    638     }
    639 
    640 
    641     /**
    642      * @hide
    643      */
    644     private Downloads() {}
    645 }
    646