Home | History | Annotate | Download | only in downloads
      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 com.android.providers.downloads;
     18 
     19 import android.app.DownloadManager;
     20 import android.content.ContentResolver;
     21 import android.content.ContentUris;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.database.Cursor;
     26 import android.net.ConnectivityManager;
     27 import android.net.NetworkInfo;
     28 import android.net.NetworkInfo.DetailedState;
     29 import android.net.Uri;
     30 import android.os.Environment;
     31 import android.provider.Downloads;
     32 import android.provider.Downloads.Impl;
     33 import android.text.TextUtils;
     34 import android.util.Pair;
     35 
     36 import com.android.internal.annotations.GuardedBy;
     37 import com.android.internal.util.IndentingPrintWriter;
     38 
     39 import java.util.ArrayList;
     40 import java.util.Collection;
     41 import java.util.Collections;
     42 import java.util.List;
     43 import java.util.concurrent.Executor;
     44 import java.util.concurrent.ExecutorService;
     45 import java.util.concurrent.Future;
     46 
     47 /**
     48  * Stores information about an individual download.
     49  */
     50 public class DownloadInfo {
     51     // TODO: move towards these in-memory objects being sources of truth, and
     52     // periodically pushing to provider.
     53 
     54     public static class Reader {
     55         private ContentResolver mResolver;
     56         private Cursor mCursor;
     57 
     58         public Reader(ContentResolver resolver, Cursor cursor) {
     59             mResolver = resolver;
     60             mCursor = cursor;
     61         }
     62 
     63         public DownloadInfo newDownloadInfo(Context context, SystemFacade systemFacade,
     64                 StorageManager storageManager, DownloadNotifier notifier) {
     65             final DownloadInfo info = new DownloadInfo(
     66                     context, systemFacade, storageManager, notifier);
     67             updateFromDatabase(info);
     68             readRequestHeaders(info);
     69             return info;
     70         }
     71 
     72         public void updateFromDatabase(DownloadInfo info) {
     73             info.mId = getLong(Downloads.Impl._ID);
     74             info.mUri = getString(Downloads.Impl.COLUMN_URI);
     75             info.mNoIntegrity = getInt(Downloads.Impl.COLUMN_NO_INTEGRITY) == 1;
     76             info.mHint = getString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
     77             info.mFileName = getString(Downloads.Impl._DATA);
     78             info.mMimeType = getString(Downloads.Impl.COLUMN_MIME_TYPE);
     79             info.mDestination = getInt(Downloads.Impl.COLUMN_DESTINATION);
     80             info.mVisibility = getInt(Downloads.Impl.COLUMN_VISIBILITY);
     81             info.mStatus = getInt(Downloads.Impl.COLUMN_STATUS);
     82             info.mNumFailed = getInt(Downloads.Impl.COLUMN_FAILED_CONNECTIONS);
     83             int retryRedirect = getInt(Constants.RETRY_AFTER_X_REDIRECT_COUNT);
     84             info.mRetryAfter = retryRedirect & 0xfffffff;
     85             info.mLastMod = getLong(Downloads.Impl.COLUMN_LAST_MODIFICATION);
     86             info.mPackage = getString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
     87             info.mClass = getString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
     88             info.mExtras = getString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS);
     89             info.mCookies = getString(Downloads.Impl.COLUMN_COOKIE_DATA);
     90             info.mUserAgent = getString(Downloads.Impl.COLUMN_USER_AGENT);
     91             info.mReferer = getString(Downloads.Impl.COLUMN_REFERER);
     92             info.mTotalBytes = getLong(Downloads.Impl.COLUMN_TOTAL_BYTES);
     93             info.mCurrentBytes = getLong(Downloads.Impl.COLUMN_CURRENT_BYTES);
     94             info.mETag = getString(Constants.ETAG);
     95             info.mUid = getInt(Constants.UID);
     96             info.mMediaScanned = getInt(Constants.MEDIA_SCANNED);
     97             info.mDeleted = getInt(Downloads.Impl.COLUMN_DELETED) == 1;
     98             info.mMediaProviderUri = getString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
     99             info.mIsPublicApi = getInt(Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0;
    100             info.mAllowedNetworkTypes = getInt(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES);
    101             info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0;
    102             info.mAllowMetered = getInt(Downloads.Impl.COLUMN_ALLOW_METERED) != 0;
    103             info.mTitle = getString(Downloads.Impl.COLUMN_TITLE);
    104             info.mDescription = getString(Downloads.Impl.COLUMN_DESCRIPTION);
    105             info.mBypassRecommendedSizeLimit =
    106                     getInt(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
    107 
    108             synchronized (this) {
    109                 info.mControl = getInt(Downloads.Impl.COLUMN_CONTROL);
    110             }
    111         }
    112 
    113         private void readRequestHeaders(DownloadInfo info) {
    114             info.mRequestHeaders.clear();
    115             Uri headerUri = Uri.withAppendedPath(
    116                     info.getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT);
    117             Cursor cursor = mResolver.query(headerUri, null, null, null, null);
    118             try {
    119                 int headerIndex =
    120                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER);
    121                 int valueIndex =
    122                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE);
    123                 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    124                     addHeader(info, cursor.getString(headerIndex), cursor.getString(valueIndex));
    125                 }
    126             } finally {
    127                 cursor.close();
    128             }
    129 
    130             if (info.mCookies != null) {
    131                 addHeader(info, "Cookie", info.mCookies);
    132             }
    133             if (info.mReferer != null) {
    134                 addHeader(info, "Referer", info.mReferer);
    135             }
    136         }
    137 
    138         private void addHeader(DownloadInfo info, String header, String value) {
    139             info.mRequestHeaders.add(Pair.create(header, value));
    140         }
    141 
    142         private String getString(String column) {
    143             int index = mCursor.getColumnIndexOrThrow(column);
    144             String s = mCursor.getString(index);
    145             return (TextUtils.isEmpty(s)) ? null : s;
    146         }
    147 
    148         private Integer getInt(String column) {
    149             return mCursor.getInt(mCursor.getColumnIndexOrThrow(column));
    150         }
    151 
    152         private Long getLong(String column) {
    153             return mCursor.getLong(mCursor.getColumnIndexOrThrow(column));
    154         }
    155     }
    156 
    157     /**
    158      * Constants used to indicate network state for a specific download, after
    159      * applying any requested constraints.
    160      */
    161     public enum NetworkState {
    162         /**
    163          * The network is usable for the given download.
    164          */
    165         OK,
    166 
    167         /**
    168          * There is no network connectivity.
    169          */
    170         NO_CONNECTION,
    171 
    172         /**
    173          * The download exceeds the maximum size for this network.
    174          */
    175         UNUSABLE_DUE_TO_SIZE,
    176 
    177         /**
    178          * The download exceeds the recommended maximum size for this network,
    179          * the user must confirm for this download to proceed without WiFi.
    180          */
    181         RECOMMENDED_UNUSABLE_DUE_TO_SIZE,
    182 
    183         /**
    184          * The current connection is roaming, and the download can't proceed
    185          * over a roaming connection.
    186          */
    187         CANNOT_USE_ROAMING,
    188 
    189         /**
    190          * The app requesting the download specific that it can't use the
    191          * current network connection.
    192          */
    193         TYPE_DISALLOWED_BY_REQUESTOR,
    194 
    195         /**
    196          * Current network is blocked for requesting application.
    197          */
    198         BLOCKED;
    199     }
    200 
    201     /**
    202      * For intents used to notify the user that a download exceeds a size threshold, if this extra
    203      * is true, WiFi is required for this download size; otherwise, it is only recommended.
    204      */
    205     public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
    206 
    207     public long mId;
    208     public String mUri;
    209     public boolean mNoIntegrity;
    210     public String mHint;
    211     public String mFileName;
    212     public String mMimeType;
    213     public int mDestination;
    214     public int mVisibility;
    215     public int mControl;
    216     public int mStatus;
    217     public int mNumFailed;
    218     public int mRetryAfter;
    219     public long mLastMod;
    220     public String mPackage;
    221     public String mClass;
    222     public String mExtras;
    223     public String mCookies;
    224     public String mUserAgent;
    225     public String mReferer;
    226     public long mTotalBytes;
    227     public long mCurrentBytes;
    228     public String mETag;
    229     public int mUid;
    230     public int mMediaScanned;
    231     public boolean mDeleted;
    232     public String mMediaProviderUri;
    233     public boolean mIsPublicApi;
    234     public int mAllowedNetworkTypes;
    235     public boolean mAllowRoaming;
    236     public boolean mAllowMetered;
    237     public String mTitle;
    238     public String mDescription;
    239     public int mBypassRecommendedSizeLimit;
    240 
    241     public int mFuzz;
    242 
    243     private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
    244 
    245     /**
    246      * Result of last {@link DownloadThread} started by
    247      * {@link #startDownloadIfReady(ExecutorService)}.
    248      */
    249     @GuardedBy("this")
    250     private Future<?> mSubmittedTask;
    251 
    252     @GuardedBy("this")
    253     private DownloadThread mTask;
    254 
    255     private final Context mContext;
    256     private final SystemFacade mSystemFacade;
    257     private final StorageManager mStorageManager;
    258     private final DownloadNotifier mNotifier;
    259 
    260     private DownloadInfo(Context context, SystemFacade systemFacade, StorageManager storageManager,
    261             DownloadNotifier notifier) {
    262         mContext = context;
    263         mSystemFacade = systemFacade;
    264         mStorageManager = storageManager;
    265         mNotifier = notifier;
    266         mFuzz = Helpers.sRandom.nextInt(1001);
    267     }
    268 
    269     public Collection<Pair<String, String>> getHeaders() {
    270         return Collections.unmodifiableList(mRequestHeaders);
    271     }
    272 
    273     public void sendIntentIfRequested() {
    274         if (mPackage == null) {
    275             return;
    276         }
    277 
    278         Intent intent;
    279         if (mIsPublicApi) {
    280             intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
    281             intent.setPackage(mPackage);
    282             intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, mId);
    283         } else { // legacy behavior
    284             if (mClass == null) {
    285                 return;
    286             }
    287             intent = new Intent(Downloads.Impl.ACTION_DOWNLOAD_COMPLETED);
    288             intent.setClassName(mPackage, mClass);
    289             if (mExtras != null) {
    290                 intent.putExtra(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, mExtras);
    291             }
    292             // We only send the content: URI, for security reasons. Otherwise, malicious
    293             //     applications would have an easier time spoofing download results by
    294             //     sending spoofed intents.
    295             intent.setData(getMyDownloadsUri());
    296         }
    297         mSystemFacade.sendBroadcast(intent);
    298     }
    299 
    300     /**
    301      * Returns the time when a download should be restarted.
    302      */
    303     public long restartTime(long now) {
    304         if (mNumFailed == 0) {
    305             return now;
    306         }
    307         if (mRetryAfter > 0) {
    308             return mLastMod + mRetryAfter;
    309         }
    310         return mLastMod +
    311                 Constants.RETRY_FIRST_DELAY *
    312                     (1000 + mFuzz) * (1 << (mNumFailed - 1));
    313     }
    314 
    315     /**
    316      * Returns whether this download should be enqueued.
    317      */
    318     private boolean isReadyToDownload() {
    319         if (mControl == Downloads.Impl.CONTROL_PAUSED) {
    320             // the download is paused, so it's not going to start
    321             return false;
    322         }
    323         switch (mStatus) {
    324             case 0: // status hasn't been initialized yet, this is a new download
    325             case Downloads.Impl.STATUS_PENDING: // download is explicit marked as ready to start
    326             case Downloads.Impl.STATUS_RUNNING: // download interrupted (process killed etc) while
    327                                                 // running, without a chance to update the database
    328                 return true;
    329 
    330             case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
    331             case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
    332                 return checkCanUseNetwork() == NetworkState.OK;
    333 
    334             case Downloads.Impl.STATUS_WAITING_TO_RETRY:
    335                 // download was waiting for a delayed restart
    336                 final long now = mSystemFacade.currentTimeMillis();
    337                 return restartTime(now) <= now;
    338             case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
    339                 // is the media mounted?
    340                 return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    341             case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
    342                 // avoids repetition of retrying download
    343                 return false;
    344         }
    345         return false;
    346     }
    347 
    348     /**
    349      * Returns whether this download has a visible notification after
    350      * completion.
    351      */
    352     public boolean hasCompletionNotification() {
    353         if (!Downloads.Impl.isStatusCompleted(mStatus)) {
    354             return false;
    355         }
    356         if (mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
    357             return true;
    358         }
    359         return false;
    360     }
    361 
    362     /**
    363      * Returns whether this download is allowed to use the network.
    364      */
    365     public NetworkState checkCanUseNetwork() {
    366         final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mUid);
    367         if (info == null || !info.isConnected()) {
    368             return NetworkState.NO_CONNECTION;
    369         }
    370         if (DetailedState.BLOCKED.equals(info.getDetailedState())) {
    371             return NetworkState.BLOCKED;
    372         }
    373         if (mSystemFacade.isNetworkRoaming() && !isRoamingAllowed()) {
    374             return NetworkState.CANNOT_USE_ROAMING;
    375         }
    376         if (mSystemFacade.isActiveNetworkMetered() && !mAllowMetered) {
    377             return NetworkState.TYPE_DISALLOWED_BY_REQUESTOR;
    378         }
    379         return checkIsNetworkTypeAllowed(info.getType());
    380     }
    381 
    382     private boolean isRoamingAllowed() {
    383         if (mIsPublicApi) {
    384             return mAllowRoaming;
    385         } else { // legacy behavior
    386             return mDestination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
    387         }
    388     }
    389 
    390     /**
    391      * Check if this download can proceed over the given network type.
    392      * @param networkType a constant from ConnectivityManager.TYPE_*.
    393      * @return one of the NETWORK_* constants
    394      */
    395     private NetworkState checkIsNetworkTypeAllowed(int networkType) {
    396         if (mIsPublicApi) {
    397             final int flag = translateNetworkTypeToApiFlag(networkType);
    398             final boolean allowAllNetworkTypes = mAllowedNetworkTypes == ~0;
    399             if (!allowAllNetworkTypes && (flag & mAllowedNetworkTypes) == 0) {
    400                 return NetworkState.TYPE_DISALLOWED_BY_REQUESTOR;
    401             }
    402         }
    403         return checkSizeAllowedForNetwork(networkType);
    404     }
    405 
    406     /**
    407      * Translate a ConnectivityManager.TYPE_* constant to the corresponding
    408      * DownloadManager.Request.NETWORK_* bit flag.
    409      */
    410     private int translateNetworkTypeToApiFlag(int networkType) {
    411         switch (networkType) {
    412             case ConnectivityManager.TYPE_MOBILE:
    413                 return DownloadManager.Request.NETWORK_MOBILE;
    414 
    415             case ConnectivityManager.TYPE_WIFI:
    416                 return DownloadManager.Request.NETWORK_WIFI;
    417 
    418             case ConnectivityManager.TYPE_BLUETOOTH:
    419                 return DownloadManager.Request.NETWORK_BLUETOOTH;
    420 
    421             default:
    422                 return 0;
    423         }
    424     }
    425 
    426     /**
    427      * Check if the download's size prohibits it from running over the current network.
    428      * @return one of the NETWORK_* constants
    429      */
    430     private NetworkState checkSizeAllowedForNetwork(int networkType) {
    431         if (mTotalBytes <= 0) {
    432             return NetworkState.OK; // we don't know the size yet
    433         }
    434         if (networkType == ConnectivityManager.TYPE_WIFI) {
    435             return NetworkState.OK; // anything goes over wifi
    436         }
    437         Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile();
    438         if (maxBytesOverMobile != null && mTotalBytes > maxBytesOverMobile) {
    439             return NetworkState.UNUSABLE_DUE_TO_SIZE;
    440         }
    441         if (mBypassRecommendedSizeLimit == 0) {
    442             Long recommendedMaxBytesOverMobile = mSystemFacade.getRecommendedMaxBytesOverMobile();
    443             if (recommendedMaxBytesOverMobile != null
    444                     && mTotalBytes > recommendedMaxBytesOverMobile) {
    445                 return NetworkState.RECOMMENDED_UNUSABLE_DUE_TO_SIZE;
    446             }
    447         }
    448         return NetworkState.OK;
    449     }
    450 
    451     /**
    452      * If download is ready to start, and isn't already pending or executing,
    453      * create a {@link DownloadThread} and enqueue it into given
    454      * {@link Executor}.
    455      *
    456      * @return If actively downloading.
    457      */
    458     public boolean startDownloadIfReady(ExecutorService executor) {
    459         synchronized (this) {
    460             final boolean isReady = isReadyToDownload();
    461             final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
    462             if (isReady && !isActive) {
    463                 if (mStatus != Impl.STATUS_RUNNING) {
    464                     mStatus = Impl.STATUS_RUNNING;
    465                     ContentValues values = new ContentValues();
    466                     values.put(Impl.COLUMN_STATUS, mStatus);
    467                     mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
    468                 }
    469 
    470                 mTask = new DownloadThread(
    471                         mContext, mSystemFacade, this, mStorageManager, mNotifier);
    472                 mSubmittedTask = executor.submit(mTask);
    473             }
    474             return isReady;
    475         }
    476     }
    477 
    478     /**
    479      * If download is ready to be scanned, enqueue it into the given
    480      * {@link DownloadScanner}.
    481      *
    482      * @return If actively scanning.
    483      */
    484     public boolean startScanIfReady(DownloadScanner scanner) {
    485         synchronized (this) {
    486             final boolean isReady = shouldScanFile();
    487             if (isReady) {
    488                 scanner.requestScan(this);
    489             }
    490             return isReady;
    491         }
    492     }
    493 
    494     public boolean isOnCache() {
    495         return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION
    496                 || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION
    497                 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
    498                 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
    499     }
    500 
    501     public Uri getMyDownloadsUri() {
    502         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId);
    503     }
    504 
    505     public Uri getAllDownloadsUri() {
    506         return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId);
    507     }
    508 
    509     public void dump(IndentingPrintWriter pw) {
    510         pw.println("DownloadInfo:");
    511         pw.increaseIndent();
    512 
    513         pw.printPair("mId", mId);
    514         pw.printPair("mLastMod", mLastMod);
    515         pw.printPair("mPackage", mPackage);
    516         pw.printPair("mUid", mUid);
    517         pw.println();
    518 
    519         pw.printPair("mUri", mUri);
    520         pw.println();
    521 
    522         pw.printPair("mMimeType", mMimeType);
    523         pw.printPair("mCookies", (mCookies != null) ? "yes" : "no");
    524         pw.printPair("mReferer", (mReferer != null) ? "yes" : "no");
    525         pw.printPair("mUserAgent", mUserAgent);
    526         pw.println();
    527 
    528         pw.printPair("mFileName", mFileName);
    529         pw.printPair("mDestination", mDestination);
    530         pw.println();
    531 
    532         pw.printPair("mStatus", Downloads.Impl.statusToString(mStatus));
    533         pw.printPair("mCurrentBytes", mCurrentBytes);
    534         pw.printPair("mTotalBytes", mTotalBytes);
    535         pw.println();
    536 
    537         pw.printPair("mNumFailed", mNumFailed);
    538         pw.printPair("mRetryAfter", mRetryAfter);
    539         pw.printPair("mETag", mETag);
    540         pw.printPair("mIsPublicApi", mIsPublicApi);
    541         pw.println();
    542 
    543         pw.printPair("mAllowedNetworkTypes", mAllowedNetworkTypes);
    544         pw.printPair("mAllowRoaming", mAllowRoaming);
    545         pw.printPair("mAllowMetered", mAllowMetered);
    546         pw.println();
    547 
    548         pw.decreaseIndent();
    549     }
    550 
    551     /**
    552      * Return time when this download will be ready for its next action, in
    553      * milliseconds after given time.
    554      *
    555      * @return If {@code 0}, download is ready to proceed immediately. If
    556      *         {@link Long#MAX_VALUE}, then download has no future actions.
    557      */
    558     public long nextActionMillis(long now) {
    559         if (Downloads.Impl.isStatusCompleted(mStatus)) {
    560             return Long.MAX_VALUE;
    561         }
    562         if (mStatus != Downloads.Impl.STATUS_WAITING_TO_RETRY) {
    563             return 0;
    564         }
    565         long when = restartTime(now);
    566         if (when <= now) {
    567             return 0;
    568         }
    569         return when - now;
    570     }
    571 
    572     /**
    573      * Returns whether a file should be scanned
    574      */
    575     public boolean shouldScanFile() {
    576         return (mMediaScanned == 0)
    577                 && (mDestination == Downloads.Impl.DESTINATION_EXTERNAL ||
    578                         mDestination == Downloads.Impl.DESTINATION_FILE_URI ||
    579                         mDestination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD)
    580                 && Downloads.Impl.isStatusSuccess(mStatus);
    581     }
    582 
    583     void notifyPauseDueToSize(boolean isWifiRequired) {
    584         Intent intent = new Intent(Intent.ACTION_VIEW);
    585         intent.setData(getAllDownloadsUri());
    586         intent.setClassName(SizeLimitActivity.class.getPackage().getName(),
    587                 SizeLimitActivity.class.getName());
    588         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    589         intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired);
    590         mContext.startActivity(intent);
    591     }
    592 
    593     /**
    594      * Query and return status of requested download.
    595      */
    596     public static int queryDownloadStatus(ContentResolver resolver, long id) {
    597         final Cursor cursor = resolver.query(
    598                 ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id),
    599                 new String[] { Downloads.Impl.COLUMN_STATUS }, null, null, null);
    600         try {
    601             if (cursor.moveToFirst()) {
    602                 return cursor.getInt(0);
    603             } else {
    604                 // TODO: increase strictness of value returned for unknown
    605                 // downloads; this is safe default for now.
    606                 return Downloads.Impl.STATUS_PENDING;
    607             }
    608         } finally {
    609             cursor.close();
    610         }
    611     }
    612 }
    613