]> nv-tegra.nvidia Code Review - android/platform/packages/providers/DownloadProvider.git/blob - src/com/android/providers/downloads/DownloadThread.java
Supply a Context to AndroidHttpClient.newInstance(), to enable
[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 org.apache.http.Header;
20 import org.apache.http.HttpResponse;
21 import org.apache.http.client.methods.HttpGet;
22 import org.apache.http.client.HttpClient;
23 import org.apache.http.entity.StringEntity;
24
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.drm.mobile1.DrmRawContent;
30 import android.net.Uri;
31 import android.os.FileUtils;
32 import android.os.PowerManager;
33 import android.os.Process;
34 import android.provider.Downloads;
35 import android.provider.DrmStore;
36 import android.util.Config;
37 import android.util.Log;
38
39 import com.android.common.AndroidHttpClient;
40
41 import java.io.File;
42 import java.io.FileDescriptor;
43 import java.io.FileNotFoundException;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.SyncFailedException;
48 import java.net.URI;
49 import java.net.URISyntaxException;
50 import java.util.Locale;
51
52 /**
53  * Runs an actual download
54  */
55 public class DownloadThread extends Thread {
56
57     private Context mContext;
58     private DownloadInfo mInfo;
59
60     public DownloadThread(Context context, DownloadInfo info) {
61         mContext = context;
62         mInfo = info;
63     }
64
65     /**
66      * Returns the user agent provided by the initiating app, or use the default one
67      */
68     private String userAgent() {
69         String userAgent = mInfo.mUserAgent;
70         if (userAgent != null) {
71         }
72         if (userAgent == null) {
73             userAgent = Constants.DEFAULT_USER_AGENT;
74         }
75         return userAgent;
76     }
77
78     /**
79      * Executes the download in a separate thread
80      */
81     public void run() {
82         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
83
84         int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
85         boolean countRetry = false;
86         int retryAfter = 0;
87         int redirectCount = mInfo.mRedirectCount;
88         String newUri = null;
89         boolean gotData = false;
90         String filename = null;
91         String mimeType = sanitizeMimeType(mInfo.mMimeType);
92         FileOutputStream stream = null;
93         AndroidHttpClient client = null;
94         PowerManager.WakeLock wakeLock = null;
95         Uri contentUri = Uri.parse(Downloads.Impl.CONTENT_URI + "/" + mInfo.mId);
96
97         try {
98             boolean continuingDownload = false;
99             String headerAcceptRanges = null;
100             String headerContentDisposition = null;
101             String headerContentLength = null;
102             String headerContentLocation = null;
103             String headerETag = null;
104             String headerTransferEncoding = null;
105
106             byte data[] = new byte[Constants.BUFFER_SIZE];
107
108             int bytesSoFar = 0;
109
110             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
111             wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
112             wakeLock.acquire();
113
114             filename = mInfo.mFileName;
115             if (filename != null) {
116                 if (!Helpers.isFilenameValid(filename)) {
117                     finalStatus = Downloads.Impl.STATUS_FILE_ERROR;
118                     notifyDownloadCompleted(
119                             finalStatus, false, 0, 0, false, filename, null, mInfo.mMimeType);
120                     return;
121                 }
122                 // We're resuming a download that got interrupted
123                 File f = new File(filename);
124                 if (f.exists()) {
125                     long fileLength = f.length();
126                     if (fileLength == 0) {
127                         // The download hadn't actually started, we can restart from scratch
128                         f.delete();
129                         filename = null;
130                     } else if (mInfo.mETag == null && !mInfo.mNoIntegrity) {
131                         // Tough luck, that's not a resumable download
132                         if (Config.LOGD) {
133                             Log.d(Constants.TAG,
134                                     "can't resume interrupted non-resumable download"); 
135                         }
136                         f.delete();
137                         finalStatus = Downloads.Impl.STATUS_PRECONDITION_FAILED;
138                         notifyDownloadCompleted(
139                                 finalStatus, false, 0, 0, false, filename, null, mInfo.mMimeType);
140                         return;
141                     } else {
142                         // All right, we'll be able to resume this download
143                         stream = new FileOutputStream(filename, true);
144                         bytesSoFar = (int) fileLength;
145                         if (mInfo.mTotalBytes != -1) {
146                             headerContentLength = Integer.toString(mInfo.mTotalBytes);
147                         }
148                         headerETag = mInfo.mETag;
149                         continuingDownload = true;
150                     }
151                 }
152             }
153
154             int bytesNotified = bytesSoFar;
155             // starting with MIN_VALUE means that the first write will commit
156             //     progress to the database
157             long timeLastNotification = 0;
158
159             client = AndroidHttpClient.newInstance(userAgent(), mContext);
160
161             if (stream != null && mInfo.mDestination == Downloads.Impl.DESTINATION_EXTERNAL
162                         && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
163                         .equalsIgnoreCase(mimeType)) {
164                 try {
165                     stream.close();
166                     stream = null;
167                 } catch (IOException ex) {
168                     if (Constants.LOGV) {
169                         Log.v(Constants.TAG, "exception when closing the file before download : " +
170                                 ex);
171                     }
172                     // nothing can really be done if the file can't be closed
173                 }
174             }
175
176             /*
177              * This loop is run once for every individual HTTP request that gets sent.
178              * The very first HTTP request is a "virgin" request, while every subsequent
179              * request is done with the original ETag and a byte-range.
180              */
181 http_request_loop:
182             while (true) {
183                 // Prepares the request and fires it.
184                 HttpGet request = new HttpGet(mInfo.mUri);
185
186                 if (Constants.LOGV) {
187                     Log.v(Constants.TAG, "initiating download for " + mInfo.mUri);
188                 }
189
190                 if (mInfo.mCookies != null) {
191                     request.addHeader("Cookie", mInfo.mCookies);
192                 }
193                 if (mInfo.mReferer != null) {
194                     request.addHeader("Referer", mInfo.mReferer);
195                 }
196                 if (continuingDownload) {
197                     if (headerETag != null) {
198                         request.addHeader("If-Match", headerETag);
199                     }
200                     request.addHeader("Range", "bytes=" + bytesSoFar + "-");
201                 }
202
203                 HttpResponse response;
204                 try {
205                     response = client.execute(request);
206                 } catch (IllegalArgumentException ex) {
207                     if (Constants.LOGV) {
208                         Log.d(Constants.TAG, "Arg exception trying to execute request for " +
209                                 mInfo.mUri + " : " + ex);
210                     } else if (Config.LOGD) {
211                         Log.d(Constants.TAG, "Arg exception trying to execute request for " +
212                                 mInfo.mId + " : " +  ex);
213                     }
214                     finalStatus = Downloads.Impl.STATUS_BAD_REQUEST;
215                     request.abort();
216                     break http_request_loop;
217                 } catch (IOException ex) {
218                     if (Constants.LOGX) {
219                         if (Helpers.isNetworkAvailable(mContext)) {
220                             Log.i(Constants.TAG, "Execute Failed " + mInfo.mId + ", Net Up");
221                         } else {
222                             Log.i(Constants.TAG, "Execute Failed " + mInfo.mId + ", Net Down");
223                         }
224                     }
225                     if (!Helpers.isNetworkAvailable(mContext)) {
226                         finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
227                     } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
228                         finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
229                         countRetry = true;
230                     } else {
231                         if (Constants.LOGV) {
232                             Log.d(Constants.TAG, "IOException trying to execute request for " +
233                                     mInfo.mUri + " : " + ex);
234                         } else if (Config.LOGD) {
235                             Log.d(Constants.TAG, "IOException trying to execute request for " +
236                                     mInfo.mId + " : " + ex);
237                         }
238                         finalStatus = Downloads.Impl.STATUS_HTTP_DATA_ERROR;
239                     }
240                     request.abort();
241                     break http_request_loop;
242                 }
243
244                 int statusCode = response.getStatusLine().getStatusCode();
245                 if (statusCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) {
246                     if (Constants.LOGVV) {
247                         Log.v(Constants.TAG, "got HTTP response code 503");
248                     }
249                     finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
250                     countRetry = true;
251                     Header header = response.getFirstHeader("Retry-After");
252                     if (header != null) {
253                        try {
254                            if (Constants.LOGVV) {
255                                Log.v(Constants.TAG, "Retry-After :" + header.getValue());
256                            }
257                            retryAfter = Integer.parseInt(header.getValue());
258                            if (retryAfter < 0) {
259                                retryAfter = 0;
260                            } else {
261                                if (retryAfter < Constants.MIN_RETRY_AFTER) {
262                                    retryAfter = Constants.MIN_RETRY_AFTER;
263                                } else if (retryAfter > Constants.MAX_RETRY_AFTER) {
264                                    retryAfter = Constants.MAX_RETRY_AFTER;
265                                }
266                                retryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1);
267                                retryAfter *= 1000;
268                            }
269                        } catch (NumberFormatException ex) {
270                            // ignored - retryAfter stays 0 in this case.
271                        }
272                     }
273                     request.abort();
274                     break http_request_loop;
275                 }
276                 if (statusCode == 301 ||
277                         statusCode == 302 ||
278                         statusCode == 303 ||
279                         statusCode == 307) {
280                     if (Constants.LOGVV) {
281                         Log.v(Constants.TAG, "got HTTP redirect " + statusCode);
282                     }
283                     if (redirectCount >= Constants.MAX_REDIRECTS) {
284                         if (Constants.LOGV) {
285                             Log.d(Constants.TAG, "too many redirects for download " + mInfo.mId +
286                                     " at " + mInfo.mUri);
287                         } else if (Config.LOGD) {
288                             Log.d(Constants.TAG, "too many redirects for download " + mInfo.mId);
289                         }
290                         finalStatus = Downloads.Impl.STATUS_TOO_MANY_REDIRECTS;
291                         request.abort();
292                         break http_request_loop;
293                     }
294                     Header header = response.getFirstHeader("Location");
295                     if (header != null) {
296                         if (Constants.LOGVV) {
297                             Log.v(Constants.TAG, "Location :" + header.getValue());
298                         }
299                         try {
300                             newUri = new URI(mInfo.mUri).
301                                     resolve(new URI(header.getValue())).
302                                     toString();
303                         } catch(URISyntaxException ex) {
304                             if (Constants.LOGV) {
305                                 Log.d(Constants.TAG,
306                                         "Couldn't resolve redirect URI " +
307                                         header.getValue() +
308                                         " for " +
309                                         mInfo.mUri);
310                             } else if (Config.LOGD) {
311                                 Log.d(Constants.TAG,
312                                         "Couldn't resolve redirect URI for download " +
313                                         mInfo.mId);
314                             }
315                             finalStatus = Downloads.Impl.STATUS_BAD_REQUEST;
316                             request.abort();
317                             break http_request_loop;
318                         }
319                         ++redirectCount;
320                         finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
321                         request.abort();
322                         break http_request_loop;
323                     }
324                 }
325                 if ((!continuingDownload && statusCode != Downloads.Impl.STATUS_SUCCESS)
326                         || (continuingDownload && statusCode != 206)) {
327                     if (Constants.LOGV) {
328                         Log.d(Constants.TAG, "http error " + statusCode + " for " + mInfo.mUri);
329                     } else if (Config.LOGD) {
330                         Log.d(Constants.TAG, "http error " + statusCode + " for download " +
331                                 mInfo.mId);
332                     }
333                     if (Downloads.Impl.isStatusError(statusCode)) {
334                         finalStatus = statusCode;
335                     } else if (statusCode >= 300 && statusCode < 400) {
336                         finalStatus = Downloads.Impl.STATUS_UNHANDLED_REDIRECT;
337                     } else if (continuingDownload && statusCode == Downloads.Impl.STATUS_SUCCESS) {
338                         finalStatus = Downloads.Impl.STATUS_PRECONDITION_FAILED;
339                     } else {
340                         finalStatus = Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE;
341                     }
342                     request.abort();
343                     break http_request_loop;
344                 } else {
345                     // Handles the response, saves the file
346                     if (Constants.LOGV) {
347                         Log.v(Constants.TAG, "received response for " + mInfo.mUri);
348                     }
349
350                     if (!continuingDownload) {
351                         Header header = response.getFirstHeader("Accept-Ranges");
352                         if (header != null) {
353                             headerAcceptRanges = header.getValue();
354                         }
355                         header = response.getFirstHeader("Content-Disposition");
356                         if (header != null) {
357                             headerContentDisposition = header.getValue();
358                         }
359                         header = response.getFirstHeader("Content-Location");
360                         if (header != null) {
361                             headerContentLocation = header.getValue();
362                         }
363                         if (mimeType == null) {
364                             header = response.getFirstHeader("Content-Type");
365                             if (header != null) {
366                                 mimeType = sanitizeMimeType(header.getValue()); 
367                             }
368                         }
369                         header = response.getFirstHeader("ETag");
370                         if (header != null) {
371                             headerETag = header.getValue();
372                         }
373                         header = response.getFirstHeader("Transfer-Encoding");
374                         if (header != null) {
375                             headerTransferEncoding = header.getValue();
376                         }
377                         if (headerTransferEncoding == null) {
378                             header = response.getFirstHeader("Content-Length");
379                             if (header != null) {
380                                 headerContentLength = header.getValue();
381                             }
382                         } else {
383                             // Ignore content-length with transfer-encoding - 2616 4.4 3
384                             if (Constants.LOGVV) {
385                                 Log.v(Constants.TAG,
386                                         "ignoring content-length because of xfer-encoding");
387                             }
388                         }
389                         if (Constants.LOGVV) {
390                             Log.v(Constants.TAG, "Accept-Ranges: " + headerAcceptRanges);
391                             Log.v(Constants.TAG, "Content-Disposition: " +
392                                     headerContentDisposition);
393                             Log.v(Constants.TAG, "Content-Length: " + headerContentLength);
394                             Log.v(Constants.TAG, "Content-Location: " + headerContentLocation);
395                             Log.v(Constants.TAG, "Content-Type: " + mimeType);
396                             Log.v(Constants.TAG, "ETag: " + headerETag);
397                             Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding);
398                         }
399
400                         if (!mInfo.mNoIntegrity && headerContentLength == null &&
401                                 (headerTransferEncoding == null
402                                         || !headerTransferEncoding.equalsIgnoreCase("chunked"))
403                                 ) {
404                             if (Config.LOGD) {
405                                 Log.d(Constants.TAG, "can't know size of download, giving up");
406                             }
407                             finalStatus = Downloads.Impl.STATUS_LENGTH_REQUIRED;
408                             request.abort();
409                             break http_request_loop;
410                         }
411
412                         DownloadFileInfo fileInfo = Helpers.generateSaveFile(
413                                 mContext,
414                                 mInfo.mUri,
415                                 mInfo.mHint,
416                                 headerContentDisposition,
417                                 headerContentLocation,
418                                 mimeType,
419                                 mInfo.mDestination,
420                                 (headerContentLength != null) ?
421                                         Integer.parseInt(headerContentLength) : 0);
422                         if (fileInfo.mFileName == null) {
423                             finalStatus = fileInfo.mStatus;
424                             request.abort();
425                             break http_request_loop;
426                         }
427                         filename = fileInfo.mFileName;
428                         stream = fileInfo.mStream;
429                         if (Constants.LOGV) {
430                             Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + filename);
431                         }
432
433                         ContentValues values = new ContentValues();
434                         values.put(Downloads.Impl._DATA, filename);
435                         if (headerETag != null) {
436                             values.put(Constants.ETAG, headerETag);
437                         }
438                         if (mimeType != null) {
439                             values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimeType);
440                         }
441                         int contentLength = -1;
442                         if (headerContentLength != null) {
443                             contentLength = Integer.parseInt(headerContentLength);
444                         }
445                         values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength);
446                         mContext.getContentResolver().update(contentUri, values, null, null);
447                     }
448
449                     InputStream entityStream;
450                     try {
451                         entityStream = response.getEntity().getContent();
452                     } catch (IOException ex) {
453                         if (Constants.LOGX) {
454                             if (Helpers.isNetworkAvailable(mContext)) {
455                                 Log.i(Constants.TAG, "Get Failed " + mInfo.mId + ", Net Up");
456                             } else {
457                                 Log.i(Constants.TAG, "Get Failed " + mInfo.mId + ", Net Down");
458                             }
459                         }
460                         if (!Helpers.isNetworkAvailable(mContext)) {
461                             finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
462                         } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
463                             finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
464                             countRetry = true;
465                         } else {
466                             if (Constants.LOGV) {
467                                 Log.d(Constants.TAG,
468                                         "IOException getting entity for " +
469                                         mInfo.mUri +
470                                         " : " +
471                                         ex);
472                             } else if (Config.LOGD) {
473                                 Log.d(Constants.TAG, "IOException getting entity for download " +
474                                         mInfo.mId + " : " + ex);
475                             }
476                             finalStatus = Downloads.Impl.STATUS_HTTP_DATA_ERROR;
477                         }
478                         request.abort();
479                         break http_request_loop;
480                     }
481                     for (;;) {
482                         int bytesRead;
483                         try {
484                             bytesRead = entityStream.read(data);
485                         } catch (IOException ex) {
486                             if (Constants.LOGX) {
487                                 if (Helpers.isNetworkAvailable(mContext)) {
488                                     Log.i(Constants.TAG, "Read Failed " + mInfo.mId + ", Net Up");
489                                 } else {
490                                     Log.i(Constants.TAG, "Read Failed " + mInfo.mId + ", Net Down");
491                                 }
492                             }
493                             ContentValues values = new ContentValues();
494                             values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, bytesSoFar);
495                             mContext.getContentResolver().update(contentUri, values, null, null);
496                             if (!mInfo.mNoIntegrity && headerETag == null) {
497                                 if (Constants.LOGV) {
498                                     Log.v(Constants.TAG, "download IOException for " + mInfo.mUri +
499                                             " : " + ex);
500                                 } else if (Config.LOGD) {
501                                     Log.d(Constants.TAG, "download IOException for download " +
502                                             mInfo.mId + " : " + ex);
503                                 }
504                                 if (Config.LOGD) {
505                                     Log.d(Constants.TAG,
506                                             "can't resume interrupted download with no ETag");
507                                 }
508                                 finalStatus = Downloads.Impl.STATUS_PRECONDITION_FAILED;
509                             } else if (!Helpers.isNetworkAvailable(mContext)) {
510                                 finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
511                             } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
512                                 finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
513                                 countRetry = true;
514                             } else {
515                                 if (Constants.LOGV) {
516                                     Log.v(Constants.TAG, "download IOException for " + mInfo.mUri +
517                                             " : " + ex);
518                                 } else if (Config.LOGD) {
519                                     Log.d(Constants.TAG, "download IOException for download " +
520                                             mInfo.mId + " : " + ex);
521                                 }
522                                 finalStatus = Downloads.Impl.STATUS_HTTP_DATA_ERROR;
523                             }
524                             request.abort();
525                             break http_request_loop;
526                         }
527                         if (bytesRead == -1) { // success
528                             ContentValues values = new ContentValues();
529                             values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, bytesSoFar);
530                             if (headerContentLength == null) {
531                                 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, bytesSoFar);
532                             }
533                             mContext.getContentResolver().update(contentUri, values, null, null);
534                             if ((headerContentLength != null)
535                                     && (bytesSoFar
536                                             != Integer.parseInt(headerContentLength))) {
537                                 if (!mInfo.mNoIntegrity && headerETag == null) {
538                                     if (Constants.LOGV) {
539                                         Log.d(Constants.TAG, "mismatched content length " +
540                                                 mInfo.mUri);
541                                     } else if (Config.LOGD) {
542                                         Log.d(Constants.TAG, "mismatched content length for " +
543                                                 mInfo.mId);
544                                     }
545                                     finalStatus = Downloads.Impl.STATUS_LENGTH_REQUIRED;
546                                 } else if (!Helpers.isNetworkAvailable(mContext)) {
547                                     finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
548                                 } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
549                                     finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
550                                     countRetry = true;
551                                 } else {
552                                     if (Constants.LOGV) {
553                                         Log.v(Constants.TAG, "closed socket for " + mInfo.mUri);
554                                     } else if (Config.LOGD) {
555                                         Log.d(Constants.TAG, "closed socket for download " +
556                                                 mInfo.mId);
557                                     }
558                                     finalStatus = Downloads.Impl.STATUS_HTTP_DATA_ERROR;
559                                 }
560                                 break http_request_loop;
561                             }
562                             break;
563                         }
564                         gotData = true;
565                         for (;;) {
566                             try {
567                                 if (stream == null) {
568                                     stream = new FileOutputStream(filename, true);
569                                 }
570                                 stream.write(data, 0, bytesRead);
571                                 if (mInfo.mDestination == Downloads.Impl.DESTINATION_EXTERNAL
572                                             && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
573                                             .equalsIgnoreCase(mimeType)) {
574                                     try {
575                                         stream.close();
576                                         stream = null;
577                                     } catch (IOException ex) {
578                                         if (Constants.LOGV) {
579                                             Log.v(Constants.TAG,
580                                                     "exception when closing the file " +
581                                                     "during download : " + ex);
582                                         }
583                                         // nothing can really be done if the file can't be closed
584                                     }
585                                 }
586                                 break;
587                             } catch (IOException ex) {
588                                 if (!Helpers.discardPurgeableFiles(
589                                         mContext, Constants.BUFFER_SIZE)) {
590                                     finalStatus = Downloads.Impl.STATUS_FILE_ERROR;
591                                     break http_request_loop;
592                                 }
593                             }
594                         }
595                         bytesSoFar += bytesRead;
596                         long now = System.currentTimeMillis();
597                         if (bytesSoFar - bytesNotified > Constants.MIN_PROGRESS_STEP
598                                 && now - timeLastNotification
599                                         > Constants.MIN_PROGRESS_TIME) {
600                             ContentValues values = new ContentValues();
601                             values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, bytesSoFar);
602                             mContext.getContentResolver().update(
603                                     contentUri, values, null, null);
604                             bytesNotified = bytesSoFar;
605                             timeLastNotification = now;
606                         }
607
608                         if (Constants.LOGVV) {
609                             Log.v(Constants.TAG, "downloaded " + bytesSoFar + " for " + mInfo.mUri);
610                         }
611                         synchronized (mInfo) {
612                             if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
613                                 if (Constants.LOGV) {
614                                     Log.v(Constants.TAG, "paused " + mInfo.mUri);
615                                 }
616                                 finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED;
617                                 request.abort();
618                                 break http_request_loop;
619                             }
620                         }
621                         if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
622                             if (Constants.LOGV) {
623                                 Log.d(Constants.TAG, "canceled " + mInfo.mUri);
624                             } else if (Config.LOGD) {
625                                 // Log.d(Constants.TAG, "canceled id " + mInfo.mId);
626                             }
627                             finalStatus = Downloads.Impl.STATUS_CANCELED;
628                             break http_request_loop;
629                         }
630                     }
631                     if (Constants.LOGV) {
632                         Log.v(Constants.TAG, "download completed for " + mInfo.mUri);
633                     }
634                     finalStatus = Downloads.Impl.STATUS_SUCCESS;
635                 }
636                 break;
637             }
638         } catch (FileNotFoundException ex) {
639             if (Config.LOGD) {
640                 Log.d(Constants.TAG, "FileNotFoundException for " + filename + " : " +  ex);
641             }
642             finalStatus = Downloads.Impl.STATUS_FILE_ERROR;
643             // falls through to the code that reports an error
644         } catch (RuntimeException ex) { //sometimes the socket code throws unchecked exceptions
645             if (Constants.LOGV) {
646                 Log.d(Constants.TAG, "Exception for " + mInfo.mUri, ex);
647             } else if (Config.LOGD) {
648                 Log.d(Constants.TAG, "Exception for id " + mInfo.mId, ex);
649             }
650             finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
651             // falls through to the code that reports an error
652         } finally {
653             mInfo.mHasActiveThread = false;
654             if (wakeLock != null) {
655                 wakeLock.release();
656                 wakeLock = null;
657             }
658             if (client != null) {
659                 client.close();
660                 client = null;
661             }
662             try {
663                 // close the file
664                 if (stream != null) {
665                     stream.close();
666                 }
667             } catch (IOException ex) {
668                 if (Constants.LOGV) {
669                     Log.v(Constants.TAG, "exception when closing the file after download : " + ex);
670                 }
671                 // nothing can really be done if the file can't be closed
672             }
673             if (filename != null) {
674                 // if the download wasn't successful, delete the file
675                 if (Downloads.Impl.isStatusError(finalStatus)) {
676                     new File(filename).delete();
677                     filename = null;
678                 } else if (Downloads.Impl.isStatusSuccess(finalStatus) &&
679                         DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
680                         .equalsIgnoreCase(mimeType)) {
681                     // transfer the file to the DRM content provider 
682                     File file = new File(filename);
683                     Intent item = DrmStore.addDrmFile(mContext.getContentResolver(), file, null);
684                     if (item == null) {
685                         Log.w(Constants.TAG, "unable to add file " + filename + " to DrmProvider");
686                         finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
687                     } else {
688                         filename = item.getDataString();
689                         mimeType = item.getType();
690                     }
691                     
692                     file.delete();
693                 } else if (Downloads.Impl.isStatusSuccess(finalStatus)) {
694                     // make sure the file is readable
695                     FileUtils.setPermissions(filename, 0644, -1, -1);
696
697                     // Sync to storage after completion
698                     try {
699                         new FileOutputStream(filename, true).getFD().sync();
700                     } catch (FileNotFoundException ex) {
701                         Log.w(Constants.TAG, "file " + filename + " not found: " + ex);
702                     } catch (SyncFailedException ex) {
703                         Log.w(Constants.TAG, "file " + filename + " sync failed: " + ex);
704                     } catch (IOException ex) {
705                         Log.w(Constants.TAG, "IOException trying to sync " + filename + ": " + ex);
706                     } catch (RuntimeException ex) {
707                         Log.w(Constants.TAG, "exception while syncing file: ", ex);
708                     }
709                 }
710             }
711             notifyDownloadCompleted(finalStatus, countRetry, retryAfter, redirectCount,
712                     gotData, filename, newUri, mimeType);
713         }
714     }
715
716     /**
717      * Stores information about the completed download, and notifies the initiating application.
718      */
719     private void notifyDownloadCompleted(
720             int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
721             String filename, String uri, String mimeType) {
722         notifyThroughDatabase(
723                 status, countRetry, retryAfter, redirectCount, gotData, filename, uri, mimeType);
724         if (Downloads.Impl.isStatusCompleted(status)) {
725             notifyThroughIntent();
726         }
727     }
728
729     private void notifyThroughDatabase(
730             int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
731             String filename, String uri, String mimeType) {
732         ContentValues values = new ContentValues();
733         values.put(Downloads.Impl.COLUMN_STATUS, status);
734         values.put(Downloads.Impl._DATA, filename);
735         if (uri != null) {
736             values.put(Downloads.Impl.COLUMN_URI, uri);
737         }
738         values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimeType);
739         values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, System.currentTimeMillis());
740         values.put(Constants.RETRY_AFTER_X_REDIRECT_COUNT, retryAfter + (redirectCount << 28));
741         if (!countRetry) {
742             values.put(Constants.FAILED_CONNECTIONS, 0);
743         } else if (gotData) {
744             values.put(Constants.FAILED_CONNECTIONS, 1);
745         } else {
746             values.put(Constants.FAILED_CONNECTIONS, mInfo.mNumFailed + 1);
747         }
748
749         mContext.getContentResolver().update(ContentUris.withAppendedId(
750                 Downloads.Impl.CONTENT_URI, mInfo.mId), values, null, null);
751     }
752
753     /**
754      * Notifies the initiating app if it requested it. That way, it can know that the
755      * download completed even if it's not actively watching the cursor.
756      */
757     private void notifyThroughIntent() {
758         Uri uri = Uri.parse(Downloads.Impl.CONTENT_URI + "/" + mInfo.mId);
759         mInfo.sendIntentIfRequested(uri, mContext);
760     }
761
762     /**
763      * Clean up a mimeType string so it can be used to dispatch an intent to
764      * view a downloaded asset.
765      * @param mimeType either null or one or more mime types (semi colon separated).
766      * @return null if mimeType was null. Otherwise a string which represents a
767      * single mimetype in lowercase and with surrounding whitespaces trimmed.
768      */
769     private String sanitizeMimeType(String mimeType) {
770         try {
771             mimeType = mimeType.trim().toLowerCase(Locale.ENGLISH);
772
773             final int semicolonIndex = mimeType.indexOf(';');
774             if (semicolonIndex != -1) {
775                 mimeType = mimeType.substring(0, semicolonIndex);
776             }
777             return mimeType;
778         } catch (NullPointerException npe) {
779             return null;
780         }
781     }
782 }