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