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