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