• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Reader(ContentResolver resolver, Cursor cursor)58         public Reader(ContentResolver resolver, Cursor cursor) {
59             mResolver = resolver;
60             mCursor = cursor;
61         }
62 
newDownloadInfo(Context context, SystemFacade systemFacade, StorageManager storageManager, DownloadNotifier notifier)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 
updateFromDatabase(DownloadInfo info)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 
readRequestHeaders(DownloadInfo info)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 
addHeader(DownloadInfo info, String header, String value)138         private void addHeader(DownloadInfo info, String header, String value) {
139             info.mRequestHeaders.add(Pair.create(header, value));
140         }
141 
getString(String column)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 
getInt(String column)148         private Integer getInt(String column) {
149             return mCursor.getInt(mCursor.getColumnIndexOrThrow(column));
150         }
151 
getLong(String column)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 
DownloadInfo(Context context, SystemFacade systemFacade, StorageManager storageManager, DownloadNotifier notifier)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 
getHeaders()269     public Collection<Pair<String, String>> getHeaders() {
270         return Collections.unmodifiableList(mRequestHeaders);
271     }
272 
sendIntentIfRequested()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      */
restartTime(long now)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      */
isReadyToDownload()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      */
hasCompletionNotification()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      */
checkCanUseNetwork()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 
isRoamingAllowed()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      */
checkIsNetworkTypeAllowed(int networkType)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      */
translateNetworkTypeToApiFlag(int networkType)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      */
checkSizeAllowedForNetwork(int networkType)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      */
startDownloadIfReady(ExecutorService executor)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      */
startScanIfReady(DownloadScanner scanner)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 
isOnCache()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 
getMyDownloadsUri()501     public Uri getMyDownloadsUri() {
502         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId);
503     }
504 
getAllDownloadsUri()505     public Uri getAllDownloadsUri() {
506         return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId);
507     }
508 
dump(IndentingPrintWriter pw)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      */
nextActionMillis(long now)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      */
shouldScanFile()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 
notifyPauseDueToSize(boolean isWifiRequired)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      */
queryDownloadStatus(ContentResolver resolver, long id)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