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