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.Log; 35 import android.util.Pair; 36 37 import com.android.internal.util.IndentingPrintWriter; 38 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.Collections; 43 import java.util.List; 44 45 /** 46 * Stores information about an individual download. 47 */ 48 public class DownloadInfo { 49 public static class Reader { 50 private ContentResolver mResolver; 51 private Cursor mCursor; 52 Reader(ContentResolver resolver, Cursor cursor)53 public Reader(ContentResolver resolver, Cursor cursor) { 54 mResolver = resolver; 55 mCursor = cursor; 56 } 57 newDownloadInfo(Context context, SystemFacade systemFacade)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 updateFromDatabase(DownloadInfo info)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 readRequestHeaders(DownloadInfo info)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 addHeader(DownloadInfo info, String header, String value)131 private void addHeader(DownloadInfo info, String header, String value) { 132 info.mRequestHeaders.add(Pair.create(header, value)); 133 } 134 getString(String column)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 getInt(String column)141 private Integer getInt(String column) { 142 return mCursor.getInt(mCursor.getColumnIndexOrThrow(column)); 143 } 144 getLong(String column)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 DownloadInfo(Context context, SystemFacade systemFacade)236 private DownloadInfo(Context context, SystemFacade systemFacade) { 237 mContext = context; 238 mSystemFacade = systemFacade; 239 mFuzz = Helpers.sRandom.nextInt(1001); 240 } 241 getHeaders()242 public Collection<Pair<String, String>> getHeaders() { 243 return Collections.unmodifiableList(mRequestHeaders); 244 } 245 sendIntentIfRequested()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 */ restartTime(long now)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 */ isReadyToStart(long now)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 // should check space to make sure it is worth retrying the download. 320 // but thats the first thing done by the thread when it retries to download 321 // it will fail pretty quickly if there is no space. 322 // so, it is not that bad to skip checking space availability here. 323 return true; 324 } 325 return false; 326 } 327 328 /** 329 * Returns whether this download has a visible notification after 330 * completion. 331 */ hasCompletionNotification()332 public boolean hasCompletionNotification() { 333 if (!Downloads.Impl.isStatusCompleted(mStatus)) { 334 return false; 335 } 336 if (mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { 337 return true; 338 } 339 return false; 340 } 341 342 /** 343 * Returns whether this download is allowed to use the network. 344 * @return one of the NETWORK_* constants 345 */ checkCanUseNetwork()346 public int checkCanUseNetwork() { 347 final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mUid); 348 if (info == null) { 349 return NETWORK_NO_CONNECTION; 350 } 351 if (DetailedState.BLOCKED.equals(info.getDetailedState())) { 352 return NETWORK_BLOCKED; 353 } 354 if (!isRoamingAllowed() && mSystemFacade.isNetworkRoaming()) { 355 return NETWORK_CANNOT_USE_ROAMING; 356 } 357 if (!mAllowMetered && mSystemFacade.isActiveNetworkMetered()) { 358 return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; 359 } 360 return checkIsNetworkTypeAllowed(info.getType()); 361 } 362 isRoamingAllowed()363 private boolean isRoamingAllowed() { 364 if (mIsPublicApi) { 365 return mAllowRoaming; 366 } else { // legacy behavior 367 return mDestination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING; 368 } 369 } 370 371 /** 372 * @return a non-localized string appropriate for logging corresponding to one of the 373 * NETWORK_* constants. 374 */ getLogMessageForNetworkError(int networkError)375 public String getLogMessageForNetworkError(int networkError) { 376 switch (networkError) { 377 case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE: 378 return "download size exceeds recommended limit for mobile network"; 379 380 case NETWORK_UNUSABLE_DUE_TO_SIZE: 381 return "download size exceeds limit for mobile network"; 382 383 case NETWORK_NO_CONNECTION: 384 return "no network connection available"; 385 386 case NETWORK_CANNOT_USE_ROAMING: 387 return "download cannot use the current network connection because it is roaming"; 388 389 case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: 390 return "download was requested to not use the current network type"; 391 392 case NETWORK_BLOCKED: 393 return "network is blocked for requesting application"; 394 395 default: 396 return "unknown error with network connectivity"; 397 } 398 } 399 400 /** 401 * Check if this download can proceed over the given network type. 402 * @param networkType a constant from ConnectivityManager.TYPE_*. 403 * @return one of the NETWORK_* constants 404 */ checkIsNetworkTypeAllowed(int networkType)405 private int checkIsNetworkTypeAllowed(int networkType) { 406 if (mIsPublicApi) { 407 final int flag = translateNetworkTypeToApiFlag(networkType); 408 final boolean allowAllNetworkTypes = mAllowedNetworkTypes == ~0; 409 if (!allowAllNetworkTypes && (flag & mAllowedNetworkTypes) == 0) { 410 return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; 411 } 412 } 413 return checkSizeAllowedForNetwork(networkType); 414 } 415 416 /** 417 * Translate a ConnectivityManager.TYPE_* constant to the corresponding 418 * DownloadManager.Request.NETWORK_* bit flag. 419 */ translateNetworkTypeToApiFlag(int networkType)420 private int translateNetworkTypeToApiFlag(int networkType) { 421 switch (networkType) { 422 case ConnectivityManager.TYPE_MOBILE: 423 return DownloadManager.Request.NETWORK_MOBILE; 424 425 case ConnectivityManager.TYPE_WIFI: 426 return DownloadManager.Request.NETWORK_WIFI; 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)437 private int checkSizeAllowedForNetwork(int networkType) { 438 if (mTotalBytes <= 0) { 439 return NETWORK_OK; // we don't know the size yet 440 } 441 if (networkType == ConnectivityManager.TYPE_WIFI) { 442 return NETWORK_OK; // anything goes over wifi 443 } 444 Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile(); 445 if (maxBytesOverMobile != null && mTotalBytes > maxBytesOverMobile) { 446 return NETWORK_UNUSABLE_DUE_TO_SIZE; 447 } 448 if (mBypassRecommendedSizeLimit == 0) { 449 Long recommendedMaxBytesOverMobile = mSystemFacade.getRecommendedMaxBytesOverMobile(); 450 if (recommendedMaxBytesOverMobile != null 451 && mTotalBytes > recommendedMaxBytesOverMobile) { 452 return NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE; 453 } 454 } 455 return NETWORK_OK; 456 } 457 startIfReady(long now, StorageManager storageManager)458 void startIfReady(long now, StorageManager storageManager) { 459 if (!isReadyToStart(now)) { 460 return; 461 } 462 463 if (Constants.LOGV) { 464 Log.v(Constants.TAG, "Service spawning thread to handle download " + mId); 465 } 466 if (mStatus != Impl.STATUS_RUNNING) { 467 mStatus = Impl.STATUS_RUNNING; 468 ContentValues values = new ContentValues(); 469 values.put(Impl.COLUMN_STATUS, mStatus); 470 mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null); 471 } 472 DownloadHandler.getInstance().enqueueDownload(this); 473 } 474 isOnCache()475 public boolean isOnCache() { 476 return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION 477 || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION 478 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING 479 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); 480 } 481 getMyDownloadsUri()482 public Uri getMyDownloadsUri() { 483 return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId); 484 } 485 getAllDownloadsUri()486 public Uri getAllDownloadsUri() { 487 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId); 488 } 489 dump(IndentingPrintWriter pw)490 public void dump(IndentingPrintWriter pw) { 491 pw.println("DownloadInfo:"); 492 pw.increaseIndent(); 493 494 pw.printPair("mId", mId); 495 pw.printPair("mLastMod", mLastMod); 496 pw.printPair("mPackage", mPackage); 497 pw.printPair("mUid", mUid); 498 pw.println(); 499 500 pw.printPair("mUri", mUri); 501 pw.println(); 502 503 pw.printPair("mMimeType", mMimeType); 504 pw.printPair("mCookies", (mCookies != null) ? "yes" : "no"); 505 pw.printPair("mReferer", (mReferer != null) ? "yes" : "no"); 506 pw.printPair("mUserAgent", mUserAgent); 507 pw.println(); 508 509 pw.printPair("mFileName", mFileName); 510 pw.printPair("mDestination", mDestination); 511 pw.println(); 512 513 pw.printPair("mStatus", Downloads.Impl.statusToString(mStatus)); 514 pw.printPair("mCurrentBytes", mCurrentBytes); 515 pw.printPair("mTotalBytes", mTotalBytes); 516 pw.println(); 517 518 pw.printPair("mNumFailed", mNumFailed); 519 pw.printPair("mRetryAfter", mRetryAfter); 520 pw.printPair("mETag", mETag); 521 pw.printPair("mIsPublicApi", mIsPublicApi); 522 pw.println(); 523 524 pw.printPair("mAllowedNetworkTypes", mAllowedNetworkTypes); 525 pw.printPair("mAllowRoaming", mAllowRoaming); 526 pw.printPair("mAllowMetered", mAllowMetered); 527 pw.println(); 528 529 pw.decreaseIndent(); 530 } 531 532 /** 533 * Returns the amount of time (as measured from the "now" parameter) 534 * at which a download will be active. 535 * 0 = immediately - service should stick around to handle this download. 536 * -1 = never - service can go away without ever waking up. 537 * positive value - service must wake up in the future, as specified in ms from "now" 538 */ nextAction(long now)539 long nextAction(long now) { 540 if (Downloads.Impl.isStatusCompleted(mStatus)) { 541 return -1; 542 } 543 if (mStatus != Downloads.Impl.STATUS_WAITING_TO_RETRY) { 544 return 0; 545 } 546 long when = restartTime(now); 547 if (when <= now) { 548 return 0; 549 } 550 return when - now; 551 } 552 553 /** 554 * Returns whether a file should be scanned 555 */ shouldScanFile()556 boolean shouldScanFile() { 557 return (mMediaScanned == 0) 558 && (mDestination == Downloads.Impl.DESTINATION_EXTERNAL || 559 mDestination == Downloads.Impl.DESTINATION_FILE_URI || 560 mDestination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) 561 && Downloads.Impl.isStatusSuccess(mStatus); 562 } 563 notifyPauseDueToSize(boolean isWifiRequired)564 void notifyPauseDueToSize(boolean isWifiRequired) { 565 Intent intent = new Intent(Intent.ACTION_VIEW); 566 intent.setData(getAllDownloadsUri()); 567 intent.setClassName(SizeLimitActivity.class.getPackage().getName(), 568 SizeLimitActivity.class.getName()); 569 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 570 intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired); 571 mContext.startActivity(intent); 572 } 573 startDownloadThread()574 void startDownloadThread() { 575 DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this, 576 StorageManager.getInstance(mContext)); 577 mSystemFacade.startThread(downloader); 578 } 579 } 580