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