summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads/DownloadThread.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/providers/downloads/DownloadThread.java')
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java710
1 files changed, 710 insertions, 0 deletions
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
new file mode 100644
index 00000000..923e36d1
--- /dev/null
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.downloads;
+
+import org.apache.http.client.methods.AbortableHttpRequest;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.HttpClient;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.drm.mobile1.DrmRawContent;
+import android.net.http.AndroidHttpClient;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.PowerManager;
+import android.os.Process;
+import android.provider.Downloads;
+import android.provider.DrmStore;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+
+/**
+ * Runs an actual download
+ */
+public class DownloadThread extends Thread {
+
+ private Context mContext;
+ private DownloadInfo mInfo;
+
+ public DownloadThread(Context context, DownloadInfo info) {
+ mContext = context;
+ mInfo = info;
+ }
+
+ /**
+ * Returns the user agent provided by the initiating app, or use the default one
+ */
+ private String userAgent() {
+ String userAgent = mInfo.userAgent;
+ if (userAgent != null) {
+ }
+ if (userAgent == null) {
+ userAgent = Constants.DEFAULT_USER_AGENT;
+ }
+ return userAgent;
+ }
+
+ /**
+ * Executes the download in a separate thread
+ */
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ int finalStatus = Downloads.STATUS_UNKNOWN_ERROR;
+ boolean countRetry = false;
+ int retryAfter = 0;
+ int redirectCount = mInfo.redirectCount;
+ String newUri = null;
+ boolean gotData = false;
+ String filename = null;
+ String mimeType = mInfo.mimetype;
+ FileOutputStream stream = null;
+ AndroidHttpClient client = null;
+ PowerManager.WakeLock wakeLock = null;
+ Uri contentUri = Uri.parse(Downloads.CONTENT_URI + "/" + mInfo.id);
+
+ try {
+ boolean continuingDownload = false;
+ String headerAcceptRanges = null;
+ String headerContentDisposition = null;
+ String headerContentLength = null;
+ String headerContentLocation = null;
+ String headerETag = null;
+ String headerTransferEncoding = null;
+
+ byte data[] = new byte[Constants.BUFFER_SIZE];
+
+ int bytesSoFar = 0;
+
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
+ wakeLock.acquire();
+
+ filename = mInfo.filename;
+ if (filename != null) {
+ if (!Helpers.isFilenameValid(filename)) {
+ finalStatus = Downloads.STATUS_FILE_ERROR;
+ notifyDownloadCompleted(
+ finalStatus, false, 0, 0, false, filename, null, mInfo.mimetype);
+ return;
+ }
+ // We're resuming a download that got interrupted
+ File f = new File(filename);
+ if (f.exists()) {
+ long fileLength = f.length();
+ if (fileLength == 0) {
+ // The download hadn't actually started, we can restart from scratch
+ f.delete();
+ filename = null;
+ } else if (mInfo.etag == null && !mInfo.noIntegrity) {
+ // Tough luck, that's not a resumable download
+ if (Config.LOGD) {
+ Log.d(Constants.TAG,
+ "can't resume interrupted non-resumable download");
+ }
+ f.delete();
+ finalStatus = Downloads.STATUS_PRECONDITION_FAILED;
+ notifyDownloadCompleted(
+ finalStatus, false, 0, 0, false, filename, null, mInfo.mimetype);
+ return;
+ } else {
+ // All right, we'll be able to resume this download
+ stream = new FileOutputStream(filename, true);
+ bytesSoFar = (int) fileLength;
+ if (mInfo.totalBytes != -1) {
+ headerContentLength = Integer.toString(mInfo.totalBytes);
+ }
+ headerETag = mInfo.etag;
+ continuingDownload = true;
+ }
+ }
+ }
+
+ int bytesNotified = bytesSoFar;
+ // starting with MIN_VALUE means that the first write will commit
+ // progress to the database
+ long timeLastNotification = 0;
+
+ client = AndroidHttpClient.newInstance(userAgent());
+
+ if (stream != null && mInfo.destination == Downloads.DESTINATION_EXTERNAL
+ && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
+ .equalsIgnoreCase(mimeType)) {
+ try {
+ stream.close();
+ stream = null;
+ } catch (IOException ex) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "exception when closing the file before download : " +
+ ex);
+ }
+ // nothing can really be done if the file can't be closed
+ }
+ }
+
+ /*
+ * This loop is run once for every individual HTTP request that gets sent.
+ * The very first HTTP request is a "virgin" request, while every subsequent
+ * request is done with the original ETag and a byte-range.
+ */
+http_request_loop:
+ while (true) {
+ // Prepares the request and fires it.
+ HttpGet request = new HttpGet(mInfo.uri);
+
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "initiating download for " + mInfo.uri);
+ }
+
+ if (mInfo.cookies != null) {
+ request.addHeader("Cookie", mInfo.cookies);
+ }
+ if (mInfo.referer != null) {
+ request.addHeader("Referer", mInfo.referer);
+ }
+ if (continuingDownload) {
+ if (headerETag != null) {
+ request.addHeader("If-Match", headerETag);
+ }
+ request.addHeader("Range", "bytes=" + bytesSoFar + "-");
+ }
+
+ HttpResponse response;
+ try {
+ response = client.execute(request);
+ } catch (IllegalArgumentException ex) {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "Arg exception trying to execute request for " +
+ mInfo.uri + " : " + ex);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "Arg exception trying to execute request for " +
+ mInfo.id + " : " + ex);
+ }
+ finalStatus = Downloads.STATUS_BAD_REQUEST;
+ request.abort();
+ break http_request_loop;
+ } catch (IOException ex) {
+ if (!Helpers.isNetworkAvailable(mContext)) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ countRetry = true;
+ } else {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "IOException trying to execute request for " +
+ mInfo.uri + " : " + ex);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "IOException trying to execute request for " +
+ mInfo.id + " : " + ex);
+ }
+ finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
+ }
+ request.abort();
+ break http_request_loop;
+ }
+
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 503 && mInfo.numFailed < Constants.MAX_RETRIES) {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "got HTTP response code 503");
+ }
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ countRetry = true;
+ Header header = response.getFirstHeader("Retry-After");
+ if (header != null) {
+ try {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "Retry-After :" + header.getValue());
+ }
+ retryAfter = Integer.parseInt(header.getValue());
+ if (retryAfter < 0) {
+ retryAfter = 0;
+ } else {
+ if (retryAfter < Constants.MIN_RETRY_AFTER) {
+ retryAfter = Constants.MIN_RETRY_AFTER;
+ } else if (retryAfter > Constants.MAX_RETRY_AFTER) {
+ retryAfter = Constants.MAX_RETRY_AFTER;
+ }
+ retryAfter += Helpers.rnd.nextInt(Constants.MIN_RETRY_AFTER + 1);
+ retryAfter *= 1000;
+ }
+ } catch (NumberFormatException ex) {
+ // ignored - retryAfter stays 0 in this case.
+ }
+ }
+ request.abort();
+ break http_request_loop;
+ }
+ if (statusCode == 301 ||
+ statusCode == 302 ||
+ statusCode == 303 ||
+ statusCode == 307) {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "got HTTP redirect " + statusCode);
+ }
+ if (redirectCount >= Constants.MAX_REDIRECTS) {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "too many redirects for download " + mInfo.id +
+ " at " + mInfo.uri);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "too many redirects for download " + mInfo.id);
+ }
+ finalStatus = Downloads.STATUS_TOO_MANY_REDIRECTS;
+ request.abort();
+ break http_request_loop;
+ }
+ Header header = response.getFirstHeader("Location");
+ if (header != null) {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "Location :" + header.getValue());
+ }
+ newUri = new URI(mInfo.uri).resolve(new URI(header.getValue())).toString();
+ ++redirectCount;
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ request.abort();
+ break http_request_loop;
+ }
+ }
+ if ((!continuingDownload && statusCode != Downloads.STATUS_SUCCESS)
+ || (continuingDownload && statusCode != 206)) {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "http error " + statusCode + " for " + mInfo.uri);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "http error " + statusCode + " for download " +
+ mInfo.id);
+ }
+ if (Downloads.isStatusError(statusCode)) {
+ finalStatus = statusCode;
+ } else if (statusCode >= 300 && statusCode < 400) {
+ finalStatus = Downloads.STATUS_UNHANDLED_REDIRECT;
+ } else if (continuingDownload && statusCode == Downloads.STATUS_SUCCESS) {
+ finalStatus = Downloads.STATUS_PRECONDITION_FAILED;
+ } else {
+ finalStatus = Downloads.STATUS_UNHANDLED_HTTP_CODE;
+ }
+ request.abort();
+ break http_request_loop;
+ } else {
+ // Handles the response, saves the file
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "received response for " + mInfo.uri);
+ }
+
+ if (!continuingDownload) {
+ Header header = response.getFirstHeader("Accept-Ranges");
+ if (header != null) {
+ headerAcceptRanges = header.getValue();
+ }
+ header = response.getFirstHeader("Content-Disposition");
+ if (header != null) {
+ headerContentDisposition = header.getValue();
+ }
+ header = response.getFirstHeader("Content-Location");
+ if (header != null) {
+ headerContentLocation = header.getValue();
+ }
+ if (mimeType == null) {
+ header = response.getFirstHeader("Content-Type");
+ if (header != null) {
+ mimeType = header.getValue();
+ final int semicolonIndex = mimeType.indexOf(';');
+ if (semicolonIndex != -1) {
+ mimeType = mimeType.substring(0, semicolonIndex);
+ }
+ }
+ }
+ header = response.getFirstHeader("ETag");
+ if (header != null) {
+ headerETag = header.getValue();
+ }
+ header = response.getFirstHeader("Transfer-Encoding");
+ if (header != null) {
+ headerTransferEncoding = header.getValue();
+ }
+ if (headerTransferEncoding == null) {
+ header = response.getFirstHeader("Content-Length");
+ if (header != null) {
+ headerContentLength = header.getValue();
+ }
+ } else {
+ // Ignore content-length with transfer-encoding - 2616 4.4 3
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG,
+ "ignoring content-length because of xfer-encoding");
+ }
+ }
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "Accept-Ranges: " + headerAcceptRanges);
+ Log.v(Constants.TAG, "Content-Disposition: " +
+ headerContentDisposition);
+ Log.v(Constants.TAG, "Content-Length: " + headerContentLength);
+ Log.v(Constants.TAG, "Content-Location: " + headerContentLocation);
+ Log.v(Constants.TAG, "Content-Type: " + mimeType);
+ Log.v(Constants.TAG, "ETag: " + headerETag);
+ Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding);
+ }
+
+ if (!mInfo.noIntegrity && headerContentLength == null &&
+ (headerTransferEncoding == null
+ || !headerTransferEncoding.equalsIgnoreCase("chunked"))
+ ) {
+ if (Config.LOGD) {
+ Log.d(Constants.TAG, "can't know size of download, giving up");
+ }
+ finalStatus = Downloads.STATUS_LENGTH_REQUIRED;
+ request.abort();
+ break http_request_loop;
+ }
+
+ DownloadFileInfo fileInfo = Helpers.generateSaveFile(
+ mContext,
+ mInfo.uri,
+ mInfo.hint,
+ headerContentDisposition,
+ headerContentLocation,
+ mimeType,
+ mInfo.destination,
+ (headerContentLength != null) ?
+ Integer.parseInt(headerContentLength) : 0);
+ if (fileInfo.filename == null) {
+ finalStatus = fileInfo.status;
+ request.abort();
+ break http_request_loop;
+ }
+ filename = fileInfo.filename;
+ stream = fileInfo.stream;
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "writing " + mInfo.uri + " to " + filename);
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(Downloads._DATA, filename);
+ if (headerETag != null) {
+ values.put(Constants.ETAG, headerETag);
+ }
+ if (mimeType != null) {
+ values.put(Downloads.MIMETYPE, mimeType);
+ }
+ int contentLength = -1;
+ if (headerContentLength != null) {
+ contentLength = Integer.parseInt(headerContentLength);
+ }
+ values.put(Downloads.TOTAL_BYTES, contentLength);
+ mContext.getContentResolver().update(contentUri, values, null, null);
+ }
+
+ InputStream entityStream;
+ try {
+ entityStream = response.getEntity().getContent();
+ } catch (IOException ex) {
+ if (!Helpers.isNetworkAvailable(mContext)) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ countRetry = true;
+ } else {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "IOException getting entity for " + mInfo.uri +
+ " : " + ex);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "IOException getting entity for download " +
+ mInfo.id + " : " + ex);
+ }
+ finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
+ }
+ request.abort();
+ break http_request_loop;
+ }
+ for (;;) {
+ int bytesRead;
+ try {
+ bytesRead = entityStream.read(data);
+ } catch (IOException ex) {
+ ContentValues values = new ContentValues();
+ values.put(Downloads.CURRENT_BYTES, bytesSoFar);
+ mContext.getContentResolver().update(contentUri, values, null, null);
+ if (!mInfo.noIntegrity && headerETag == null) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "download IOException for " + mInfo.uri +
+ " : " + ex);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "download IOException for download " +
+ mInfo.id + " : " + ex);
+ }
+ if (Config.LOGD) {
+ Log.d(Constants.TAG,
+ "can't resume interrupted download with no ETag");
+ }
+ finalStatus = Downloads.STATUS_PRECONDITION_FAILED;
+ } else if (!Helpers.isNetworkAvailable(mContext)) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ countRetry = true;
+ } else {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "download IOException for " + mInfo.uri +
+ " : " + ex);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "download IOException for download " +
+ mInfo.id + " : " + ex);
+ }
+ finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
+ }
+ request.abort();
+ break http_request_loop;
+ }
+ if (bytesRead == -1) { // success
+ ContentValues values = new ContentValues();
+ values.put(Downloads.CURRENT_BYTES, bytesSoFar);
+ if (headerContentLength == null) {
+ values.put(Downloads.TOTAL_BYTES, bytesSoFar);
+ }
+ mContext.getContentResolver().update(contentUri, values, null, null);
+ if ((headerContentLength != null)
+ && (bytesSoFar
+ != Integer.parseInt(headerContentLength))) {
+ if (!mInfo.noIntegrity && headerETag == null) {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "mismatched content length " +
+ mInfo.uri);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "mismatched content length for " +
+ mInfo.id);
+ }
+ finalStatus = Downloads.STATUS_LENGTH_REQUIRED;
+ } else if (!Helpers.isNetworkAvailable(mContext)) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ } else if (mInfo.numFailed < Constants.MAX_RETRIES) {
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ countRetry = true;
+ } else {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "closed socket for " + mInfo.uri);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "closed socket for download " +
+ mInfo.id);
+ }
+ finalStatus = Downloads.STATUS_HTTP_DATA_ERROR;
+ }
+ break http_request_loop;
+ }
+ break;
+ }
+ gotData = true;
+ for (;;) {
+ try {
+ if (stream == null) {
+ stream = new FileOutputStream(filename, true);
+ }
+ stream.write(data, 0, bytesRead);
+ if (mInfo.destination == Downloads.DESTINATION_EXTERNAL
+ && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
+ .equalsIgnoreCase(mimeType)) {
+ try {
+ stream.close();
+ stream = null;
+ } catch (IOException ex) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG,
+ "exception when closing the file " +
+ "during download : " + ex);
+ }
+ // nothing can really be done if the file can't be closed
+ }
+ }
+ break;
+ } catch (IOException ex) {
+ if (!Helpers.discardPurgeableFiles(
+ mContext, Constants.BUFFER_SIZE)) {
+ finalStatus = Downloads.STATUS_FILE_ERROR;
+ break http_request_loop;
+ }
+ }
+ }
+ bytesSoFar += bytesRead;
+ long now = System.currentTimeMillis();
+ if (bytesSoFar - bytesNotified > Constants.MIN_PROGRESS_STEP
+ && now - timeLastNotification
+ > Constants.MIN_PROGRESS_TIME) {
+ ContentValues values = new ContentValues();
+ values.put(Downloads.CURRENT_BYTES, bytesSoFar);
+ mContext.getContentResolver().update(
+ contentUri, values, null, null);
+ bytesNotified = bytesSoFar;
+ timeLastNotification = now;
+ }
+
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "downloaded " + bytesSoFar + " for " + mInfo.uri);
+ }
+ synchronized(mInfo) {
+ if (mInfo.control == Downloads.CONTROL_PAUSED) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "paused " + mInfo.uri);
+ }
+ finalStatus = Downloads.STATUS_RUNNING_PAUSED;
+ request.abort();
+ break http_request_loop;
+ }
+ }
+ if (mInfo.status == Downloads.STATUS_CANCELED) {
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "canceled " + mInfo.uri);
+ } else if (Config.LOGD) {
+ // Log.d(Constants.TAG, "canceled id " + mInfo.id);
+ }
+ finalStatus = Downloads.STATUS_CANCELED;
+ break http_request_loop;
+ }
+ }
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "download completed for " + mInfo.uri);
+ }
+ finalStatus = Downloads.STATUS_SUCCESS;
+ }
+ break;
+ }
+ } catch (FileNotFoundException ex) {
+ if (Config.LOGD) {
+ Log.d(Constants.TAG, "FileNotFoundException for " + filename + " : " + ex);
+ }
+ finalStatus = Downloads.STATUS_FILE_ERROR;
+ // falls through to the code that reports an error
+ } catch (Exception ex) { //sometimes the socket code throws unchecked exceptions
+ if (Constants.LOGV) {
+ Log.d(Constants.TAG, "Exception for " + mInfo.uri, ex);
+ } else if (Config.LOGD) {
+ Log.d(Constants.TAG, "Exception for id " + mInfo.id, ex);
+ }
+ finalStatus = Downloads.STATUS_UNKNOWN_ERROR;
+ // falls through to the code that reports an error
+ } finally {
+ mInfo.hasActiveThread = false;
+ if (wakeLock != null) {
+ wakeLock.release();
+ wakeLock = null;
+ }
+ if (client != null) {
+ client.close();
+ client = null;
+ }
+ try {
+ // close the file
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException ex) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "exception when closing the file after download : " + ex);
+ }
+ // nothing can really be done if the file can't be closed
+ }
+ if (filename != null) {
+ // if the download wasn't successful, delete the file
+ if (Downloads.isStatusError(finalStatus)) {
+ new File(filename).delete();
+ filename = null;
+ } else if (Downloads.isStatusSuccess(finalStatus) &&
+ DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING
+ .equalsIgnoreCase(mimeType)) {
+ // transfer the file to the DRM content provider
+ File file = new File(filename);
+ Intent item = DrmStore.addDrmFile(mContext.getContentResolver(), file, null);
+ if (item == null) {
+ Log.w(Constants.TAG, "unable to add file " + filename + " to DrmProvider");
+ finalStatus = Downloads.STATUS_UNKNOWN_ERROR;
+ } else {
+ filename = item.getDataString();
+ mimeType = item.getType();
+ }
+
+ file.delete();
+ } else if (Downloads.isStatusSuccess(finalStatus)) {
+ // make sure the file is readable
+ FileUtils.setPermissions(filename, 0644, -1, -1);
+ }
+ }
+ notifyDownloadCompleted(finalStatus, countRetry, retryAfter, redirectCount,
+ gotData, filename, newUri, mimeType);
+ }
+ }
+
+ /**
+ * Stores information about the completed download, and notifies the initiating application.
+ */
+ private void notifyDownloadCompleted(
+ int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
+ String filename, String uri, String mimeType) {
+ notifyThroughDatabase(
+ status, countRetry, retryAfter, redirectCount, gotData, filename, uri, mimeType);
+ if (Downloads.isStatusCompleted(status)) {
+ notifyThroughIntent();
+ }
+ }
+
+ private void notifyThroughDatabase(
+ int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
+ String filename, String uri, String mimeType) {
+ ContentValues values = new ContentValues();
+ values.put(Downloads.STATUS, status);
+ values.put(Downloads._DATA, filename);
+ if (uri != null) {
+ values.put(Downloads.URI, uri);
+ }
+ values.put(Downloads.MIMETYPE, mimeType);
+ values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
+ values.put(Constants.RETRY_AFTER___REDIRECT_COUNT, retryAfter + (redirectCount << 28));
+ if (!countRetry) {
+ values.put(Constants.FAILED_CONNECTIONS, 0);
+ } else if (gotData) {
+ values.put(Constants.FAILED_CONNECTIONS, 1);
+ } else {
+ values.put(Constants.FAILED_CONNECTIONS, mInfo.numFailed + 1);
+ }
+
+ mContext.getContentResolver().update(
+ ContentUris.withAppendedId(Downloads.CONTENT_URI, mInfo.id), values, null, null);
+ }
+
+ /**
+ * Notifies the initiating app if it requested it. That way, it can know that the
+ * download completed even if it's not actively watching the cursor.
+ */
+ private void notifyThroughIntent() {
+ Uri uri = Uri.parse(Downloads.CONTENT_URI + "/" + mInfo.id);
+ mInfo.sendIntentIfRequested(uri, mContext);
+ }
+
+}