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