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