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