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