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             mInfo.sendIntentIfRequested();
386             if (mInfo.shouldScanFile(mInfoDelta.mStatus)) {
387                 DownloadScanner.requestScanBlocking(mContext, mInfo.mId, mInfoDelta.mFileName,
388                         mInfoDelta.mMimeType);
389             }
390         } else if (mInfoDelta.mStatus == STATUS_WAITING_TO_RETRY
391                 || mInfoDelta.mStatus == STATUS_WAITING_FOR_NETWORK
392                 || mInfoDelta.mStatus == STATUS_QUEUED_FOR_WIFI) {
393             Helpers.scheduleJob(mContext, DownloadInfo.queryDownloadInfo(mContext, mId));
394         }
395 
396         mJobService.jobFinishedInternal(mParams, false);
397     }
398 
requestShutdown()399     public void requestShutdown() {
400         mShutdownRequested = true;
401     }
402 
403     /**
404      * Fully execute a single download request. Setup and send the request,
405      * handle the response, and transfer the data to the destination file.
406      */
executeDownload()407     private void executeDownload() throws StopRequestException {
408         final boolean resuming = mInfoDelta.mCurrentBytes != 0;
409 
410         URL url;
411         try {
412             // TODO: migrate URL sanity checking into client side of API
413             url = new URL(mInfoDelta.mUri);
414         } catch (MalformedURLException e) {
415             throw new StopRequestException(STATUS_BAD_REQUEST, e);
416         }
417 
418         boolean cleartextTrafficPermitted = mSystemFacade.isCleartextTrafficPermitted(mInfo.mUid);
419         SSLContext appContext;
420         try {
421             appContext = mSystemFacade.getSSLContextForPackage(mContext, mInfo.mPackage);
422         } catch (GeneralSecurityException e) {
423             // This should never happen.
424             throw new StopRequestException(STATUS_UNKNOWN_ERROR, "Unable to create SSLContext.");
425         }
426         int redirectionCount = 0;
427         while (redirectionCount++ < Constants.MAX_REDIRECTS) {
428             // Enforce the cleartext traffic opt-out for the UID. This cannot be enforced earlier
429             // because of HTTP redirects which can change the protocol between HTTP and HTTPS.
430             if ((!cleartextTrafficPermitted) && ("http".equalsIgnoreCase(url.getProtocol()))) {
431                 throw new StopRequestException(STATUS_BAD_REQUEST,
432                         "Cleartext traffic not permitted for UID " + mInfo.mUid + ": "
433                         + Uri.parse(url.toString()).toSafeString());
434             }
435 
436             // Open connection and follow any redirects until we have a useful
437             // response with body.
438             HttpURLConnection conn = null;
439             try {
440                 // Check that the caller is allowed to make network connections. If so, make one on
441                 // their behalf to open the url.
442                 checkConnectivity();
443                 conn = (HttpURLConnection) mNetwork.openConnection(url);
444                 conn.setInstanceFollowRedirects(false);
445                 conn.setConnectTimeout(DEFAULT_TIMEOUT);
446                 conn.setReadTimeout(DEFAULT_TIMEOUT);
447                 // If this is going over HTTPS configure the trust to be the same as the calling
448                 // package.
449                 if (conn instanceof HttpsURLConnection) {
450                     ((HttpsURLConnection)conn).setSSLSocketFactory(appContext.getSocketFactory());
451                 }
452 
453                 addRequestHeaders(conn, resuming);
454 
455                 final int responseCode = conn.getResponseCode();
456                 switch (responseCode) {
457                     case HTTP_OK:
458                         if (resuming) {
459                             throw new StopRequestException(
460                                     STATUS_CANNOT_RESUME, "Expected partial, but received OK");
461                         }
462                         parseOkHeaders(conn);
463                         transferData(conn);
464                         return;
465 
466                     case HTTP_PARTIAL:
467                         if (!resuming) {
468                             throw new StopRequestException(
469                                     STATUS_CANNOT_RESUME, "Expected OK, but received partial");
470                         }
471                         transferData(conn);
472                         return;
473 
474                     case HTTP_MOVED_PERM:
475                     case HTTP_MOVED_TEMP:
476                     case HTTP_SEE_OTHER:
477                     case HTTP_TEMP_REDIRECT:
478                         final String location = conn.getHeaderField("Location");
479                         url = new URL(url, location);
480                         if (responseCode == HTTP_MOVED_PERM) {
481                             // Push updated URL back to database
482                             mInfoDelta.mUri = url.toString();
483                         }
484                         continue;
485 
486                     case HTTP_PRECON_FAILED:
487                         throw new StopRequestException(
488                                 STATUS_CANNOT_RESUME, "Precondition failed");
489 
490                     case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
491                         throw new StopRequestException(
492                                 STATUS_CANNOT_RESUME, "Requested range not satisfiable");
493 
494                     case HTTP_UNAVAILABLE:
495                         parseUnavailableHeaders(conn);
496                         throw new StopRequestException(
497                                 HTTP_UNAVAILABLE, conn.getResponseMessage());
498 
499                     case HTTP_INTERNAL_ERROR:
500                         throw new StopRequestException(
501                                 HTTP_INTERNAL_ERROR, conn.getResponseMessage());
502 
503                     default:
504                         StopRequestException.throwUnhandledHttpError(
505                                 responseCode, conn.getResponseMessage());
506                 }
507 
508             } catch (IOException e) {
509                 if (e instanceof ProtocolException
510                         && e.getMessage().startsWith("Unexpected status line")) {
511                     throw new StopRequestException(STATUS_UNHANDLED_HTTP_CODE, e);
512                 } else {
513                     // Trouble with low-level sockets
514                     throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);
515                 }
516 
517             } finally {
518                 if (conn != null) conn.disconnect();
519             }
520         }
521 
522         throw new StopRequestException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");
523     }
524 
525     /**
526      * Transfer data from the given connection to the destination file.
527      */
transferData(HttpURLConnection conn)528     private void transferData(HttpURLConnection conn) throws StopRequestException {
529 
530         // To detect when we're really finished, we either need a length, closed
531         // connection, or chunked encoding.
532         final boolean hasLength = mInfoDelta.mTotalBytes != -1;
533         final boolean isConnectionClose = "close".equalsIgnoreCase(
534                 conn.getHeaderField("Connection"));
535         final boolean isEncodingChunked = "chunked".equalsIgnoreCase(
536                 conn.getHeaderField("Transfer-Encoding"));
537 
538         final boolean finishKnown = hasLength || isConnectionClose || isEncodingChunked;
539         if (!finishKnown) {
540             throw new StopRequestException(
541                     STATUS_CANNOT_RESUME, "can't know size of download, giving up");
542         }
543 
544         DrmManagerClient drmClient = null;
545         ParcelFileDescriptor outPfd = null;
546         FileDescriptor outFd = null;
547         InputStream in = null;
548         OutputStream out = null;
549         try {
550             try {
551                 in = conn.getInputStream();
552             } catch (IOException e) {
553                 throw new StopRequestException(STATUS_HTTP_DATA_ERROR, e);
554             }
555 
556             try {
557                 outPfd = mContext.getContentResolver()
558                         .openFileDescriptor(mInfo.getAllDownloadsUri(), "rw");
559                 outFd = outPfd.getFileDescriptor();
560 
561                 if (DownloadDrmHelper.isDrmConvertNeeded(mInfoDelta.mMimeType)) {
562                     drmClient = new DrmManagerClient(mContext);
563                     out = new DrmOutputStream(drmClient, outPfd, mInfoDelta.mMimeType);
564                 } else {
565                     out = new ParcelFileDescriptor.AutoCloseOutputStream(outPfd);
566                 }
567 
568                 // Pre-flight disk space requirements, when known
569                 if (mInfoDelta.mTotalBytes > 0) {
570                     final long curSize = Os.fstat(outFd).st_size;
571                     final long newBytes = mInfoDelta.mTotalBytes - curSize;
572 
573                     StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes);
574 
575                     try {
576                         // We found enough space, so claim it for ourselves
577                         Os.posix_fallocate(outFd, 0, mInfoDelta.mTotalBytes);
578                     } catch (ErrnoException e) {
579                         if (e.errno == OsConstants.ENOSYS || e.errno == OsConstants.ENOTSUP) {
580                             Log.w(TAG, "fallocate() not supported; falling back to ftruncate()");
581                             Os.ftruncate(outFd, mInfoDelta.mTotalBytes);
582                         } else {
583                             throw e;
584                         }
585                     }
586                 }
587 
588                 // Move into place to begin writing
589                 Os.lseek(outFd, mInfoDelta.mCurrentBytes, OsConstants.SEEK_SET);
590 
591             } catch (ErrnoException e) {
592                 throw new StopRequestException(STATUS_FILE_ERROR, e);
593             } catch (IOException e) {
594                 throw new StopRequestException(STATUS_FILE_ERROR, e);
595             }
596 
597             // Start streaming data, periodically watch for pause/cancel
598             // commands and checking disk space as needed.
599             transferData(in, out, outFd);
600 
601             try {
602                 if (out instanceof DrmOutputStream) {
603                     ((DrmOutputStream) out).finish();
604                 }
605             } catch (IOException e) {
606                 throw new StopRequestException(STATUS_FILE_ERROR, e);
607             }
608 
609         } finally {
610             if (drmClient != null) {
611                 drmClient.close();
612             }
613 
614             IoUtils.closeQuietly(in);
615 
616             try {
617                 if (out != null) out.flush();
618                 if (outFd != null) outFd.sync();
619             } catch (IOException e) {
620             } finally {
621                 IoUtils.closeQuietly(out);
622             }
623         }
624     }
625 
626     /**
627      * Transfer as much data as possible from the HTTP response to the
628      * destination file.
629      */
transferData(InputStream in, OutputStream out, FileDescriptor outFd)630     private void transferData(InputStream in, OutputStream out, FileDescriptor outFd)
631             throws StopRequestException {
632         final byte buffer[] = new byte[Constants.BUFFER_SIZE];
633         while (true) {
634             if (mPolicyDirty) checkConnectivity();
635 
636             if (mShutdownRequested) {
637                 throw new StopRequestException(STATUS_HTTP_DATA_ERROR,
638                         "Local halt requested; job probably timed out");
639             }
640 
641             int len = -1;
642             try {
643                 len = in.read(buffer);
644             } catch (IOException e) {
645                 throw new StopRequestException(
646                         STATUS_HTTP_DATA_ERROR, "Failed reading response: " + e, e);
647             }
648 
649             if (len == -1) {
650                 break;
651             }
652 
653             try {
654                 // When streaming, ensure space before each write
655                 if (mInfoDelta.mTotalBytes == -1) {
656                     final long curSize = Os.fstat(outFd).st_size;
657                     final long newBytes = (mInfoDelta.mCurrentBytes + len) - curSize;
658 
659                     StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes);
660                 }
661 
662                 out.write(buffer, 0, len);
663 
664                 mMadeProgress = true;
665                 mInfoDelta.mCurrentBytes += len;
666 
667                 updateProgress(outFd);
668 
669             } catch (ErrnoException e) {
670                 throw new StopRequestException(STATUS_FILE_ERROR, e);
671             } catch (IOException e) {
672                 throw new StopRequestException(STATUS_FILE_ERROR, e);
673             }
674         }
675 
676         // Finished without error; verify length if known
677         if (mInfoDelta.mTotalBytes != -1 && mInfoDelta.mCurrentBytes != mInfoDelta.mTotalBytes) {
678             throw new StopRequestException(STATUS_HTTP_DATA_ERROR, "Content length mismatch");
679         }
680     }
681 
682     /**
683      * Called just before the thread finishes, regardless of status, to take any
684      * necessary action on the downloaded file.
685      */
finalizeDestination()686     private void finalizeDestination() {
687         if (Downloads.Impl.isStatusError(mInfoDelta.mStatus)) {
688             // When error, free up any disk space
689             try {
690                 final ParcelFileDescriptor target = mContext.getContentResolver()
691                         .openFileDescriptor(mInfo.getAllDownloadsUri(), "rw");
692                 try {
693                     Os.ftruncate(target.getFileDescriptor(), 0);
694                 } catch (ErrnoException ignored) {
695                 } finally {
696                     IoUtils.closeQuietly(target);
697                 }
698             } catch (FileNotFoundException ignored) {
699             }
700 
701             // Delete if local file
702             if (mInfoDelta.mFileName != null) {
703                 new File(mInfoDelta.mFileName).delete();
704                 mInfoDelta.mFileName = null;
705             }
706 
707         } else if (Downloads.Impl.isStatusSuccess(mInfoDelta.mStatus)) {
708             // When success, open access if local file
709             if (mInfoDelta.mFileName != null) {
710                 if (mInfo.mDestination != Downloads.Impl.DESTINATION_FILE_URI) {
711                     try {
712                         // Move into final resting place, if needed
713                         final File before = new File(mInfoDelta.mFileName);
714                         final File beforeDir = Helpers.getRunningDestinationDirectory(
715                                 mContext, mInfo.mDestination);
716                         final File afterDir = Helpers.getSuccessDestinationDirectory(
717                                 mContext, mInfo.mDestination);
718                         if (!beforeDir.equals(afterDir)
719                                 && before.getParentFile().equals(beforeDir)) {
720                             final File after = new File(afterDir, before.getName());
721                             if (before.renameTo(after)) {
722                                 mInfoDelta.mFileName = after.getAbsolutePath();
723                             }
724                         }
725                     } catch (IOException ignored) {
726                     }
727                 }
728             }
729         }
730     }
731 
732     /**
733      * Check if current connectivity is valid for this request.
734      */
checkConnectivity()735     private void checkConnectivity() throws StopRequestException {
736         // checking connectivity will apply current policy
737         mPolicyDirty = false;
738 
739         final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork, mInfo.mUid,
740                 mIgnoreBlocked);
741         if (info == null || !info.isConnected()) {
742             throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is disconnected");
743         }
744         if (info.isRoaming() && !mInfo.isRoamingAllowed()) {
745             throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is roaming");
746         }
747         if (info.isMetered() && !mInfo.isMeteredAllowed(mInfoDelta.mTotalBytes)) {
748             throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is metered");
749         }
750     }
751 
752     /**
753      * Report download progress through the database if necessary.
754      */
updateProgress(FileDescriptor outFd)755     private void updateProgress(FileDescriptor outFd) throws IOException, StopRequestException {
756         final long now = SystemClock.elapsedRealtime();
757         final long currentBytes = mInfoDelta.mCurrentBytes;
758 
759         final long sampleDelta = now - mSpeedSampleStart;
760         if (sampleDelta > 500) {
761             final long sampleSpeed = ((currentBytes - mSpeedSampleBytes) * 1000)
762                     / sampleDelta;
763 
764             if (mSpeed == 0) {
765                 mSpeed = sampleSpeed;
766             } else {
767                 mSpeed = ((mSpeed * 3) + sampleSpeed) / 4;
768             }
769 
770             // Only notify once we have a full sample window
771             if (mSpeedSampleStart != 0) {
772                 mNotifier.notifyDownloadSpeed(mId, mSpeed);
773             }
774 
775             mSpeedSampleStart = now;
776             mSpeedSampleBytes = currentBytes;
777         }
778 
779         final long bytesDelta = currentBytes - mLastUpdateBytes;
780         final long timeDelta = now - mLastUpdateTime;
781         if (bytesDelta > Constants.MIN_PROGRESS_STEP && timeDelta > Constants.MIN_PROGRESS_TIME) {
782             // fsync() to ensure that current progress has been flushed to disk,
783             // so we can always resume based on latest database information.
784             outFd.sync();
785 
786             mInfoDelta.writeToDatabaseOrThrow();
787 
788             mLastUpdateBytes = currentBytes;
789             mLastUpdateTime = now;
790         }
791     }
792 
793     /**
794      * Process response headers from first server response. This derives its
795      * filename, size, and ETag.
796      */
parseOkHeaders(HttpURLConnection conn)797     private void parseOkHeaders(HttpURLConnection conn) throws StopRequestException {
798         if (mInfoDelta.mFileName == null) {
799             final String contentDisposition = conn.getHeaderField("Content-Disposition");
800             final String contentLocation = conn.getHeaderField("Content-Location");
801 
802             try {
803                 mInfoDelta.mFileName = Helpers.generateSaveFile(mContext, mInfoDelta.mUri,
804                         mInfo.mHint, contentDisposition, contentLocation, mInfoDelta.mMimeType,
805                         mInfo.mDestination);
806             } catch (IOException e) {
807                 throw new StopRequestException(
808                         Downloads.Impl.STATUS_FILE_ERROR, "Failed to generate filename: " + e);
809             }
810         }
811 
812         if (mInfoDelta.mMimeType == null) {
813             mInfoDelta.mMimeType = Intent.normalizeMimeType(conn.getContentType());
814         }
815 
816         final String transferEncoding = conn.getHeaderField("Transfer-Encoding");
817         if (transferEncoding == null) {
818             mInfoDelta.mTotalBytes = getHeaderFieldLong(conn, "Content-Length", -1);
819         } else {
820             mInfoDelta.mTotalBytes = -1;
821         }
822 
823         mInfoDelta.mETag = conn.getHeaderField("ETag");
824 
825         mInfoDelta.writeToDatabaseOrThrow();
826 
827         // Check connectivity again now that we know the total size
828         checkConnectivity();
829     }
830 
parseUnavailableHeaders(HttpURLConnection conn)831     private void parseUnavailableHeaders(HttpURLConnection conn) {
832         long retryAfter = conn.getHeaderFieldInt("Retry-After", -1);
833         retryAfter = MathUtils.constrain(retryAfter, Constants.MIN_RETRY_AFTER,
834                 Constants.MAX_RETRY_AFTER);
835         mInfoDelta.mRetryAfter = (int) (retryAfter * SECOND_IN_MILLIS);
836     }
837 
838     /**
839      * Add custom headers for this download to the HTTP request.
840      */
addRequestHeaders(HttpURLConnection conn, boolean resuming)841     private void addRequestHeaders(HttpURLConnection conn, boolean resuming) {
842         for (Pair<String, String> header : mInfo.getHeaders()) {
843             conn.addRequestProperty(header.first, header.second);
844         }
845 
846         // Only splice in user agent when not already defined
847         if (conn.getRequestProperty("User-Agent") == null) {
848             conn.addRequestProperty("User-Agent", mInfo.getUserAgent());
849         }
850 
851         // Defeat transparent gzip compression, since it doesn't allow us to
852         // easily resume partial downloads.
853         conn.setRequestProperty("Accept-Encoding", "identity");
854 
855         // Defeat connection reuse, since otherwise servers may continue
856         // streaming large downloads after cancelled.
857         conn.setRequestProperty("Connection", "close");
858 
859         if (resuming) {
860             if (mInfoDelta.mETag != null) {
861                 conn.addRequestProperty("If-Match", mInfoDelta.mETag);
862             }
863             conn.addRequestProperty("Range", "bytes=" + mInfoDelta.mCurrentBytes + "-");
864         }
865     }
866 
logDebug(String msg)867     private void logDebug(String msg) {
868         Log.d(TAG, "[" + mId + "] " + msg);
869     }
870 
logWarning(String msg)871     private void logWarning(String msg) {
872         Log.w(TAG, "[" + mId + "] " + msg);
873     }
874 
logError(String msg, Throwable t)875     private void logError(String msg, Throwable t) {
876         Log.e(TAG, "[" + mId + "] " + msg, t);
877     }
878 
879     private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
880         @Override
881         public void onUidRulesChanged(int uid, int uidRules) {
882             // caller is NPMS, since we only register with them
883             if (uid == mInfo.mUid) {
884                 mPolicyDirty = true;
885             }
886         }
887 
888         @Override
889         public void onMeteredIfacesChanged(String[] meteredIfaces) {
890             // caller is NPMS, since we only register with them
891             mPolicyDirty = true;
892         }
893 
894         @Override
895         public void onRestrictBackgroundChanged(boolean restrictBackground) {
896             // caller is NPMS, since we only register with them
897             mPolicyDirty = true;
898         }
899 
900         @Override
901         public void onRestrictBackgroundWhitelistChanged(int uid, boolean whitelisted) {
902             // caller is NPMS, since we only register with them
903             if (uid == mInfo.mUid) {
904                 mPolicyDirty = true;
905             }
906         }
907 
908         @Override
909         public void onRestrictBackgroundBlacklistChanged(int uid, boolean blacklisted) {
910             // caller is NPMS, since we only register with them
911             if (uid == mInfo.mUid) {
912                 mPolicyDirty = true;
913             }
914         }
915     };
916 
getHeaderFieldLong(URLConnection conn, String field, long defaultValue)917     private static long getHeaderFieldLong(URLConnection conn, String field, long defaultValue) {
918         try {
919             return Long.parseLong(conn.getHeaderField(field));
920         } catch (NumberFormatException e) {
921             return defaultValue;
922         }
923     }
924 
925     /**
926      * Return if given status is eligible to be treated as
927      * {@link android.provider.Downloads.Impl#STATUS_WAITING_TO_RETRY}.
928      */
isStatusRetryable(int status)929     public static boolean isStatusRetryable(int status) {
930         switch (status) {
931             case STATUS_HTTP_DATA_ERROR:
932             case HTTP_UNAVAILABLE:
933             case HTTP_INTERNAL_ERROR:
934             case STATUS_FILE_ERROR:
935                 return true;
936             default:
937                 return false;
938         }
939     }
940 }
941