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