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