FileDocCategorySizeDatePackage
DownloadThread.javaAPI DocAndroid 1.5 API35409Wed May 06 22:42:48 BST 2009com.android.providers.downloads

DownloadThread.java

/*
 * 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;
import java.util.Locale;

/**
 * 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 = sanitizeMimeType(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 = sanitizeMimeType(header.getValue()); 
                            }
                        }
                        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);
    }

    /**
     * Clean up a mimeType string so it can be used to dispatch an intent to
     * view a downloaded asset.
     * @param mimeType either null or one or more mime types (semi colon separated).
     * @return null if mimeType was null. Otherwise a string which represents a
     * single mimetype in lowercase and with surrounding whitespaces trimmed.
     */
    private String sanitizeMimeType(String mimeType) {
        try {
            mimeType = mimeType.trim().toLowerCase(Locale.ENGLISH);

            final int semicolonIndex = mimeType.indexOf(';');
            if (semicolonIndex != -1) {
                mimeType = mimeType.substring(0, semicolonIndex);
            }
            return mimeType;
        } catch (NullPointerException npe) {
            return null;
        }
    }
}