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 static android.provider.Downloads.Impl.STATUS_BAD_REQUEST; 20 import static android.provider.Downloads.Impl.STATUS_CANCELED; 21 import static android.provider.Downloads.Impl.STATUS_CANNOT_RESUME; 22 import static android.provider.Downloads.Impl.STATUS_FILE_ERROR; 23 import static android.provider.Downloads.Impl.STATUS_HTTP_DATA_ERROR; 24 import static android.provider.Downloads.Impl.STATUS_SUCCESS; 25 import static android.provider.Downloads.Impl.STATUS_TOO_MANY_REDIRECTS; 26 import static android.provider.Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE; 27 import static android.provider.Downloads.Impl.STATUS_UNKNOWN_ERROR; 28 import static android.provider.Downloads.Impl.STATUS_WAITING_FOR_NETWORK; 29 import static android.provider.Downloads.Impl.STATUS_WAITING_TO_RETRY; 30 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 31 import static com.android.providers.downloads.Constants.TAG; 32 import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; 33 import static java.net.HttpURLConnection.HTTP_MOVED_PERM; 34 import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; 35 import static java.net.HttpURLConnection.HTTP_OK; 36 import static java.net.HttpURLConnection.HTTP_PARTIAL; 37 import static java.net.HttpURLConnection.HTTP_PRECON_FAILED; 38 import static java.net.HttpURLConnection.HTTP_SEE_OTHER; 39 import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; 40 41 import android.content.ContentValues; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.drm.DrmManagerClient; 45 import android.drm.DrmOutputStream; 46 import android.net.ConnectivityManager; 47 import android.net.INetworkPolicyListener; 48 import android.net.NetworkInfo; 49 import android.net.NetworkPolicyManager; 50 import android.net.TrafficStats; 51 import android.os.ParcelFileDescriptor; 52 import android.os.PowerManager; 53 import android.os.Process; 54 import android.os.SystemClock; 55 import android.os.WorkSource; 56 import android.provider.Downloads; 57 import android.system.ErrnoException; 58 import android.system.Os; 59 import android.system.OsConstants; 60 import android.util.Log; 61 import android.util.Pair; 62 63 import com.android.providers.downloads.DownloadInfo.NetworkState; 64 65 import libcore.io.IoUtils; 66 67 import java.io.File; 68 import java.io.FileDescriptor; 69 import java.io.FileNotFoundException; 70 import java.io.IOException; 71 import java.io.InputStream; 72 import java.io.OutputStream; 73 import java.net.HttpURLConnection; 74 import java.net.MalformedURLException; 75 import java.net.ProtocolException; 76 import java.net.URL; 77 import java.net.URLConnection; 78 79 /** 80 * Task which executes a given {@link DownloadInfo}: making network requests, 81 * persisting data to disk, and updating {@link DownloadProvider}. 82 * <p> 83 * To know if a download is successful, we need to know either the final content 84 * length to expect, or the transfer to be chunked. To resume an interrupted 85 * download, we need an ETag. 86 * <p> 87 * Failed network requests are retried several times before giving up. Local 88 * disk errors fail immediately and are not retried. 89 */ 90 public class DownloadThread implements Runnable { 91 92 // TODO: bind each download to a specific network interface to avoid state 93 // checking races once we have ConnectivityManager API 94 95 // TODO: add support for saving to content:// 96 97 private static final int HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; 98 private static final int HTTP_TEMP_REDIRECT = 307; 99 100 private static final int DEFAULT_TIMEOUT = (int) (20 * SECOND_IN_MILLIS); 101 102 private final Context mContext; 103 private final SystemFacade mSystemFacade; 104 private final DownloadNotifier mNotifier; 105 106 private final long mId; 107 108 /** 109 * Info object that should be treated as read-only. Any potentially mutated 110 * fields are tracked in {@link #mInfoDelta}. If a field exists in 111 * {@link #mInfoDelta}, it must not be read from {@link #mInfo}. 112 */ 113 private final DownloadInfo mInfo; 114 private final DownloadInfoDelta mInfoDelta; 115 116 private volatile boolean mPolicyDirty; 117 118 /** 119 * Local changes to {@link DownloadInfo}. These are kept local to avoid 120 * racing with the thread that updates based on change notifications. 121 */ 122 private class DownloadInfoDelta { 123 public String mUri; 124 public String mFileName; 125 public String mMimeType; 126 public int mStatus; 127 public int mNumFailed; 128 public int mRetryAfter; 129 public long mTotalBytes; 130 public long mCurrentBytes; 131 public String mETag; 132 133 public String mErrorMsg; 134 135 public DownloadInfoDelta(DownloadInfo info) { 136 mUri = info.mUri; 137 mFileName = info.mFileName; 138 mMimeType = info.mMimeType; 139 mStatus = info.mStatus; 140 mNumFailed = info.mNumFailed; 141 mRetryAfter = info.mRetryAfter; 142 mTotalBytes = info.mTotalBytes; 143 mCurrentBytes = info.mCurrentBytes; 144 mETag = info.mETag; 145 } 146 147 private ContentValues buildContentValues() { 148 final ContentValues values = new ContentValues(); 149 150 values.put(Downloads.Impl.COLUMN_URI, mUri); 151 values.put(Downloads.Impl._DATA, mFileName); 152 values.put(Downloads.Impl.COLUMN_MIME_TYPE, mMimeType); 153 values.put(Downloads.Impl.COLUMN_STATUS, mStatus); 154 values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, mNumFailed); 155 values.put(Constants.RETRY_AFTER_X_REDIRECT_COUNT, mRetryAfter); 156 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, mTotalBytes); 157 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, mCurrentBytes); 158 values.put(Constants.ETAG, mETag); 159 160 values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, mSystemFacade.currentTimeMillis()); 161 values.put(Downloads.Impl.COLUMN_ERROR_MSG, mErrorMsg); 162 163 return values; 164 } 165 166 /** 167 * Blindly push update of current delta values to provider. 168 */ 169 public void writeToDatabase() { 170 mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), buildContentValues(), 171 null, null); 172 } 173 174 /** 175 * Push update of current delta values to provider, asserting strongly 176 * that we haven't been paused or deleted. 177 */ 178 public void writeToDatabaseOrThrow() throws StopRequestException { 179 if (mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), 180 buildContentValues(), Downloads.Impl.COLUMN_DELETED + " == '0'", null) == 0) { 181 throw new StopRequestException(STATUS_CANCELED, "Download deleted or missing!"); 182 } 183 } 184 } 185 186 /** 187 * Flag indicating if we've made forward progress transferring file data 188 * from a remote server. 189 */ 190 private boolean mMadeProgress = false; 191 192 /** 193 * Details from the last time we pushed a database update. 194 */ 195 private long mLastUpdateBytes = 0; 196 private long mLastUpdateTime = 0; 197 198 private int mNetworkType = ConnectivityManager.TYPE_NONE; 199 200 /** Historical bytes/second speed of this download. */ 201 private long mSpeed; 202 /** Time when current sample started. */ 203 private long mSpeedSampleStart; 204 /** Bytes transferred since current sample started. */ 205 private long mSpeedSampleBytes; 206 207 public DownloadThread(Context context, SystemFacade systemFacade, DownloadNotifier notifier, 208 DownloadInfo info) { 209 mContext = context; 210 mSystemFacade = systemFacade; 211 mNotifier = notifier; 212 213 mId = info.mId; 214 mInfo = info; 215 mInfoDelta = new DownloadInfoDelta(info); 216 } 217 218 @Override 219 public void run() { 220 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 221 222 // Skip when download already marked as finished; this download was 223 // probably started again while racing with UpdateThread. 224 if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mId) 225 == Downloads.Impl.STATUS_SUCCESS) { 226 logDebug("Already finished; skipping"); 227 return; 228 } 229 230 final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext); 231 PowerManager.WakeLock wakeLock = null; 232 final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 233 234 try { 235 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); 236 wakeLock.setWorkSource(new WorkSource(mInfo.mUid)); 237 wakeLock.acquire(); 238 239 // while performing download, register for rules updates 240 netPolicy.registerListener(mPolicyListener); 241 242 logDebug("Starting"); 243 244 // Remember which network this download started on; used to 245 // determine if errors were due to network changes. 246 final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid); 247 if (info != null) { 248 mNetworkType = info.getType(); 249 } 250 251 // Network traffic on this thread should be counted against the 252 // requesting UID, and is tagged with well-known value. 253 TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD); 254 TrafficStats.setThreadStatsUid(mInfo.mUid); 255 256 executeDownload(); 257 258 mInfoDelta.mStatus = STATUS_SUCCESS; 259 TrafficStats.incrementOperationCount(1); 260 261 // If we just finished a chunked file, record total size 262 if (mInfoDelta.mTotalBytes == -1) { 263 mInfoDelta.mTotalBytes = mInfoDelta.mCurrentBytes; 264 } 265 266 } catch (StopRequestException e) { 267 mInfoDelta.mStatus = e.getFinalStatus(); 268 mInfoDelta.mErrorMsg = e.getMessage(); 269 270 logWarning("Stop requested with status " 271 + Downloads.Impl.statusToString(mInfoDelta.mStatus) + ": " 272 + mInfoDelta.mErrorMsg); 273 274 // Nobody below our level should request retries, since we handle 275 // failure counts at this level. 276 if (mInfoDelta.mStatus == STATUS_WAITING_TO_RETRY) { 277 throw new IllegalStateException("Execution should always throw final error codes"); 278 } 279 280 // Some errors should be retryable, unless we fail too many times. 281 if (isStatusRetryable(mInfoDelta.mStatus)) { 282 if (mMadeProgress) { 283 mInfoDelta.mNumFailed = 1; 284 } else { 285 mInfoDelta.mNumFailed += 1; 286 } 287 288 if (mInfoDelta.mNumFailed < Constants.MAX_RETRIES) { 289 final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid); 290 if (info != null && info.getType() == mNetworkType && info.isConnected()) { 291 // Underlying network is still intact, use normal backoff 292 mInfoDelta.mStatus = STATUS_WAITING_TO_RETRY; 293 } else { 294 // Network changed, retry on any next available 295 mInfoDelta.mStatus = STATUS_WAITING_FOR_NETWORK; 296 } 297 298 if ((mInfoDelta.mETag == null && mMadeProgress) 299 || DownloadDrmHelper.isDrmConvertNeeded(mInfoDelta.mMimeType)) { 300 // However, if we wrote data and have no ETag to verify 301 // contents against later, we can't actually resume. 302 mInfoDelta.mStatus = STATUS_CANNOT_RESUME; 303 } 304 } 305 } 306 307 } catch (Throwable t) { 308 mInfoDelta.mStatus = STATUS_UNKNOWN_ERROR; 309 mInfoDelta.mErrorMsg = t.toString(); 310 311 logError("Failed: " + mInfoDelta.mErrorMsg, t); 312 313 } finally { 314 logDebug("Finished with status " + Downloads.Impl.statusToString(mInfoDelta.mStatus)); 315 316 mNotifier.notifyDownloadSpeed(mId, 0); 317 318 finalizeDestination(); 319 320 mInfoDelta.writeToDatabase(); 321 322 if (Downloads.Impl.isStatusCompleted(mInfoDelta.mStatus)) { 323 mInfo.sendIntentIfRequested(); 324 } 325 326 TrafficStats.clearThreadStatsTag(); 327 TrafficStats.clearThreadStatsUid(); 328 329 netPolicy.unregisterListener(mPolicyListener); 330 331 if (wakeLock != null) { 332 wakeLock.release(); 333 wakeLock = null; 334 } 335 } 336 } 337 338 /** 339 * Fully execute a single download request. Setup and send the request, 340 * handle the response, and transfer the data to the destination file. 341 */ 342 private void executeDownload() throws StopRequestException { 343 final boolean resuming = mInfoDelta.mCurrentBytes != 0; 344 345 URL url; 346 try { 347 // TODO: migrate URL sanity checking into client side of API 348 url = new URL(mInfoDelta.mUri); 349 } catch (MalformedURLException e) { 350 throw new StopRequestException(STATUS_BAD_REQUEST, e); 351 } 352 353 int redirectionCount = 0; 354 while (redirectionCount++ < Constants.MAX_REDIRECTS) { 355 // Open connection and follow any redirects until we have a useful 356 // response with body. 357 HttpURLConnection conn = null; 358 try { 359 checkConnectivity(); 360 conn = (HttpURLConnection) url.openConnection(); 361 conn.setInstanceFollowRedirects(false); 362 conn.setConnectTimeout(DEFAULT_TIMEOUT); 363 conn.setReadTimeout(DEFAULT_TIMEOUT); 364 365 addRequestHeaders(conn, resuming); 366 367 final int responseCode = conn.getResponseCode(); 368 switch (responseCode) { 369 case HTTP_OK: 370 if (resuming) { 371 throw new StopRequestException( 372 STATUS_CANNOT_RESUME, "Expected partial, but received OK"); 373 } 374 parseOkHeaders(conn); 375 transferData(conn); 376 return; 377 378 case HTTP_PARTIAL: 379 if (!resuming) { 380 throw new StopRequestException( 381 STATUS_CANNOT_RESUME, "Expected OK, but received partial"); 382 } 383 transferData(conn); 384 return; 385 386 case HTTP_MOVED_PERM: 387 case HTTP_MOVED_TEMP: 388 case HTTP_SEE_OTHER: 389 case HTTP_TEMP_REDIRECT: 390 final String location = conn.getHeaderField("Location"); 391 url = new URL(url, location); 392 if (responseCode == HTTP_MOVED_PERM) { 393 // Push updated URL back to database 394 mInfoDelta.mUri = url.toString(); 395 } 396 continue; 397 398 case HTTP_PRECON_FAILED: 399 throw new StopRequestException( 400 STATUS_CANNOT_RESUME, "Precondition failed"); 401 402 case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: 403 throw new StopRequestException( 404 STATUS_CANNOT_RESUME, "Requested range not satisfiable"); 405 406 case HTTP_UNAVAILABLE: 407 parseUnavailableHeaders(conn); 408 throw new StopRequestException( 409 HTTP_UNAVAILABLE, conn.getResponseMessage()); 410 411 case HTTP_INTERNAL_ERROR: 412 throw new StopRequestException( 413 HTTP_INTERNAL_ERROR, conn.getResponseMessage()); 414 415 default: 416 StopRequestException.throwUnhandledHttpError( 417 responseCode, conn.getResponseMessage()); 418 } 419 420 } catch (IOException e) { 421 if (e instanceof ProtocolException 422 && e.getMessage().startsWith("Unexpected status line")) { 423 throw new StopRequestException(STATUS_UNHANDLED_HTTP_CODE, e); 424 } else { 425 // Trouble with low-level sockets 426 throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e); 427 } 428 429 } finally { 430 if (conn != null) conn.disconnect(); 431 } 432 } 433 434 throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects"); 435 } 436 437 /** 438 * Transfer data from the given connection to the destination file. 439 */ 440 private void transferData(HttpURLConnection conn) throws StopRequestException { 441 442 // To detect when we're really finished, we either need a length, closed 443 // connection, or chunked encoding. 444 final boolean hasLength = mInfoDelta.mTotalBytes != -1; 445 final boolean isConnectionClose = "close".equalsIgnoreCase( 446 conn.getHeaderField("Connection")); 447 final boolean isEncodingChunked = "chunked".equalsIgnoreCase( 448 conn.getHeaderField("Transfer-Encoding")); 449 450 final boolean finishKnown = hasLength || isConnectionClose || isEncodingChunked; 451 if (!finishKnown) { 452 throw new StopRequestException( 453 STATUS_CANNOT_RESUME, "can't know size of download, giving up"); 454 } 455 456 DrmManagerClient drmClient = null; 457 ParcelFileDescriptor outPfd = null; 458 FileDescriptor outFd = null; 459 InputStream in = null; 460 OutputStream out = null; 461 try { 462 try { 463 in = conn.getInputStream(); 464 } catch (IOException e) { 465 throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e); 466 } 467 468 try { 469 outPfd = mContext.getContentResolver() 470 .openFileDescriptor(mInfo.getAllDownloadsUri(), "rw"); 471 outFd = outPfd.getFileDescriptor(); 472 473 if (DownloadDrmHelper.isDrmConvertNeeded(mInfoDelta.mMimeType)) { 474 drmClient = new DrmManagerClient(mContext); 475 out = new DrmOutputStream(drmClient, outPfd, mInfoDelta.mMimeType); 476 } else { 477 out = new ParcelFileDescriptor.AutoCloseOutputStream(outPfd); 478 } 479 480 // Pre-flight disk space requirements, when known 481 if (mInfoDelta.mTotalBytes > 0) { 482 final long curSize = Os.fstat(outFd).st_size; 483 final long newBytes = mInfoDelta.mTotalBytes - curSize; 484 485 StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes); 486 487 try { 488 // We found enough space, so claim it for ourselves 489 Os.posix_fallocate(outFd, 0, mInfoDelta.mTotalBytes); 490 } catch (ErrnoException e) { 491 if (e.errno == OsConstants.ENOSYS || e.errno == OsConstants.ENOTSUP) { 492 Log.w(TAG, "fallocate() not supported; falling back to ftruncate()"); 493 Os.ftruncate(outFd, mInfoDelta.mTotalBytes); 494 } else { 495 throw e; 496 } 497 } 498 } 499 500 // Move into place to begin writing 501 Os.lseek(outFd, mInfoDelta.mCurrentBytes, OsConstants.SEEK_SET); 502 503 } catch (ErrnoException e) { 504 throw new StopRequestException(STATUS_FILE_ERROR, e); 505 } catch (IOException e) { 506 throw new StopRequestException(STATUS_FILE_ERROR, e); 507 } 508 509 // Start streaming data, periodically watch for pause/cancel 510 // commands and checking disk space as needed. 511 transferData(in, out, outFd); 512 513 try { 514 if (out instanceof DrmOutputStream) { 515 ((DrmOutputStream) out).finish(); 516 } 517 } catch (IOException e) { 518 throw new StopRequestException(STATUS_FILE_ERROR, e); 519 } 520 521 } finally { 522 if (drmClient != null) { 523 drmClient.release(); 524 } 525 526 IoUtils.closeQuietly(in); 527 528 try { 529 if (out != null) out.flush(); 530 if (outFd != null) outFd.sync(); 531 } catch (IOException e) { 532 } finally { 533 IoUtils.closeQuietly(out); 534 } 535 } 536 } 537 538 /** 539 * Transfer as much data as possible from the HTTP response to the 540 * destination file. 541 */ 542 private void transferData(InputStream in, OutputStream out, FileDescriptor outFd) 543 throws StopRequestException { 544 final byte buffer[] = new byte[Constants.BUFFER_SIZE]; 545 while (true) { 546 checkPausedOrCanceled(); 547 548 int len = -1; 549 try { 550 len = in.read(buffer); 551 } catch (IOException e) { 552 throw new StopRequestException( 553 STATUS_HTTP_DATA_ERROR, "Failed reading response: " + e, e); 554 } 555 556 if (len == -1) { 557 break; 558 } 559 560 try { 561 // When streaming, ensure space before each write 562 if (mInfoDelta.mTotalBytes == -1) { 563 final long curSize = Os.fstat(outFd).st_size; 564 final long newBytes = (mInfoDelta.mCurrentBytes + len) - curSize; 565 566 StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes); 567 } 568 569 out.write(buffer, 0, len); 570 571 mMadeProgress = true; 572 mInfoDelta.mCurrentBytes += len; 573 574 updateProgress(outFd); 575 576 } catch (ErrnoException e) { 577 throw new StopRequestException(STATUS_FILE_ERROR, e); 578 } catch (IOException e) { 579 throw new StopRequestException(STATUS_FILE_ERROR, e); 580 } 581 } 582 583 // Finished without error; verify length if known 584 if (mInfoDelta.mTotalBytes != -1 && mInfoDelta.mCurrentBytes != mInfoDelta.mTotalBytes) { 585 throw new StopRequestException(STATUS_HTTP_DATA_ERROR, "Content length mismatch"); 586 } 587 } 588 589 /** 590 * Called just before the thread finishes, regardless of status, to take any 591 * necessary action on the downloaded file. 592 */ 593 private void finalizeDestination() { 594 if (Downloads.Impl.isStatusError(mInfoDelta.mStatus)) { 595 // When error, free up any disk space 596 try { 597 final ParcelFileDescriptor target = mContext.getContentResolver() 598 .openFileDescriptor(mInfo.getAllDownloadsUri(), "rw"); 599 try { 600 Os.ftruncate(target.getFileDescriptor(), 0); 601 } catch (ErrnoException ignored) { 602 } finally { 603 IoUtils.closeQuietly(target); 604 } 605 } catch (FileNotFoundException ignored) { 606 } 607 608 // Delete if local file 609 if (mInfoDelta.mFileName != null) { 610 new File(mInfoDelta.mFileName).delete(); 611 mInfoDelta.mFileName = null; 612 } 613 614 } else if (Downloads.Impl.isStatusSuccess(mInfoDelta.mStatus)) { 615 // When success, open access if local file 616 if (mInfoDelta.mFileName != null) { 617 try { 618 // TODO: remove this once PackageInstaller works with content:// 619 Os.chmod(mInfoDelta.mFileName, 0644); 620 } catch (ErrnoException ignored) { 621 } 622 623 if (mInfo.mDestination != Downloads.Impl.DESTINATION_FILE_URI) { 624 try { 625 // Move into final resting place, if needed 626 final File before = new File(mInfoDelta.mFileName); 627 final File beforeDir = Helpers.getRunningDestinationDirectory( 628 mContext, mInfo.mDestination); 629 final File afterDir = Helpers.getSuccessDestinationDirectory( 630 mContext, mInfo.mDestination); 631 if (!beforeDir.equals(afterDir) 632 && before.getParentFile().equals(beforeDir)) { 633 final File after = new File(afterDir, before.getName()); 634 if (before.renameTo(after)) { 635 mInfoDelta.mFileName = after.getAbsolutePath(); 636 } 637 } 638 } catch (IOException ignored) { 639 } 640 } 641 } 642 } 643 } 644 645 /** 646 * Check if current connectivity is valid for this request. 647 */ 648 private void checkConnectivity() throws StopRequestException { 649 // checking connectivity will apply current policy 650 mPolicyDirty = false; 651 652 final NetworkState networkUsable = mInfo.checkCanUseNetwork(mInfoDelta.mTotalBytes); 653 if (networkUsable != NetworkState.OK) { 654 int status = Downloads.Impl.STATUS_WAITING_FOR_NETWORK; 655 if (networkUsable == NetworkState.UNUSABLE_DUE_TO_SIZE) { 656 status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI; 657 mInfo.notifyPauseDueToSize(true); 658 } else if (networkUsable == NetworkState.RECOMMENDED_UNUSABLE_DUE_TO_SIZE) { 659 status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI; 660 mInfo.notifyPauseDueToSize(false); 661 } 662 throw new StopRequestException(status, networkUsable.name()); 663 } 664 } 665 666 /** 667 * Check if the download has been paused or canceled, stopping the request 668 * appropriately if it has been. 669 */ 670 private void checkPausedOrCanceled() throws StopRequestException { 671 synchronized (mInfo) { 672 if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) { 673 throw new StopRequestException( 674 Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner"); 675 } 676 if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED || mInfo.mDeleted) { 677 throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled"); 678 } 679 } 680 681 // if policy has been changed, trigger connectivity check 682 if (mPolicyDirty) { 683 checkConnectivity(); 684 } 685 } 686 687 /** 688 * Report download progress through the database if necessary. 689 */ 690 private void updateProgress(FileDescriptor outFd) throws IOException, StopRequestException { 691 final long now = SystemClock.elapsedRealtime(); 692 final long currentBytes = mInfoDelta.mCurrentBytes; 693 694 final long sampleDelta = now - mSpeedSampleStart; 695 if (sampleDelta > 500) { 696 final long sampleSpeed = ((currentBytes - mSpeedSampleBytes) * 1000) 697 / sampleDelta; 698 699 if (mSpeed == 0) { 700 mSpeed = sampleSpeed; 701 } else { 702 mSpeed = ((mSpeed * 3) + sampleSpeed) / 4; 703 } 704 705 // Only notify once we have a full sample window 706 if (mSpeedSampleStart != 0) { 707 mNotifier.notifyDownloadSpeed(mId, mSpeed); 708 } 709 710 mSpeedSampleStart = now; 711 mSpeedSampleBytes = currentBytes; 712 } 713 714 final long bytesDelta = currentBytes - mLastUpdateBytes; 715 final long timeDelta = now - mLastUpdateTime; 716 if (bytesDelta > Constants.MIN_PROGRESS_STEP && timeDelta > Constants.MIN_PROGRESS_TIME) { 717 // fsync() to ensure that current progress has been flushed to disk, 718 // so we can always resume based on latest database information. 719 outFd.sync(); 720 721 mInfoDelta.writeToDatabaseOrThrow(); 722 723 mLastUpdateBytes = currentBytes; 724 mLastUpdateTime = now; 725 } 726 } 727 728 /** 729 * Process response headers from first server response. This derives its 730 * filename, size, and ETag. 731 */ 732 private void parseOkHeaders(HttpURLConnection conn) throws StopRequestException { 733 if (mInfoDelta.mFileName == null) { 734 final String contentDisposition = conn.getHeaderField("Content-Disposition"); 735 final String contentLocation = conn.getHeaderField("Content-Location"); 736 737 try { 738 mInfoDelta.mFileName = Helpers.generateSaveFile(mContext, mInfoDelta.mUri, 739 mInfo.mHint, contentDisposition, contentLocation, mInfoDelta.mMimeType, 740 mInfo.mDestination); 741 } catch (IOException e) { 742 throw new StopRequestException( 743 Downloads.Impl.STATUS_FILE_ERROR, "Failed to generate filename: " + e); 744 } 745 } 746 747 if (mInfoDelta.mMimeType == null) { 748 mInfoDelta.mMimeType = Intent.normalizeMimeType(conn.getContentType()); 749 } 750 751 final String transferEncoding = conn.getHeaderField("Transfer-Encoding"); 752 if (transferEncoding == null) { 753 mInfoDelta.mTotalBytes = getHeaderFieldLong(conn, "Content-Length", -1); 754 } else { 755 mInfoDelta.mTotalBytes = -1; 756 } 757 758 mInfoDelta.mETag = conn.getHeaderField("ETag"); 759 760 mInfoDelta.writeToDatabaseOrThrow(); 761 762 // Check connectivity again now that we know the total size 763 checkConnectivity(); 764 } 765 766 private void parseUnavailableHeaders(HttpURLConnection conn) { 767 long retryAfter = conn.getHeaderFieldInt("Retry-After", -1); 768 if (retryAfter < 0) { 769 retryAfter = 0; 770 } else { 771 if (retryAfter < Constants.MIN_RETRY_AFTER) { 772 retryAfter = Constants.MIN_RETRY_AFTER; 773 } else if (retryAfter > Constants.MAX_RETRY_AFTER) { 774 retryAfter = Constants.MAX_RETRY_AFTER; 775 } 776 retryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1); 777 } 778 779 mInfoDelta.mRetryAfter = (int) (retryAfter * SECOND_IN_MILLIS); 780 } 781 782 /** 783 * Add custom headers for this download to the HTTP request. 784 */ 785 private void addRequestHeaders(HttpURLConnection conn, boolean resuming) { 786 for (Pair<String, String> header : mInfo.getHeaders()) { 787 conn.addRequestProperty(header.first, header.second); 788 } 789 790 // Only splice in user agent when not already defined 791 if (conn.getRequestProperty("User-Agent") == null) { 792 conn.addRequestProperty("User-Agent", mInfo.getUserAgent()); 793 } 794 795 // Defeat transparent gzip compression, since it doesn't allow us to 796 // easily resume partial downloads. 797 conn.setRequestProperty("Accept-Encoding", "identity"); 798 799 // Defeat connection reuse, since otherwise servers may continue 800 // streaming large downloads after cancelled. 801 conn.setRequestProperty("Connection", "close"); 802 803 if (resuming) { 804 if (mInfoDelta.mETag != null) { 805 conn.addRequestProperty("If-Match", mInfoDelta.mETag); 806 } 807 conn.addRequestProperty("Range", "bytes=" + mInfoDelta.mCurrentBytes + "-"); 808 } 809 } 810 811 private void logDebug(String msg) { 812 Log.d(TAG, "[" + mId + "] " + msg); 813 } 814 815 private void logWarning(String msg) { 816 Log.w(TAG, "[" + mId + "] " + msg); 817 } 818 819 private void logError(String msg, Throwable t) { 820 Log.e(TAG, "[" + mId + "] " + msg, t); 821 } 822 823 private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { 824 @Override 825 public void onUidRulesChanged(int uid, int uidRules) { 826 // caller is NPMS, since we only register with them 827 if (uid == mInfo.mUid) { 828 mPolicyDirty = true; 829 } 830 } 831 832 @Override 833 public void onMeteredIfacesChanged(String[] meteredIfaces) { 834 // caller is NPMS, since we only register with them 835 mPolicyDirty = true; 836 } 837 838 @Override 839 public void onRestrictBackgroundChanged(boolean restrictBackground) { 840 // caller is NPMS, since we only register with them 841 mPolicyDirty = true; 842 } 843 }; 844 845 private static long getHeaderFieldLong(URLConnection conn, String field, long defaultValue) { 846 try { 847 return Long.parseLong(conn.getHeaderField(field)); 848 } catch (NumberFormatException e) { 849 return defaultValue; 850 } 851 } 852 853 /** 854 * Return if given status is eligible to be treated as 855 * {@link android.provider.Downloads.Impl#STATUS_WAITING_TO_RETRY}. 856 */ 857 public static boolean isStatusRetryable(int status) { 858 switch (status) { 859 case STATUS_HTTP_DATA_ERROR: 860 case HTTP_UNAVAILABLE: 861 case HTTP_INTERNAL_ERROR: 862 case STATUS_FILE_ERROR: 863 return true; 864 default: 865 return false; 866 } 867 } 868 } 869