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