Code drop from //branches/cupcake/...@124589
[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
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.userAgent;
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.STATUS_UNKNOWN_ERROR;
85         boolean countRetry = false;
86         int retryAfter = 0;
87         int redirectCount = mInfo.redirectCount;
88         String newUri = null;
89         boolean gotData = false;
90         String filename = null;
91         String mimeType = mInfo.mimetype;
92         FileOutputStream stream = null;
93         AndroidHttpClient client = null;
94         PowerManager.WakeLock wakeLock = null;
95         Uri contentUri = Uri.parse(Downloads.CONTENT_URI + "/" + mInfo.id);
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.filename;
115             if (filename != null) {
116                 if (!Helpers.isFilenameValid(filename)) {
117                     finalStatus = Downloads.STATUS_FILE_ERROR;
118                     notifyDownloadCompleted(
119                             finalStatus, false, 0, 0, false, filename, null, mInfo.mimetype);
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.etag == null && !mInfo.noIntegrity) {
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.STATUS_PRECONDITION_FAILED;
138                         notifyDownloadCompleted(
139                                 finalStatus, false, 0, 0, false, filename, null, mInfo.mimetype);
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.totalBytes != -1) {
146                             headerContentLength = Integer.toString(mInfo.totalBytes);
147                         }
148                         headerETag = mInfo.etag;
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());
160
161             if (stream != null && mInfo.destination == Downloads.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.uri);
185
186                 if (Constants.LOGV) {
187                     Log.v(Constants.TAG, "initiating download for " + mInfo.uri);
188                 }
189
190                 if (mInfo.cookies != null) {
191                     request.addHeader("Cookie", mInfo.cookies);
192                 }
193                 if (mInfo.referer != null) {
194                     request.addHeader("Referer", mInfo.referer);
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.uri + " : " + ex);
210                     } else if (Config.LOGD) {
211                         Log.d(Constants.TAG, "Arg exception trying to execute request for " +
212                                 mInfo.id + " : " +  ex);
213                     }
214                     finalStatus = Downloads.STATUS_BAD_REQUEST;
215                     request.abort();
216                     break http_request_loop;
217                 } catch (IOException ex) {
218                     if (!Helpers.isNetworkAvailable(mContext)) {
219                         finalStatus = Downloads.STATUS_RUNNING_PAUSED;
220                     } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
221                         finalStatus = Downloads.STATUS_RUNNING_PAUSED;
222                         countRetry = true;
223                     } else {
224                         if (Constants.LOGV) {
225                             Log.d(Constants.TAG, "IOException trying to execute request for " +
226                                     mInfo.uri + " : " + ex);
227                         } else if (Config.LOGD) {
228                             Log.d(Constants.TAG, "IOException trying to execute request for " +
229                                     mInfo.id + " : " + ex);
230                         }
231                         finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
232                     }
233                     request.abort();
234                     break http_request_loop;
235                 }
236
237                 int statusCode = response.getStatusLine().getStatusCode();
238                 if (statusCode == 503 && mInfo.numFailed < Constants.MAX_RETRIES) {
239                     if (Constants.LOGVV) {
240                         Log.v(Constants.TAG, "got HTTP response code 503");
241                     }
242                     finalStatus = Downloads.STATUS_RUNNING_PAUSED;
243                     countRetry = true;
244                     Header header = response.getFirstHeader("Retry-After");
245                     if (header != null) {
246                        try {
247                            if (Constants.LOGVV) {
248                                Log.v(Constants.TAG, "Retry-After :" + header.getValue());
249                            }
250                            retryAfter = Integer.parseInt(header.getValue());
251                            if (retryAfter < 0) {
252                                retryAfter = 0;
253                            } else {
254                                if (retryAfter < Constants.MIN_RETRY_AFTER) {
255                                    retryAfter = Constants.MIN_RETRY_AFTER;
256                                } else if (retryAfter > Constants.MAX_RETRY_AFTER) {
257                                    retryAfter = Constants.MAX_RETRY_AFTER;
258                                }
259                                retryAfter += Helpers.rnd.nextInt(Constants.MIN_RETRY_AFTER + 1);
260                                retryAfter *= 1000;
261                            }
262                        } catch (NumberFormatException ex) {
263                            // ignored - retryAfter stays 0 in this case.
264                        }
265                     }
266                     request.abort();
267                     break http_request_loop;
268                 }
269                 if (statusCode == 301 ||
270                         statusCode == 302 ||
271                         statusCode == 303 ||
272                         statusCode == 307) {
273                     if (Constants.LOGVV) {
274                         Log.v(Constants.TAG, "got HTTP redirect " + statusCode);
275                     }
276                     if (redirectCount >= Constants.MAX_REDIRECTS) {
277                         if (Constants.LOGV) {
278                             Log.d(Constants.TAG, "too many redirects for download " + mInfo.id +
279                                     " at " + mInfo.uri);
280                         } else if (Config.LOGD) {
281                             Log.d(Constants.TAG, "too many redirects for download " + mInfo.id);
282                         }
283                         finalStatus = Downloads.STATUS_TOO_MANY_REDIRECTS;
284                         request.abort();
285                         break http_request_loop;
286                     }
287                     Header header = response.getFirstHeader("Location");
288                     if (header != null) {
289                         if (Constants.LOGVV) {
290                             Log.v(Constants.TAG, "Location :" + header.getValue());
291                         }
292                         newUri = new URI(mInfo.uri).resolve(new URI(header.getValue())).toString();
293                         ++redirectCount;
294                         finalStatus = Downloads.STATUS_RUNNING_PAUSED;
295                         request.abort();
296                         break http_request_loop;
297                     }
298                 }
299                 if ((!continuingDownload && statusCode != Downloads.STATUS_SUCCESS)
300                         || (continuingDownload && statusCode != 206)) {
301                     if (Constants.LOGV) {
302                         Log.d(Constants.TAG, "http error " + statusCode + " for " + mInfo.uri);
303                     } else if (Config.LOGD) {
304                         Log.d(Constants.TAG, "http error " + statusCode + " for download " +
305                                 mInfo.id);
306                     }
307                     if (Downloads.isStatusError(statusCode)) {
308                         finalStatus = statusCode;
309                     } else if (statusCode >= 300 && statusCode < 400) {
310                         finalStatus = Downloads.STATUS_UNHANDLED_REDIRECT;
311                     } else if (continuingDownload && statusCode == Downloads.STATUS_SUCCESS) {
312                         finalStatus = Downloads.STATUS_PRECONDITION_FAILED;
313                     } else {
314                         finalStatus = Downloads.STATUS_UNHANDLED_HTTP_CODE;
315                     }
316                     request.abort();
317                     break http_request_loop;
318                 } else {
319                     // Handles the response, saves the file
320                     if (Constants.LOGV) {
321                         Log.v(Constants.TAG, "received response for " + mInfo.uri);
322                     }
323
324                     if (!continuingDownload) {
325                         Header header = response.getFirstHeader("Accept-Ranges");
326                         if (header != null) {
327                             headerAcceptRanges = header.getValue();
328                         }
329                         header = response.getFirstHeader("Content-Disposition");
330                         if (header != null) {
331                             headerContentDisposition = header.getValue();
332                         }
333                         header = response.getFirstHeader("Content-Location");
334                         if (header != null) {
335                             headerContentLocation = header.getValue();
336                         }
337                         if (mimeType == null) {
338                             header = response.getFirstHeader("Content-Type");
339                             if (header != null) {
340                                 mimeType = header.getValue();
341                                 final int semicolonIndex = mimeType.indexOf(';');
342                                 if (semicolonIndex != -1) {
343                                     mimeType = mimeType.substring(0, semicolonIndex);
344                                 }
345                             }
346                         }
347                         header = response.getFirstHeader("ETag");
348                         if (header != null) {
349                             headerETag = header.getValue();
350                         }
351                         header = response.getFirstHeader("Transfer-Encoding");
352                         if (header != null) {
353                             headerTransferEncoding = header.getValue();
354                         }
355                         if (headerTransferEncoding == null) {
356                             header = response.getFirstHeader("Content-Length");
357                             if (header != null) {
358                                 headerContentLength = header.getValue();
359                             }
360                         } else {
361                             // Ignore content-length with transfer-encoding - 2616 4.4 3
362                             if (Constants.LOGVV) {
363                                 Log.v(Constants.TAG,
364                                         "ignoring content-length because of xfer-encoding");
365                             }
366                         }
367                         if (Constants.LOGVV) {
368                             Log.v(Constants.TAG, "Accept-Ranges: " + headerAcceptRanges);
369                             Log.v(Constants.TAG, "Content-Disposition: " +
370                                     headerContentDisposition);
371                             Log.v(Constants.TAG, "Content-Length: " + headerContentLength);
372                             Log.v(Constants.TAG, "Content-Location: " + headerContentLocation);
373                             Log.v(Constants.TAG, "Content-Type: " + mimeType);
374                             Log.v(Constants.TAG, "ETag: " + headerETag);
375                             Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding);
376                         }
377
378                         if (!mInfo.noIntegrity && headerContentLength == null &&
379                                 (headerTransferEncoding == null
380                                         || !headerTransferEncoding.equalsIgnoreCase("chunked"))
381                                 ) {
382                             if (Config.LOGD) {
383                                 Log.d(Constants.TAG, "can't know size of download, giving up");
384                             }
385                             finalStatus = Downloads.STATUS_LENGTH_REQUIRED;
386                             request.abort();
387                             break http_request_loop;
388                         }
389
390                         DownloadFileInfo fileInfo = Helpers.generateSaveFile(
391                                 mContext,
392                                 mInfo.uri,
393                                 mInfo.hint,
394                                 headerContentDisposition,
395                                 headerContentLocation,
396                                 mimeType,
397                                 mInfo.destination,
398                                 (headerContentLength != null) ?
399                                         Integer.parseInt(headerContentLength) : 0);
400                         if (fileInfo.filename == null) {
401                             finalStatus = fileInfo.status;
402                             request.abort();
403                             break http_request_loop;
404                         }
405                         filename = fileInfo.filename;
406                         stream = fileInfo.stream;
407                         if (Constants.LOGV) {
408                             Log.v(Constants.TAG, "writing " + mInfo.uri + " to " + filename);
409                         }
410
411                         ContentValues values = new ContentValues();
412                         values.put(Downloads._DATA, filename);
413                         if (headerETag != null) {
414                             values.put(Constants.ETAG, headerETag);
415                         }
416                         if (mimeType != null) {
417                             values.put(Downloads.MIMETYPE, mimeType);
418                         }
419                         int contentLength = -1;
420                         if (headerContentLength != null) {
421                             contentLength = Integer.parseInt(headerContentLength);
422                         }
423                         values.put(Downloads.TOTAL_BYTES, contentLength);
424                         mContext.getContentResolver().update(contentUri, values, null, null);
425                     }
426
427                     InputStream entityStream;
428                     try {
429                         entityStream = response.getEntity().getContent();
430                     } catch (IOException ex) {
431                         if (!Helpers.isNetworkAvailable(mContext)) {
432                             finalStatus = Downloads.STATUS_RUNNING_PAUSED;
433                         } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
434                             finalStatus = Downloads.STATUS_RUNNING_PAUSED;
435                             countRetry = true;
436                         } else {
437                             if (Constants.LOGV) {
438                                 Log.d(Constants.TAG, "IOException getting entity for " + mInfo.uri +
439                                     " : " + ex);
440                             } else if (Config.LOGD) {
441                                 Log.d(Constants.TAG, "IOException getting entity for download " +
442                                         mInfo.id + " : " + ex);
443                             }
444                             finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
445                         }
446                         request.abort();
447                         break http_request_loop;
448                     }
449                     for (;;) {
450                         int bytesRead;
451                         try {
452                             bytesRead = entityStream.read(data);
453                         } catch (IOException ex) {
454                             ContentValues values = new ContentValues();
455                             values.put(Downloads.CURRENT_BYTES, bytesSoFar);
456                             mContext.getContentResolver().update(contentUri, values, null, null);
457                             if (!mInfo.noIntegrity && headerETag == null) {
458                                 if (Constants.LOGV) {
459                                     Log.v(Constants.TAG, "download IOException for " + mInfo.uri +
460                                             " : " + ex);
461                                 } else if (Config.LOGD) {
462                                     Log.d(Constants.TAG, "download IOException for download " +
463                                             mInfo.id + " : " + ex);
464                                 }
465                                 if (Config.LOGD) {
466                                     Log.d(Constants.TAG,
467                                             "can't resume interrupted download with no ETag");
468                                 }
469                                 finalStatus = Downloads.STATUS_PRECONDITION_FAILED;
470                             } else if (!Helpers.isNetworkAvailable(mContext)) {
471                                 finalStatus = Downloads.STATUS_RUNNING_PAUSED;
472                             } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
473                                 finalStatus = Downloads.STATUS_RUNNING_PAUSED;
474                                 countRetry = true;
475                             } else {
476                                 if (Constants.LOGV) {
477                                     Log.v(Constants.TAG, "download IOException for " + mInfo.uri +
478                                             " : " + ex);
479                                 } else if (Config.LOGD) {
480                                     Log.d(Constants.TAG, "download IOException for download " +
481                                             mInfo.id + " : " + ex);
482                                 }
483                                 finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
484                             }
485                             request.abort();
486                             break http_request_loop;
487                         }
488                         if (bytesRead == -1) { // success
489                             ContentValues values = new ContentValues();
490                             values.put(Downloads.CURRENT_BYTES, bytesSoFar);
491                             if (headerContentLength == null) {
492                                 values.put(Downloads.TOTAL_BYTES, bytesSoFar);
493                             }
494                             mContext.getContentResolver().update(contentUri, values, null, null);
495                             if ((headerContentLength != null)
496                                     && (bytesSoFar
497                                             != Integer.parseInt(headerContentLength))) {
498                                 if (!mInfo.noIntegrity && headerETag == null) {
499                                     if (Constants.LOGV) {
500                                         Log.d(Constants.TAG, "mismatched content length " +
501                                                 mInfo.uri);
502                                     } else if (Config.LOGD) {
503                                         Log.d(Constants.TAG, "mismatched content length for " +
504                                                 mInfo.id);
505                                     }
506                                     finalStatus = Downloads.STATUS_LENGTH_REQUIRED;
507                                 } else if (!Helpers.isNetworkAvailable(mContext)) {
508                                     finalStatus = Downloads.STATUS_RUNNING_PAUSED;
509                                 } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
510                                     finalStatus = Downloads.STATUS_RUNNING_PAUSED;
511                                     countRetry = true;
512                                 } else {
513                                     if (Constants.LOGV) {
514                                         Log.v(Constants.TAG, "closed socket for " + mInfo.uri);
515                                     } else if (Config.LOGD) {
516                                         Log.d(Constants.TAG, "closed socket for download " +
517                                                 mInfo.id);
518                                     }
519                                     finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
520                                 }
521                                 break http_request_loop;
522                             }
523                             break;
524                         }
525                         gotData = true;
526                         for (;;) {
527                             try {
528                                 if (stream == null) {
529                                     stream = new FileOutputStream(filename, true);
530                                 }
531                                 stream.write(data, 0, bytesRead);
532                                 if (mInfo.destination == Downloads.DESTINATION_EXTERNAL
533                                             && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
534                                             .equalsIgnoreCase(mimeType)) {
535                                     try {
536                                         stream.close();
537                                         stream = null;
538                                     } catch (IOException ex) {
539                                         if (Constants.LOGV) {
540                                             Log.v(Constants.TAG,
541                                                     "exception when closing the file " +
542                                                     "during download : " + ex);
543                                         }
544                                         // nothing can really be done if the file can't be closed
545                                     }
546                                 }
547                                 break;
548                             } catch (IOException ex) {
549                                 if (!Helpers.discardPurgeableFiles(
550                                         mContext, Constants.BUFFER_SIZE)) {
551                                     finalStatus = Downloads.STATUS_FILE_ERROR;
552                                     break http_request_loop;
553                                 }
554                             }
555                         }
556                         bytesSoFar += bytesRead;
557                         long now = System.currentTimeMillis();
558                         if (bytesSoFar - bytesNotified > Constants.MIN_PROGRESS_STEP
559                                 && now - timeLastNotification
560                                         > Constants.MIN_PROGRESS_TIME) {
561                             ContentValues values = new ContentValues();
562                             values.put(Downloads.CURRENT_BYTES, bytesSoFar);
563                             mContext.getContentResolver().update(
564                                     contentUri, values, null, null);
565                             bytesNotified = bytesSoFar;
566                             timeLastNotification = now;
567                         }
568
569                         if (Constants.LOGVV) {
570                             Log.v(Constants.TAG, "downloaded " + bytesSoFar + " for " + mInfo.uri);
571                         }
572                         synchronized(mInfo) {
573                             if (mInfo.control == Downloads.CONTROL_PAUSED) {
574                                 if (Constants.LOGV) {
575                                     Log.v(Constants.TAG, "paused " + mInfo.uri);
576                                 }
577                                 finalStatus = Downloads.STATUS_RUNNING_PAUSED;
578                                 request.abort();
579                                 break http_request_loop;
580                             }
581                         }
582                         if (mInfo.status == Downloads.STATUS_CANCELED) {
583                             if (Constants.LOGV) {
584                                 Log.d(Constants.TAG, "canceled " + mInfo.uri);
585                             } else if (Config.LOGD) {
586                                 // Log.d(Constants.TAG, "canceled id " + mInfo.id);
587                             }
588                             finalStatus = Downloads.STATUS_CANCELED;
589                             break http_request_loop;
590                         }
591                     }
592                     if (Constants.LOGV) {
593                         Log.v(Constants.TAG, "download completed for " + mInfo.uri);
594                     }
595                     finalStatus = Downloads.STATUS_SUCCESS;
596                 }
597                 break;
598             }
599         } catch (FileNotFoundException ex) {
600             if (Config.LOGD) {
601                 Log.d(Constants.TAG, "FileNotFoundException for " + filename + " : " +  ex);
602             }
603             finalStatus = Downloads.STATUS_FILE_ERROR;
604             // falls through to the code that reports an error
605         } catch (Exception ex) { //sometimes the socket code throws unchecked exceptions
606             if (Constants.LOGV) {
607                 Log.d(Constants.TAG, "Exception for " + mInfo.uri, ex);
608             } else if (Config.LOGD) {
609                 Log.d(Constants.TAG, "Exception for id " + mInfo.id, ex);
610             }
611             finalStatus = Downloads.STATUS_UNKNOWN_ERROR;
612             // falls through to the code that reports an error
613         } finally {
614             mInfo.hasActiveThread = false;
615             if (wakeLock != null) {
616                 wakeLock.release();
617                 wakeLock = null;
618             }
619             if (client != null) {
620                 client.close();
621                 client = null;
622             }
623             try {
624                 // close the file
625                 if (stream != null) {
626                     stream.close();
627                 }
628             } catch (IOException ex) {
629                 if (Constants.LOGV) {
630                     Log.v(Constants.TAG, "exception when closing the file after download : " + ex);
631                 }
632                 // nothing can really be done if the file can't be closed
633             }
634             if (filename != null) {
635                 // if the download wasn't successful, delete the file
636                 if (Downloads.isStatusError(finalStatus)) {
637                     new File(filename).delete();
638                     filename = null;
639                 } else if (Downloads.isStatusSuccess(finalStatus) &&
640                         DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
641                         .equalsIgnoreCase(mimeType)) {
642                     // transfer the file to the DRM content provider 
643                     File file = new File(filename);
644                     Intent item = DrmStore.addDrmFile(mContext.getContentResolver(), file, null);
645                     if (item == null) {
646                         Log.w(Constants.TAG, "unable to add file " + filename + " to DrmProvider");
647                         finalStatus = Downloads.STATUS_UNKNOWN_ERROR;
648                     } else {
649                         filename = item.getDataString();
650                         mimeType = item.getType();
651                     }
652                     
653                     file.delete();
654                 } else if (Downloads.isStatusSuccess(finalStatus)) {
655                     // make sure the file is readable
656                     FileUtils.setPermissions(filename, 0644, -1, -1);
657                 }
658             }
659             notifyDownloadCompleted(finalStatus, countRetry, retryAfter, redirectCount,
660                     gotData, filename, newUri, mimeType);
661         }
662     }
663
664     /**
665      * Stores information about the completed download, and notifies the initiating application.
666      */
667     private void notifyDownloadCompleted(
668             int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
669             String filename, String uri, String mimeType) {
670         notifyThroughDatabase(
671                 status, countRetry, retryAfter, redirectCount, gotData, filename, uri, mimeType);
672         if (Downloads.isStatusCompleted(status)) {
673             notifyThroughIntent();
674         }
675     }
676
677     private void notifyThroughDatabase(
678             int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
679             String filename, String uri, String mimeType) {
680         ContentValues values = new ContentValues();
681         values.put(Downloads.STATUS, status);
682         values.put(Downloads._DATA, filename);
683         if (uri != null) {
684             values.put(Downloads.URI, uri);
685         }
686         values.put(Downloads.MIMETYPE, mimeType);
687         values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
688         values.put(Constants.RETRY_AFTER___REDIRECT_COUNT, retryAfter + (redirectCount << 28));
689         if (!countRetry) {
690             values.put(Constants.FAILED_CONNECTIONS, 0);
691         } else if (gotData) {
692             values.put(Constants.FAILED_CONNECTIONS, 1);
693         } else {
694             values.put(Constants.FAILED_CONNECTIONS, mInfo.numFailed + 1);
695         }
696
697         mContext.getContentResolver().update(
698                 ContentUris.withAppendedId(Downloads.CONTENT_URI, mInfo.id), values, null, null);
699     }
700
701     /**
702      * Notifies the initiating app if it requested it. That way, it can know that the
703      * download completed even if it's not actively watching the cursor.
704      */
705     private void notifyThroughIntent() {
706         Uri uri = Uri.parse(Downloads.CONTENT_URI + "/" + mInfo.id);
707         mInfo.sendIntentIfRequested(uri, mContext);
708     }
709
710 }