Home | History | Annotate | Download | only in downloads
      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