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