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