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