[android] android源码中的下载线程的实现

/*
 * 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 static com.android.providers.downloads.Constants.TAG;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
//import android.drm.DrmManagerClient;
import android.drm.DrmRights;
//import android.drm.DrmUtils;
import com.mediatek.drm.OmaDrmClient;
import android.media.MediaScannerConnection;
import android.net.INetworkPolicyListener;
import android.net.NetworkPolicyManager;
import android.net.Proxy;
import android.net.TrafficStats;
import android.net.WebAddress;
import android.net.http.HttpAuthHeader;
import android.os.FileUtils;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
import android.provider.Downloads;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;

import com.mediatek.downloadmanager.ext.Extensions;
import com.mediatek.downloadmanager.ext.IDownloadProviderFeatureEx;
import com.mediatek.xlog.Xlog;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SyncFailedException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Locale;

/**
 * Runs an actual download
 */
public class DownloadThread extends Thread {

    private IDownloadProviderFeatureEx mDownloadProviderFeatureEx;
    private final Context mContext;
    private final DownloadInfo mInfo;
    private final SystemFacade mSystemFacade;
    private final StorageManager mStorageManager;
    private DrmConvertSession mDrmConvertSession;
    private boolean mDownloadAlreadyCompleted;

    private volatile boolean mPolicyDirty;
    private static final String TAG = "DownloadThread";
   
    /// M: Add for fix GMS low memory issue 332710. @{
    private static final String PLAY_STORE_RECEIVER = "com.google.android.finsky."
            + "download.DownloadBroadcastReceiver";
    private static final String PLAY_STORE_CLASS = "com.android.vending";
    /// @}
   
    public DownloadThread(Context context, SystemFacade systemFacade, DownloadInfo info,
            StorageManager storageManager) {
        mContext = context;
        mSystemFacade = systemFacade;
        mInfo = info;
        mStorageManager = storageManager;
    }

    /**
     * Returns the user agent provided by the initiating app, or use the default one
     */
    private String userAgent() {
        String userAgent = mInfo.mUserAgent;
        if (userAgent == null) {
            userAgent = Constants.DEFAULT_USER_AGENT;
        }
        return userAgent;
    }

    /**
     * State for the entire run() method.
     */
    static class State {
        public String mFilename;
        public FileOutputStream mStream;
        public String mMimeType;
        public boolean mCountRetry = false;
        public int mRetryAfter = 0;
        public int mRedirectCount = 0;
        public String mNewUri;
        public boolean mGotData = false;
        public String mRequestUri;
        public long mTotalBytes = -1;
        public long mCurrentBytes = 0;
        public String mHeaderETag;
        public boolean mContinuingDownload = false;
        public long mBytesNotified = 0;
        public long mTimeLastNotification = 0;

        /** Historical bytes/second speed of this download. */
        public long mSpeed;
        /** Time when current sample started. */
        public long mSpeedSampleStart;
        /** Bytes transferred since current sample started. */
        public long mSpeedSampleBytes;

        // M: Add to support OMA download
        public int mOmaDownload;
        public int mOmaDownloadStatus;
        public String mOmaDownloadInsNotifyUrl;

        // M: Add to support DRM
        public long mTotalWriteBytes = 0;

        public State(DownloadInfo info) {
            mMimeType = Intent.normalizeMimeType(info.mMimeType);
            mRequestUri = info.mUri;
            mFilename = info.mFileName;
            mTotalBytes = info.mTotalBytes;
            mCurrentBytes = info.mCurrentBytes;
           
            // Add to support OMA download
            mOmaDownload = info.mOmaDownload;
            mOmaDownloadStatus = info.mOmaDownloadStatus;
            mOmaDownloadInsNotifyUrl = info.mOmaDownloadInsNotifyUrl;
        }
    }

    /**
     * State within executeDownload()
     */
    private static class InnerState {
        public String mHeaderContentLength;
        public String mHeaderContentDisposition;
        public String mHeaderContentLocation;
        // M: Add to support Authenticate download
        public int mAuthScheme = HttpAuthHeader.UNKNOWN;
        public HttpAuthHeader mAuthHeader = null;
        public String mHost = null;
        public boolean mIsAuthNeeded = false;
        // M: As description on HttpHost, -1 means default port
        public int mPort = -1;
        public String mScheme = null;
    }

    /**
     * Raised from methods called by executeDownload() to indicate that the download should be
     * retried immediately.
     */
    private class RetryDownload extends Throwable {}

    /**
     * Executes the download in a separate thread
     */
    @Override
    public void run() {
        Log.i(TAG, "start run download thread");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        try {
            runInternal();
        } finally {
            DownloadHandler.getInstance().dequeueDownload(mInfo.mId);
        }
    }
   
    private void runInternal() {
        // Skip when download already marked as finished; this download was
        // probably started again while racing with UpdateThread.
        if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mInfo.mId)
                == Downloads.Impl.STATUS_SUCCESS) {
            Log.d(TAG, "Download " + mInfo.mId + " already finished; skipping");
            return;
        }

        mDownloadAlreadyCompleted = false;
        State state = new State(mInfo);
        // Modify to support Authenticate download
        // AndroidHttpClient client = null;
        DefaultHttpClient client = null;
        PowerManager.WakeLock wakeLock = null;
        int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
        String errorMsg = null;

        final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext);
        final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);

        try {
            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
            wakeLock.acquire();

            // M: while performing download, register for rules updates
            netPolicy.registerListener(mPolicyListener);

            if (Constants.LOGV) {
                Log.v(Constants.TAG, "initiating download for " + mInfo.mUri);
            }

            // M: Modify to support Authenticate download
            // client = AndroidHttpClient.newInstance(userAgent(), mContext);
            HttpParams params = new BasicHttpParams();
            // As AndroidHttpClient, set these params
            HttpConnectionParams.setStaleCheckingEnabled(params, false);
            HttpConnectionParams.setConnectionTimeout(params, 60 * 1000);

            /// M: Operator Feature set the socket time out to 30S;
            /// check whether need notify if the file already exsit. @{
            mDownloadProviderFeatureEx = Extensions.getDefault(mContext);
            mDownloadProviderFeatureEx.setHttpSocketTimeOut(params, 60 * 1000);
           
            HttpConnectionParams.setSocketBufferSize(params, 8192);
            HttpProtocolParams.setUserAgent(params, userAgent());
            HttpClientParams.setRedirecting(params, false);
            client = new DefaultHttpClient(params);
           
            // network traffic on this thread should be counted against the
            // requesting uid, and is tagged with well-known value.
            TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_DOWNLOAD);
            TrafficStats.setThreadStatsUid(mInfo.mUid);

            boolean finished = false;
            while(!finished) {
                Log.i(Constants.TAG, "Initiating request for download " + mInfo.mId +
                        /// M: add thread id in log @{
                        ",currentThread id: " + Thread.currentThread().getId());
                        /// @}
                // Set or unset proxy, which may have changed since last GET request.
                // setDefaultProxy() supports null as proxy parameter.
                ConnRouteParams.setDefaultProxy(client.getParams(),
                        Proxy.getPreferredHttpHost(mContext, state.mRequestUri));
                HttpGet request = new HttpGet(state.mRequestUri);
                try {
                    executeDownload(state, client, request);
                    finished = true;
                } catch (RetryDownload exc) {
                    // fall through
                } finally {
                    request.abort();
                    request = null;
                }
            }
           
            // M: Add this to support OMA DL.
            // Need to before Handle DRM. Because if install notify failed,
            // the Media object will be discard
            if (Downloads.Impl.MTK_OMA_DOWNLOAD_SUPPORT) {
                handleOmaDownloadMediaObject(state);
            }

            if (Constants.LOGV) {
                Log.v(Constants.TAG, "download completed for " + mInfo.mUri);
            }
            finalizeDestinationFile(state);

            /// M: Add this to support OMA DL.
            /// Deal with .dd file
            if (Downloads.Impl.MTK_OMA_DOWNLOAD_SUPPORT) {
                handleOmaDownloadDescriptorFile(state);
            }
           
            finalStatus = Downloads.Impl.STATUS_SUCCESS;
            Xlog.i(Constants.DL_ENHANCE, "Download success" + mInfo.mUri + ",mInfo.mId: "
                    + mInfo.mId + ",currentThread id: " + Thread.currentThread().getId());
        } catch (StopRequestException error) {
            // remove the cause before printing, in case it contains PII
            errorMsg = error.getMessage();
            String msg = "Aborting request for download " + mInfo.mId +
                    /// M: add thread id in log @{
                    ",currentThread id: " + Thread.currentThread().getId() +
                    /// @}
                    ",: " + errorMsg;
            Log.w(Constants.TAG, msg);
            if (Constants.LOGV) {
                Log.w(Constants.TAG, msg, error);
            }
            finalStatus = error.mFinalStatus;
            // fall through to finally block
            mDownloadProviderFeatureEx = Extensions.getDefault(mContext);
            if (mDownloadProviderFeatureEx.shouldNotifyFileAlreadyExist()) {
                if (finalStatus == Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR) {
                    mInfo.notifyFileAlreadyExist(errorMsg);
                }
            }
            /// @}
           
            /// M: Add for support OMA Download
            /// Notify to web server if failed @{
            if (Downloads.Impl.MTK_OMA_DOWNLOAD_SUPPORT
                    && ((errorMsg != null && errorMsg.equals(Downloads.Impl.OMADL_OCCUR_ERROR_NEED_NOTIFY))
                            || Downloads.Impl.isStatusError(finalStatus))
                    && state.mOmaDownload == 1 && state.mOmaDownloadInsNotifyUrl != null) {
                int notifyCode = OmaStatusHandler.SUCCESS;
                URL notifyUrl = null;
                try {
                    notifyUrl = new URL(state.mOmaDownloadInsNotifyUrl);
                } catch (MalformedURLException e) {
                    // TODO:need error handling
                    // There will update OMA_Download_Status, or the query will reuse
                    Xlog.e(Constants.LOG_OMA_DL, "DownloadThread: New notify url failed" + state.mOmaDownloadInsNotifyUrl);
                }
                switch (state.mOmaDownloadStatus) {
                    case Downloads.Impl.OMADL_STATUS_ERROR_INVALID_DESCRIPTOR:
                        notifyCode = OmaStatusHandler.INVALID_DESCRIPTOR;
                        break;
                    case Downloads.Impl.OMADL_STATUS_ERROR_ATTRIBUTE_MISMATCH:
                        notifyCode = OmaStatusHandler.ATTRIBUTE_MISMATCH;
                        break;
                    case Downloads.Impl.OMADL_STATUS_ERROR_INSUFFICIENT_MEMORY:
                        notifyCode = OmaStatusHandler.INSUFFICIENT_MEMORY;
                        break;
                    case Downloads.Impl.OMADL_STATUS_ERROR_INVALID_DDVERSION:
                        notifyCode = OmaStatusHandler.INVALID_DDVERSION;
                        break;
                    default:
                        //notifyCode = OmaStatusHandler.DEVICE_ABORTED;
                        notifyCode = OmaStatusHandler.LOADER_ERROR;
                        break;
                }
               
                notifyOMADownloadWebServerErrorStatus(notifyUrl, notifyCode);
            }
            /// @}
        } catch (Throwable ex) { //sometimes the socket code throws unchecked exceptions
            errorMsg = ex.getMessage();
            String msg = "Exception for id " + mInfo.mId + ": " + errorMsg;
            Log.w(Constants.TAG, msg, ex);
            /// M: falls through to the code that reports an error @{
            if (Downloads.Impl.MTK_OMA_DOWNLOAD_SUPPORT && state.mOmaDownload == 1
                    && state.mOmaDownloadInsNotifyUrl != null) {
                URL notifyUrl = null;
                try {
                    notifyUrl = new URL(state.mOmaDownloadInsNotifyUrl);
                } catch (MalformedURLException e) {
                    // TODO:need error handling
                    // There will update OMA_Download_Status, or the query will reuse
                    Xlog.e(Constants.LOG_OMA_DL, "DownloadThread: New notify url failed" + state.mOmaDownloadInsNotifyUrl);
                }
                notifyOMADownloadWebServerErrorStatus(notifyUrl, OmaStatusHandler.LOADER_ERROR);
            }
            /// @}
            finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
            // falls through to the code that reports an error
        } finally {
            TrafficStats.clearThreadStatsTag();
            TrafficStats.clearThreadStatsUid();

            if (client != null) {
                /// M: Modify to support Authenticate download
                // client.close();
                client.getConnectionManager().shutdown();
                client = null;
            }
            Xlog.d(Constants.DL_ENHANCE, "before cleanupDestination(), finalStauts : " + finalStatus);
            cleanupDestination(state, finalStatus);
            notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter,
                                    state.mGotData, state.mFilename,
                                    state.mNewUri, state.mMimeType, errorMsg);
           
            /// M: Add for fix GMS low memory issue 332710. @{
            Xlog.d(Constants.DL_ENHANCE, "after notifyDownloadCompleted"
                    + " mInfo.mClass is: " + mInfo.mClass + " mInfo.mPackage "
                    + mInfo.mPackage + ",after cleanupDestination(), finalStatus: " + finalStatus
                    + " ,now state.mFilename = " + state.mFilename);
            if (finalStatus == Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR
                    && mInfo.mClass != null && mInfo.mPackage != null
                    && mInfo.mClass.equalsIgnoreCase(PLAY_STORE_RECEIVER)
                    && mInfo.mPackage.equalsIgnoreCase(PLAY_STORE_CLASS)) {
                mInfo.sendIntentIfRequested();
            }
            /// @}
           

            netPolicy.unregisterListener(mPolicyListener);

            if (wakeLock != null) {
                wakeLock.release();
                wakeLock = null;
            }
        }
        mStorageManager.incrementNumDownloadsSoFar();
        mDownloadAlreadyCompleted = false;
    }

    /**
     * M: This function is used to notify webserver
     * oma download error status
     */
    private void notifyOMADownloadWebServerErrorStatus(URL notifyUrl, int notifyCode) {
        if (notifyUrl != null) {
            Xlog.i(Constants.LOG_OMA_DL, "DownloadThread: catch StopRequest and need to notify web server: " +
                    notifyUrl.toString() + " and Notify code is:" + notifyCode);
            OmaDescription omaDescription = new OmaDescription();
            omaDescription.setInstallNotifyUrl(notifyUrl);
            omaDescription.setStatusCode(notifyCode);
            if (OmaDownload.installNotify(omaDescription, null) != OmaStatusHandler.READY) {
                Xlog.d(Constants.LOG_OMA_DL, "DownloadThread: catch StopRequest but notify URL : " +
                        "" + notifyUrl + " failed");
            } else {
                Xlog.d(Constants.LOG_OMA_DL, "DownloadThread: catch StopRequest and notify URL OK");
            }
        }
    }

    /**
     * M: After download complete, Check whether OMA DL or not.
     * Deal with OMA DL file (install Notify and next url)
     *
     */
    private void handleOmaDownloadMediaObject(State state) throws StopRequestException {   
        if (state.mOmaDownload != 1 || state.mMimeType.equalsIgnoreCase("application/vnd.oma.dd+xml")) {
            return;
        }
        state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_DOWNLOAD_COMPLETELY;
        ContentValues values = new ContentValues();

        if (state.mOmaDownloadInsNotifyUrl != null) {
            Xlog.i(Constants.LOG_OMA_DL, "Handle Media object, notify URL is: " +
                    state.mOmaDownloadInsNotifyUrl);
            URL notifyUrl = null;
            try {
                notifyUrl = new URL(state.mOmaDownloadInsNotifyUrl);
            } catch (MalformedURLException e) {    
                values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_STATUS,
                        Downloads.Impl.OMADL_STATUS_ERROR_INSTALL_FAILED);
                mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
               
                state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_ERROR_INSTALL_FAILED;
               
                // There will update OMA_Download_Status, or the query will reuse
                Xlog.e(Constants.LOG_OMA_DL, "DownloadThread: handleOmaDownloadMediaObject(): " +
                        "New url failed" + state.mOmaDownloadInsNotifyUrl);
                throw new StopRequestException(Downloads.Impl.STATUS_UNKNOWN_ERROR,
                        "OMA Download Installation Media Object Failure");
            }
           
            OmaDescription omaDescription = new OmaDescription();
            omaDescription.setInstallNotifyUrl(notifyUrl);
            omaDescription.setStatusCode(OmaStatusHandler.SUCCESS);
            if (OmaDownload.installNotify(omaDescription, null) != OmaStatusHandler.READY) {
                values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_STATUS,
                        Downloads.Impl.OMADL_STATUS_ERROR_INSTALL_FAILED);
                mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
               
                state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_ERROR_INSTALL_FAILED;
                throw new StopRequestException(Downloads.Impl.STATUS_UNKNOWN_ERROR,
                        "OMA Download Installation Media Object Failure");
            }
            Xlog.i(Constants.LOG_OMA_DL, "Handle Media object, after notify URL");
        }

        if (mInfo.mOmaDownloadNextUrl != null) {
            Xlog.d(Constants.LOG_OMA_DL, "DownloadThread:handleOmaDownloadMediaObject(): " +
                    "next url is: " + mInfo.mOmaDownloadNextUrl);
            values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
            //mInfo.notifyOmaDownloadNextUrl(mInfo.mOmaDownloadNextUrl);
            values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_FLAG, 1);
            // Download the Media Object success and install notify success and need to show user next URL
            values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_STATUS,
                    Downloads.Impl.OMADL_STATUS_HAS_NEXT_URL);
            mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
        }
    }
   
    /**
     * M: Add this function to support OMA DL
     * This function is used to handle .dd file
     */
    private void handleOmaDownloadDescriptorFile (State state) throws StopRequestException {
        // Handle .dd file
        if (state.mMimeType != null) {
            if (state.mMimeType.equals("application/vnd.oma.dd+xml")) {            
                state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_DOWNLOAD_COMPLETELY;
                File ddFile = new File(state.mFilename);
                URL ddUrl = null;
                try {
                    ddUrl = new URL(state.mRequestUri);
                } catch (MalformedURLException e) {
                    // TODO:need error handling and update UI
                    // There will update OMA_Download_Status, or the query will reuse
                    Xlog.e(Constants.LOG_OMA_DL, "DownloadThread: handleOmaDescriptorFile():" +
                            "New url failed" + state.mRequestUri);
                }
                Xlog.i(Constants.LOG_OMA_DL, "DownloadThread: handleOmaDescriptorFile(): "
                        + "URL is " + ddUrl + "file path is " + ddFile);
               
                if (ddFile != null && ddUrl != null) {
                    OmaDescription omaDescription = new OmaDescription();
                    int parseStatus = OmaDownload.parseXml(ddUrl, ddFile, omaDescription);
                   
                    ContentValues values = new ContentValues();
                    if (omaDescription != null && parseStatus == OmaStatusHandler.SUCCESS) {
                        // Update downloads.db
                        // Show this is OMA DL
                        values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS);
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_FLAG, 1);
                        // Update the parse status to success
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_STATUS,
                                Downloads.Impl.OMADL_STATUS_PARSE_DDFILE_SUCCESS);
                        // Update the info. This info will show to user
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_DD_FILE_INFO_NAME,
                                omaDescription.getName());
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_DD_FILE_INFO_VENDOR,
                                omaDescription.getVendor());
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_DD_FILE_INFO_SIZE,
                                omaDescription.getSize());
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_DD_FILE_INFO_TYPE,
                                omaDescription.getType().get(0));
                        Xlog.d(Constants.LOG_OMA_DL, "DownloadThread: handleOmaDownloadDescriptorFile(): " +
                                "dd file's mimtType is :" + omaDescription.getType().get(0));
                       
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_DD_FILE_INFO_DESCRIPTION,
                                omaDescription.getDescription());
 
                        if (omaDescription.getObjectUrl() != null) {
                            Xlog.d(Constants.LOG_OMA_DL, "DownloadThread: handleOmaDownloadDescriptorFile(): " +
                                    "dd file's object url :" + omaDescription.getObjectUrl().toString());
                            values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_OBJECT_URL,
                                    omaDescription.getObjectUrl().toString());
                        }
                        if (omaDescription.getNextUrl() != null) {
                            values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_NEXT_URL,
                                    omaDescription.getNextUrl().toString());
                            mInfo.mOmaDownloadNextUrl = omaDescription.getNextUrl().toString();
                        }
                        if (omaDescription.getInstallNotifyUrl() != null) {
                            values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_INSTALL_NOTIFY_URL,
                                    omaDescription.getInstallNotifyUrl().toString());
                            state.mOmaDownloadInsNotifyUrl = omaDescription.getInstallNotifyUrl().toString();
                        }
                        mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
                       
                        // Note: these class members maybe change to DownloadThread class's local variable.
                        // So, the values can not be modified by DownloadService's updateDownload function.
                        state.mOmaDownload = 1;
                        state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_PARSE_DDFILE_SUCCESS;
                       
                    } else {
                        Xlog.w(Constants.LOG_OMA_DL, "DownloadThread: handleOmaDownloadDescriptorFile(): " +
                                "parse .dd file failed, error is: " + parseStatus);
                       
                        // Update database
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_FLAG, 1);
                        values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_STATUS, parseStatus);
                        if (omaDescription.getInstallNotifyUrl() != null) {
                            values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_INSTALL_NOTIFY_URL,
                                    omaDescription.getInstallNotifyUrl().toString());
                        }
                        mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
                       
                        //Need to install notify
                        state.mOmaDownload = 1;
                        if (omaDescription.getInstallNotifyUrl() != null) {
                            state.mOmaDownloadInsNotifyUrl = omaDescription.getInstallNotifyUrl().toString();
                        }
                        if (parseStatus == OmaStatusHandler.INVALID_DDVERSION) {
                            state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_ERROR_INVALID_DDVERSION;
                            throw new StopRequestException(Downloads.Impl.OMADL_STATUS_ERROR_INVALID_DDVERSION,
                                    Downloads.Impl.OMADL_OCCUR_ERROR_NEED_NOTIFY);
                        } else {
                            state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_ERROR_INVALID_DESCRIPTOR;
                            throw new StopRequestException(Downloads.Impl.STATUS_BAD_REQUEST,
                                    Downloads.Impl.OMADL_OCCUR_ERROR_NEED_NOTIFY);
                        }   
                    }
                }
            }
        }
       
    }
   
   
    /**
     * Fully execute a single download request - setup and send the request, handle the response,
     * and transfer the data to the destination file.
     */
    //private void executeDownload(State state, AndroidHttpClient client, HttpGet request)
    private void executeDownload(State state, DefaultHttpClient client, HttpGet request)
            throws StopRequestException, RetryDownload {
        InnerState innerState = new InnerState();
        byte data[] = new byte[Constants.BUFFER_SIZE];
        HttpResponse response = null;

        setupDestinationFile(state, innerState);
        addRequestHeaders(state, request);

        // skip when already finished; remove after fixing race in 5217390
        if (state.mCurrentBytes == state.mTotalBytes) {
            mDownloadAlreadyCompleted = true;
            Log.i(Constants.TAG, "Skipping initiating request for download " +
                  mInfo.mId + "; already completed");
            return;
        }

        // check just before sending the request to avoid using an invalid connection at all
        checkConnectivity();

        /// M: set innerState and other params. @{
        do {
            innerState.mIsAuthNeeded = false;
            // Add to support Authenticate download
            BasicHttpContext localcontext = new BasicHttpContext();
            WebAddress webAddress = new WebAddress(state.mRequestUri);
            innerState.mHost = webAddress.getHost();
           
            innerState.mPort = webAddress.getPort();
            innerState.mScheme = webAddress.getScheme();
            if (mInfo.mUsername != null || mInfo.mPassword != null) {
                Xlog.d(Constants.DL_ENHANCE, "DownloadThread:executeLoad: " +
                        "do-while loop: mInfo.mUsername is " + mInfo.mUsername
                        + " mInfo.mPassword is " + mInfo.mPassword);
                if (innerState.mAuthScheme != HttpAuthHeader.UNKNOWN && innerState.mAuthHeader != null) {
                   
                    client.getCredentialsProvider().setCredentials(
                            new AuthScope(innerState.mHost, -1),
                            new UsernamePasswordCredentials(mInfo.mUsername, mInfo.mPassword));
   
                    if (innerState.mAuthScheme == HttpAuthHeader.BASIC) {
                        // Generate BASIC scheme object and stick it to the
                        // local execution context
                        BasicScheme basicAuth = new BasicScheme();
                        localcontext.setAttribute("preemptive-auth", basicAuth);
   
                        // Add as the first request interceptor
                        client.addRequestInterceptor(new DownloadInfo.PreemptiveAuth(), 0);
                        Xlog.d(Constants.DL_ENHANCE, "Add basic interceptor for BASIC auth scheme ");
                    } else if (innerState.mAuthScheme == HttpAuthHeader.DIGEST) {
                        // Generate DIGEST scheme object, initialize it and
                        // stick it to
                        // the local execution context
                        DigestScheme digestAuth = new DigestScheme();
   
                        // set realm
                        String realm = innerState.mAuthHeader.getRealm();
                        digestAuth.overrideParamter("realm", realm);
                        // set nonce
                        String nonce = innerState.mAuthHeader.getNonce();
                        digestAuth.overrideParamter("nonce", nonce);
                        // set qop
                        String qop = innerState.mAuthHeader.getQop();
                        digestAuth.overrideParamter("qop", qop);
                        // set algorithm
                        String algorithm = innerState.mAuthHeader.getAlgorithm();
                        digestAuth.overrideParamter("algorithm", algorithm);
                        // set opaque
                        String opaque = innerState.mAuthHeader.getOpaque();
                        digestAuth.overrideParamter("opaque", opaque);
   
                        localcontext.setAttribute("preemptive-auth", digestAuth);
   
                        // Add as the first request interceptor
                        client.addRequestInterceptor(new DownloadInfo.PreemptiveAuth(), 0);
                        // Add as the last response interceptor
                        client.addResponseInterceptor(new DownloadInfo.PersistentDigest());
                        Xlog.d(Constants.DL_ENHANCE, "Add digest interceptor for DIGEST auth scheme ");
                    }
                }
            }
   
            // HttpResponse response = sendRequest(state, client, request);
            // Modify to support Authenticate download
            response = sendRequest(state, client, request, innerState, localcontext);
            handleExceptionalStatus(state, innerState, response);
        } while (innerState.mIsAuthNeeded);
        /// @}
        if (Constants.LOGV) {
            Log.v(Constants.TAG, "received response for " + mInfo.mUri);
        }

        processResponseHeaders(state, innerState, response);
        InputStream entityStream = openResponseEntity(state, response);
        transferData(state, innerState, data, entityStream);
        /// M: Add to support DRM
        state.mTotalWriteBytes = state.mCurrentBytes;
    }

    /**
     * Check if current connectivity is valid for this request.
     */
    private void checkConnectivity() throws StopRequestException {
        // checking connectivity will apply current policy
        mPolicyDirty = false;

        int networkUsable = mInfo.checkCanUseNetwork();
        if (networkUsable != DownloadInfo.NETWORK_OK) {
            int status = Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
            if (networkUsable == DownloadInfo.NETWORK_UNUSABLE_DUE_TO_SIZE) {
                status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
                mInfo.notifyPauseDueToSize(true);
            } else if (networkUsable == DownloadInfo.NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE) {
                status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
                mInfo.notifyPauseDueToSize(false);
            }
            throw new StopRequestException(status,
                    mInfo.getLogMessageForNetworkError(networkUsable));
        }
    }

    /**
     * Transfer as much data as possible from the HTTP response to the destination file.
     * @param data buffer to use to read data
     * @param entityStream stream for reading the HTTP response entity
     */
    private void transferData(
            State state, InnerState innerState, byte[] data, InputStream entityStream)
            throws StopRequestException {
        for (;;) {
            int bytesRead = readFromResponse(state, innerState, data, entityStream);
            if (bytesRead == -1) { // success, end of stream already reached
                handleEndOfStream(state, innerState);
                return;
            }

            state.mGotData = true;
            writeDataToDestination(state, data, bytesRead);
            state.mCurrentBytes += bytesRead;
            reportProgress(state, innerState);

            if (Constants.LOGVV) {
                Log.v(Constants.TAG, "downloaded " + state.mCurrentBytes + " for "
                      + mInfo.mUri);
            }

            checkPausedOrCanceled(state);
        }
    }

    /**
     * Called after a successful completion to take any necessary action on the downloaded file.
     */
    private void finalizeDestinationFile(State state) throws StopRequestException {
        if (state.mFilename != null) {
            // make sure the file is readable
            FileUtils.setPermissions(state.mFilename, 0644, -1, -1);
            syncDestination(state);
           
            File file = new File(state.mFilename);
            Xlog.d(Constants.DL_DRM, "finalizeDestinationFile:MimeType is: "  + state.mMimeType +
                    "Total Write Bytes is: " + state.mTotalWriteBytes + "file length is: " + file.length()
                     + ", file.exists(): " + file.exists() + ",file location: " + state.mFilename);
            /// M: support MTK DRM @{
            if (file.length() == state.mTotalWriteBytes) {
                ContentValues values = new ContentValues();
                // If written bytes is not equal to file.length(), don't install DRM file
                if ((Constants.MTK_DRM_ENABLED)
                        && Helpers.isMtkDRMFile(state.mMimeType)) {
                    //DrmManagerClient drmClient = new DrmManagerClient(this.mContext);
                    OmaDrmClient drmClient = new OmaDrmClient(this.mContext);
                    if (Helpers.isMtkDRMFLOrCDFile(state.mMimeType)) {
                        int result = drmClient.installDrmMsg(state.mFilename);
                        Xlog.d(Constants.DL_DRM, "install FLCD result is"  + result +
                                ",alfter install DRM Msg, new File(state.mFilename).exists(): " +
                                new File(state.mFilename).exists() + "new File(state.mFilename).length()" +
                                new File(state.mFilename).length());
                        String[] paths = {state.mFilename};
                        String[] mimeTypes = {state.mMimeType};
                        MediaScannerConnection.scanFile(mContext, paths, mimeTypes, null);
                    } else if (Helpers.isMtkDRMRightFile(state.mMimeType)) {
                        try {
                            DrmRights rights = new DrmRights(state.mFilename, state.mMimeType);
                            int result = drmClient.saveRights(rights, null, null);
                            if (result == OmaDrmClient.ERROR_NONE) {
                                /*
                                String strCID = drmClient.getContentIdFromRights(rights);
                                Xlog.d(Constants.DL_DRM, "finalizeDestinationFile:saverights return CID:"
                                        + strCID);
                                DrmUtils.rescanDrmMediaFiles(mContext, strCID, null);
                                */
                                drmClient.rescanDrmMediaFiles(mContext, rights, null);
                            }
                           
                            // Mark for delete for DRM right file
                            values = new ContentValues();
                            values.put(Downloads.Impl.COLUMN_DELETED, 1);
                            mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
                            Xlog.e(Constants.DL_DRM, "Mark for delete DRM rights file");
                           
                        } catch (IOException e) {
                            Xlog.e(Constants.DL_DRM, "save rights " + state.mFilename + " exception");
                        }
                    }
                    /// M : when file length change after install drm msg, update state.mCurrentBytes. @{
                    if (new File(state.mFilename).length() != state.mTotalWriteBytes) {
                        state.mCurrentBytes = new File(state.mFilename).length();
                        state.mTotalWriteBytes = state.mCurrentBytes;
                    }
                    /// @}
                }
                values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, state.mCurrentBytes);
                Log.v(Constants.TAG, "finalizeDestinationFile(), update total bytes, state.mCurrentBytes: " +
                        state.mCurrentBytes + ", id: " + mInfo.mId);
                mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
                Xlog.d(Constants.DL_ENHANCE, "finalizeDestinationFile: " +
                        " Update Total Bytes:"  + state.mCurrentBytes);
            }
            /// @}
        }
    }
   

    /**
     * Called just before the thread finishes, regardless of status, to take any necessary action on
     * the downloaded file.
     */
    private void cleanupDestination(State state, int finalStatus) {
        if (mDrmConvertSession != null) {
            if ((Constants.MTK_DRM_ENABLED)) {
                finalStatus = mDrmConvertSession.close(state.mFilename);
            } else {
                mDrmConvertSession.close(state.mFilename);
            }
        }

        closeDestination(state);
        if (state.mFilename != null && Downloads.Impl.isStatusError(finalStatus)) {
            Slog.d(TAG, "cleanupDestination() deleting " + state.mFilename);
            new File(state.mFilename).delete();
            state.mFilename = null;
        }
    }

    /**
     * Sync the destination file to storage.
     */
    private void syncDestination(State state) {
        FileOutputStream downloadedFileStream = null;
        try {
            downloadedFileStream = new FileOutputStream(state.mFilename, true);
            downloadedFileStream.getFD().sync();
        } catch (FileNotFoundException ex) {
            Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex);
        } catch (SyncFailedException ex) {
            Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex);
        } catch (IOException ex) {
            Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex);
        } catch (RuntimeException ex) {
            Log.w(Constants.TAG, "exception while syncing file: ", ex);
        } finally {
            if(downloadedFileStream != null) {
                try {
                    downloadedFileStream.close();
                } catch (IOException ex) {
                    Log.w(Constants.TAG, "IOException while closing synced file: ", ex);
                } catch (RuntimeException ex) {
                    Log.w(Constants.TAG, "exception while closing file: ", ex);
                }
            }
        }
    }

    /**
     * Close the destination output stream.
     */
    private void closeDestination(State state) {
        try {
            // close the file
            if (state.mStream != null) {
                state.mStream.close();
                state.mStream = null;
            }
        } 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
        }
    }

    /**
     * Check if the download has been paused or canceled, stopping the request appropriately if it
     * has been.
     */
    private void checkPausedOrCanceled(State state) throws StopRequestException {
        synchronized (mInfo) {
            if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
                Xlog.i(Constants.DL_ENHANCE, "DownloadThread: checkPausedOrCanceled: user pause download");
                throw new StopRequestException(
                        Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
            }
            if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
                throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
            }
        }

        // if policy has been changed, trigger connectivity check
        if (mPolicyDirty) {
            checkConnectivity();
        }
    }

    /**
     * Report download progress through the database if necessary.
     */
    private void reportProgress(State state, InnerState innerState) {
        final long now = SystemClock.elapsedRealtime();

        final long sampleDelta = now - state.mSpeedSampleStart;
        if (sampleDelta > 500) {
            final long sampleSpeed = ((state.mCurrentBytes - state.mSpeedSampleBytes) * 1000)
                    / sampleDelta;

            if (state.mSpeed == 0) {
                state.mSpeed = sampleSpeed;
            } else {
                state.mSpeed = ((state.mSpeed * 3) + sampleSpeed) / 4;
            }

            state.mSpeedSampleStart = now;
            state.mSpeedSampleBytes = state.mCurrentBytes;

            DownloadHandler.getInstance().setCurrentSpeed(mInfo.mId, state.mSpeed);
        }

        if (state.mCurrentBytes - state.mBytesNotified > Constants.MIN_PROGRESS_STEP &&
            now - state.mTimeLastNotification > Constants.MIN_PROGRESS_TIME) {
            ContentValues values = new ContentValues();
            values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, state.mCurrentBytes);
            mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
            state.mBytesNotified = state.mCurrentBytes;
            state.mTimeLastNotification = now;
        }
    }

    /**
     * Write a data buffer to the destination file.
     * @param data buffer containing the data to write
     * @param bytesRead how many bytes to write from the buffer
     */
    private void writeDataToDestination(State state, byte[] data, int bytesRead)
            throws StopRequestException {
        for (;;) {
            try {
                if (state.mStream == null) {
                    state.mStream = new FileOutputStream(state.mFilename, true);
                }
                mStorageManager.verifySpaceBeforeWritingToFile(mInfo.mDestination, state.mFilename,
                        bytesRead);
                if (!DownloadDrmHelper.isDrmConvertNeeded(mInfo.mMimeType)) {
                    state.mStream.write(data, 0, bytesRead);
                } else {
                    byte[] convertedData = mDrmConvertSession.convert(data, bytesRead);
                    if (convertedData != null) {
                        state.mStream.write(convertedData, 0, convertedData.length);
                    } else {
                        throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR,
                                "Error converting drm data.");
                    }
                }
                return;
            } catch (IOException ex) {
                // couldn't write to file. are we out of space? check.
                // TODO this check should only be done once. why is this being done
                // in a while(true) loop (see the enclosing statement: for(;;)
                if (state.mStream != null) {
                    mStorageManager.verifySpace(mInfo.mDestination, state.mFilename, bytesRead);
                }
            } finally {
                if (mInfo.mDestination == Downloads.Impl.DESTINATION_EXTERNAL) {
                    closeDestination(state);
                }
            }
        }
    }

    /**
     * Called when we've reached the end of the HTTP response stream, to update the database and
     * check for consistency.
     */
    private void handleEndOfStream(State state, InnerState innerState) throws StopRequestException {
        ContentValues values = new ContentValues();
        values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, state.mCurrentBytes);
       
        Xlog.d(Constants.DL_ENHANCE, "handleEndOfStream: " +
                "innerState.mHeaderContentLength: " +  innerState.mHeaderContentLength +
                " state.mCurrentBytes: " + state.mCurrentBytes +
                " mInfo.mTotalBytes: " + mInfo.mTotalBytes +
                " state.mTotalBytes: " + state.mTotalBytes);
       
        if (innerState.mHeaderContentLength == null || mInfo.mTotalBytes < 0) {
            values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, state.mCurrentBytes);
            Log.v(Constants.TAG, "handleEndOfStream(), update total bytes, state.mCurrentBytes: " +
                    state.mCurrentBytes + ", id: " + mInfo.mId);
        }
        mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);

        boolean lengthMismatched = (innerState.mHeaderContentLength != null)
                && (state.mCurrentBytes != Integer.parseInt(innerState.mHeaderContentLength));
        if (lengthMismatched) {
            if (cannotResume(state)) {
                throw new StopRequestException(Downloads.Impl.STATUS_CANNOT_RESUME,
                        "mismatched content length");
            } else {
                throw new StopRequestException(getFinalStatusForHttpError(state),
                        "closed socket before end of file");
            }
        }
    }

    private boolean cannotResume(State state) {
        /// M: return state.mCurrentBytes > 0 && !mInfo.mNoIntegrity && state.mHeaderETag == null;
        /// Extend the resume condition, exclude the Etag influence
        Xlog.d(Constants.DL_ENHANCE, "innerState.mBytesSoFar is: " +
                state.mCurrentBytes);
        return state.mCurrentBytes < 0;
    }

    /**
     * Read some data from the HTTP response stream, handling I/O errors.
     * @param data buffer to use to read data
     * @param entityStream stream for reading the HTTP response entity
     * @return the number of bytes actually read or -1 if the end of the stream has been reached
     */
    private int readFromResponse(State state, InnerState innerState, byte[] data,
                                 InputStream entityStream) throws StopRequestException {
        try {
            return entityStream.read(data);
        } catch (IOException ex) {
            logNetworkState(mInfo.mUid);
            ContentValues values = new ContentValues();
            values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, state.mCurrentBytes);
            mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
            if (cannotResume(state)) {
                String message = "while reading response: " + ex.toString()
                + ", can't resume interrupted download with no ETag";
                throw new StopRequestException(Downloads.Impl.STATUS_CANNOT_RESUME,
                        message, ex);
            } else {
                /// M: The read() function didn't response until timeout.
                /// But user may click paused button at the moment.
                /// So change the control status
                synchronized (mInfo) {
                    if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
                        Xlog.e(Constants.DL_ENHANCE, "Before read response happen exception, " +
                                "user click paused");
                        throw new StopRequestException(
                                Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
                    }
                }
                throw new StopRequestException(getFinalStatusForHttpError(state),
                        "while reading response: " + ex.toString(), ex);
            }
        }
    }

    /**
     * Open a stream for the HTTP response entity, handling I/O errors.
     * @return an InputStream to read the response entity
     */
    private InputStream openResponseEntity(State state, HttpResponse response)
            throws StopRequestException {
        try {
            return response.getEntity().getContent();
        } catch (IOException ex) {
            logNetworkState(mInfo.mUid);
            throw new StopRequestException(getFinalStatusForHttpError(state),
                    "while getting entity: " + ex.toString(), ex);
        }
    }

    private void logNetworkState(int uid) {
        if (Constants.LOGX) {
            Log.i(Constants.TAG,
                    "Net " + (Helpers.isNetworkAvailable(mSystemFacade, uid) ? "Up" : "Down"));
        }
    }

    /**
     * Read HTTP response headers and take appropriate action, including setting up the destination
     * file and updating the database.
     */
    private void processResponseHeaders(State state, InnerState innerState, HttpResponse response)
            throws StopRequestException {
        if (state.mContinuingDownload) {
            // ignore response headers on resume requests
            return;
        }

        readResponseHeaders(state, innerState, response);
        if (DownloadDrmHelper.isDrmConvertNeeded(state.mMimeType)) {
            mDrmConvertSession = DrmConvertSession.open(mContext, state.mMimeType);
            if (mDrmConvertSession == null) {
                throw new StopRequestException(Downloads.Impl.STATUS_NOT_ACCEPTABLE, "Mimetype "
                        + state.mMimeType + " can not be converted.");
            }
        }

        state.mFilename = Helpers.generateSaveFile(
                mContext,
                mInfo.mUri,
                mInfo.mHint,
                innerState.mHeaderContentDisposition,
                innerState.mHeaderContentLocation,
                state.mMimeType,
                mInfo.mDestination,
                (innerState.mHeaderContentLength != null) ?
                        Long.parseLong(innerState.mHeaderContentLength) : 0,
                /// M: Modify to support CU customization @{
                mInfo.mIsPublicApi, mStorageManager,
                mInfo.mContinueDownload,
                mInfo.mPackage,
                mInfo.mDownloadPath);
                /// @}
        try {
            state.mStream = new FileOutputStream(state.mFilename);
        } catch (FileNotFoundException exc) {
            throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR,
                    "while opening destination file: " + exc.toString(), exc);
        }
        //if (Constants.LOGV) {
            Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename);
        //}

        updateDatabaseFromHeaders(state, innerState);
        // check connectivity again now that we know the total size
        checkConnectivity();
    }

    /**
     * Update necessary database fields based on values of HTTP response headers that have been
     * read.
     */
    private void updateDatabaseFromHeaders(State state, InnerState innerState) {
        ContentValues values = new ContentValues();
        values.put(Downloads.Impl._DATA, state.mFilename);
        if (state.mHeaderETag != null) {
            values.put(Constants.ETAG, state.mHeaderETag);
        }
        if (state.mMimeType != null) {
            values.put(Downloads.Impl.COLUMN_MIME_TYPE, state.mMimeType);
        }
        values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, mInfo.mTotalBytes);
        Log.v(Constants.TAG, "updateDatabaseFromHeaders(), update total bytes, mInfo.mTotalBytes: " +
                mInfo.mTotalBytes + ", id: " + mInfo.mId);
        mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
    }

    /**
     * Read headers from the HTTP response and store them into local state.
     */
    private void readResponseHeaders(State state, InnerState innerState, HttpResponse response)
            throws StopRequestException {
        Header header = response.getFirstHeader("Content-Disposition");
        if (header != null) {
            innerState.mHeaderContentDisposition = header.getValue();
        }
        header = response.getFirstHeader("Content-Location");
        if (header != null) {
            innerState.mHeaderContentLocation = header.getValue();
        }
        if (state.mMimeType == null) {
            header = response.getFirstHeader("Content-Type");
            if (header != null) {
                state.mMimeType = Intent.normalizeMimeType(header.getValue());
            }
        }
        header = response.getFirstHeader("ETag");
        if (header != null) {
            state.mHeaderETag = header.getValue();
        }
        String headerTransferEncoding = null;
        header = response.getFirstHeader("Transfer-Encoding");
        if (header != null) {
            headerTransferEncoding = header.getValue();
        }
        if (headerTransferEncoding == null) {
            header = response.getFirstHeader("Content-Length");
            if (header != null) {
                innerState.mHeaderContentLength = header.getValue();
  Log.v(Constants.TAG, "mInfo.mTotalBytes: " + mInfo.mTotalBytes);
                state.mTotalBytes = mInfo.mTotalBytes =
                        Long.parseLong(innerState.mHeaderContentLength);
  Log.v(Constants.TAG, "mInfo.mTotalBytes: " + mInfo.mTotalBytes);
            }
        } 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, "Content-Disposition: " +
                    innerState.mHeaderContentDisposition);
            Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength);
            Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation);
            Log.v(Constants.TAG, "Content-Type: " + state.mMimeType);
            Log.v(Constants.TAG, "ETag: " + state.mHeaderETag);
            Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding);
       // }

        boolean noSizeInfo = innerState.mHeaderContentLength == null
                && (headerTransferEncoding == null
                    || !headerTransferEncoding.equalsIgnoreCase("chunked"));
        if (!mInfo.mNoIntegrity && noSizeInfo) {
            throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                    "can't know size of download, giving up");
        }
       
        /// M: Add this for OMA_DL
        /// OMA_DL HLD: 4.4 Installation Failure: in the case of retrieval errors
        /// If MimeType is not same with .dd file description, throw exception ATTRIBUTE_MISMATCH exception
        /// && !state.mMimeType.equals("audio/mp3") @{
        if (Downloads.Impl.MTK_OMA_DOWNLOAD_SUPPORT && state.mOmaDownload == 1 &&
                !state.mMimeType.equalsIgnoreCase("application/vnd.oma.dd+xml")) {
            header = response.getFirstHeader("Content-Type");
            if (header != null) {
                String mimeType = sanitizeMimeType(header.getValue());
                Xlog.d(Constants.LOG_OMA_DL, "DownloadThread:readResponseHeader():" +
                        " header mimeType is:" + mimeType
                        + "state.mMimeType is :" + state.mMimeType);
               
               
                if (Helpers.isMtkDRMFile(mimeType)) {
                    state.mMimeType = mimeType;
                    return;
                }
               
                if (((state.mMimeType.equals("audio/mp3") || state.mMimeType.equals("audio/mpeg")) &&
                        (mimeType.equals("audio/mp3") || mimeType.equals("audio/mpeg")))) {
                    return;
                }
               
                // This means ATTRIBUTE_MISMATCH
                if (!mimeType.equals(state.mMimeType)) {             
                    ContentValues values = new ContentValues();
                    values.put(Downloads.Impl.COLUMN_OMA_DOWNLOAD_STATUS,
                            Downloads.Impl.OMADL_STATUS_ERROR_ATTRIBUTE_MISMATCH);
                    mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
                   
                    state.mOmaDownloadStatus = Downloads.Impl.OMADL_STATUS_ERROR_ATTRIBUTE_MISMATCH;
                    throw new StopRequestException(Downloads.Impl.OMADL_STATUS_ERROR_ATTRIBUTE_MISMATCH,
                            Downloads.Impl.OMADL_OCCUR_ERROR_NEED_NOTIFY);
                }
            }
        }
        /// @}
    }

    /**
     * Check the HTTP response status and handle anything unusual (e.g. not 200/206).
     */
    private void handleExceptionalStatus(State state, InnerState innerState, HttpResponse response)
            throws StopRequestException, RetryDownload {
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) {
            handleServiceUnavailable(state, response);
        }
        if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307) {
            handleRedirect(state, response, statusCode);
        }
        /// M: Add this to support Authenticate Download @{
        if (statusCode == Downloads.Impl.STATUS_NEED_HTTP_AUTH) {
            if ((mInfo.mUsername != null || mInfo.mPassword != null)
                    && innerState.mAuthScheme == HttpAuthHeader.UNKNOWN
                    && innerState.mAuthHeader == null) {
                Header header = response.getFirstHeader("WWW-Authenticate");
                if (header != null) {
                    String headerAuthString = header.getValue();
                    Xlog.d(Constants.DL_ENHANCE, "response.getFirstHeader WWW-Authenticate is: "
                            + headerAuthString);
                    //Using HttpAuthHeader parse Basic Auth.
                    //Only use first Auth header tag.
                    innerState.mAuthHeader = new HttpAuthHeader(headerAuthString);
                   
                    if (innerState.mAuthHeader != null) {
                       if (innerState.mAuthHeader.getScheme() == HttpAuthHeader.BASIC)
                       {
                           innerState.mAuthScheme = HttpAuthHeader.BASIC;
                       } else if (innerState.mAuthHeader.getScheme() ==  HttpAuthHeader.DIGEST) {
                           innerState.mAuthScheme = HttpAuthHeader.DIGEST;
                       }
                       Xlog.d(Constants.DL_ENHANCE, "Auth scheme and mAuthHeader.scheme is  "
                               + innerState.mAuthScheme);
                       innerState.mIsAuthNeeded = true;
                       return;
                    }  
                }
               
            } else {
                Xlog.w(Constants.DL_ENHANCE, "DownloadThread: handleExceptionalStatus:" +
                        " 401, need Authenticate ");
                throw new StopRequestException(Downloads.Impl.STATUS_NEED_HTTP_AUTH, "http error " + statusCode);
            }
            //handleAuthenticate(state, response, statusCode);
        }
        /// @}
       
        if (Constants.LOGV) {
            Log.i(Constants.TAG, "recevd_status = " + statusCode +
                    ", mContinuingDownload = " + state.mContinuingDownload);
        }
        int expectedStatus = state.mContinuingDownload ? 206 : Downloads.Impl.STATUS_SUCCESS;
        if (statusCode != expectedStatus) {
            handleOtherStatus(state, innerState, statusCode);
        }
    }

    /**
     * Handle a status that we don't know how to deal with properly.
     */
    private void handleOtherStatus(State state, InnerState innerState, int statusCode)
            throws StopRequestException {
        if (statusCode == 416) {
            // range request failed. it should never fail.
            throw new IllegalStateException("Http Range request failure: totalBytes = " +
                    state.mTotalBytes + ", bytes recvd so far: " + state.mCurrentBytes);
        }
        int finalStatus;
        if (Downloads.Impl.isStatusError(statusCode)) {
            finalStatus = statusCode;
        } else if (statusCode >= 300 && statusCode < 400) {
            finalStatus = Downloads.Impl.STATUS_UNHANDLED_REDIRECT;
        } else if (state.mContinuingDownload && statusCode == Downloads.Impl.STATUS_SUCCESS) {
            finalStatus = Downloads.Impl.STATUS_CANNOT_RESUME;
        } else {
            finalStatus = Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE;
        }
        throw new StopRequestException(finalStatus, "http error " +
                statusCode + ", mContinuingDownload: " + state.mContinuingDownload);
    }

    /**
     * Handle a 3xx redirect status.
     */
    private void handleRedirect(State state, HttpResponse response, int statusCode)
            throws StopRequestException, RetryDownload {
        if (Constants.LOGVV) {
            Log.v(Constants.TAG, "got HTTP redirect " + statusCode);
        }
        if (state.mRedirectCount >= Constants.MAX_REDIRECTS) {
            throw new StopRequestException(Downloads.Impl.STATUS_TOO_MANY_REDIRECTS,
                    "too many redirects");
        }
        Header header = response.getFirstHeader("Location");
        if (header == null) {
            return;
        }
        if (Constants.LOGVV) {
            Log.v(Constants.TAG, "Location :" + header.getValue());
        }

        String newUri;
        try {
            newUri = new URI(mInfo.mUri).resolve(new URI(header.getValue())).toString();
        } catch(URISyntaxException ex) {
            if (Constants.LOGV) {
                Log.d(Constants.TAG, "Couldn't resolve redirect URI " + header.getValue()
                        + " for " + mInfo.mUri);
            }
            throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                    "Couldn't resolve redirect URI");
        }
        ++state.mRedirectCount;
        state.mRequestUri = newUri;
        if (statusCode == 301 || statusCode == 303) {
            // use the new URI for all future requests (should a retry/resume be necessary)
            state.mNewUri = newUri;
        }
        throw new RetryDownload();
    }

    /**
     * Handle a 503 Service Unavailable status by processing the Retry-After header.
     */
    private void handleServiceUnavailable(State state, HttpResponse response)
            throws StopRequestException {
        if (Constants.LOGVV) {
            Log.v(Constants.TAG, "got HTTP response code 503");
        }
        state.mCountRetry = true;
        Header header = response.getFirstHeader("Retry-After");
        if (header != null) {
           try {
               if (Constants.LOGVV) {
                   Log.v(Constants.TAG, "Retry-After :" + header.getValue());
               }
               state.mRetryAfter = Integer.parseInt(header.getValue());
               if (state.mRetryAfter < 0) {
                   state.mRetryAfter = 0;
               } else {
                   if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) {
                       state.mRetryAfter = Constants.MIN_RETRY_AFTER;
                   } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) {
                       state.mRetryAfter = Constants.MAX_RETRY_AFTER;
                   }
                   state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1);
                   state.mRetryAfter *= 1000;
               }
           } catch (NumberFormatException ex) {
               // ignored - retryAfter stays 0 in this case.
           }
        }
        throw new StopRequestException(Downloads.Impl.STATUS_WAITING_TO_RETRY,
                "got 503 Service Unavailable, will retry later");
    }

    /**
     * Send the request to the server, handling any I/O exceptions.
     */
    private HttpResponse sendRequest(State state, DefaultHttpClient client, HttpGet request,
            InnerState innerState, BasicHttpContext localcontext) throws StopRequestException {
        try {
            return client.execute(request);
        } catch (IllegalArgumentException ex) {
            throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
                    "while trying to execute request: " + ex.toString(), ex);
        } catch (IOException ex) {
            logNetworkState(mInfo.mUid);
            throw new StopRequestException(getFinalStatusForHttpError(state),
                    "while trying to execute request: " + ex.toString(), ex);
        }
    }

    private int getFinalStatusForHttpError(State state) {
        int networkUsable = mInfo.checkCanUseNetwork();
        if (networkUsable != DownloadInfo.NETWORK_OK) {
            switch (networkUsable) {
                case DownloadInfo.NETWORK_UNUSABLE_DUE_TO_SIZE:
                case DownloadInfo.NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE:
                    return Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
                default:
                    return Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
            }
        } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
            state.mCountRetry = true;
            return Downloads.Impl.STATUS_WAITING_TO_RETRY;
        } else {
            Log.w(Constants.TAG, "reached max retries for " + mInfo.mId);
            return Downloads.Impl.STATUS_HTTP_DATA_ERROR;
        }
    }

    /**
     * Prepare the destination file to receive data.  If the file already exists, we'll set up
     * appropriately for resumption.
     */
    private void setupDestinationFile(State state, InnerState innerState)
            throws StopRequestException {
        Xlog.d(Constants.TAG, "setupDestinationFile(): state.mFilename :" + state.mFilename);
        if (!TextUtils.isEmpty(state.mFilename)) { // only true if we've already run a thread for this download
            if (Constants.LOGV) {
                Log.i(Constants.TAG, "have run thread before for id: " + mInfo.mId +
                        ", and state.mFilename: " + state.mFilename);
            }
            if (!Helpers.isFilenameValid(state.mFilename,
                    mStorageManager.getDownloadDataDirectory())) {
                // this should never happen
                throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR,
                        "found invalid internal destination filename");
            }
           
            Xlog.d(Constants.TAG, "new File(state.mFilename).exists: " + new File(state.mFilename).exists() + "," +
              "length: " + new File(state.mFilename).length());
            // We're resuming a download that got interrupted
            File f = new File(state.mFilename);
            if (f.exists()) {
                if (Constants.LOGV) {
                    Log.i(Constants.TAG, "resuming download for id: " + mInfo.mId +
                            ", and state.mFilename: " + state.mFilename);
                }
                long fileLength = f.length();

                /// M: modify to fix tablet cts testDwonloadManagerDestination case fail [ALPS00357624] @{
                // Because CTS test file is 0 bytes, the file will be deleted in this.
                // So add "state.mCurrentBytes != state.mTotalBytes". If they equal, it means
                // the predownload is complete.
                Xlog.i(Constants.DL_ENHANCE, "setupDestinationFile: file " + state.mFilename +
                        " exsit. File length is: " + fileLength +
                        "state.mCurrentBytes: " + state.mCurrentBytes +
                        "state.mTotalBytes: " + state.mTotalBytes);
                if (fileLength == 0 && (state.mCurrentBytes != state.mTotalBytes)) {
                /// @}
                    // The download hadn't actually started, we can restart from scratch
                    Slog.d(TAG, "setupDestinationFile() found fileLength=0, deleting "
                            + state.mFilename);
                    f.delete();
                    state.mFilename = null;
                    /// M: modify to fix tablet cts testDwonloadManagerDestination case fail [ALPS00357624] @{
                    //if (Constants.LOGV) {
                        Log.i(Constants.TAG, "resuming download for id: " + mInfo.mId +
                                ", BUT starting from scratch again: Delete file: " + state.mFilename);
                    //}
                    /// @}
                /*
                } else if (mInfo.mETag == null && !mInfo.mNoIntegrity) {
                    // This should've been caught upon failure
                    Slog.d(TAG, "setupDestinationFile() unable to resume download, deleting "
                            + state.mFilename);
                    f.delete();
                    throw new StopRequestException(Downloads.Impl.STATUS_CANNOT_RESUME,
                            "Trying to resume a download that can't be resumed");
                */
                } else {
                    Log.v(Constants.TAG, "Can resume download");
                    // All right, we'll be able to resume this download
                    if (Constants.LOGV) {
                        Log.i(Constants.TAG, "resuming download for id: " + mInfo.mId +
                                ", and starting with file of length: " + fileLength);
                    }
                    try {
                        state.mStream = new FileOutputStream(state.mFilename, true);
                    } catch (FileNotFoundException exc) {
                        throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR,
                                "while opening destination for resuming: " + exc.toString(), exc);
                    }
                    state.mCurrentBytes = (int) fileLength;
                    if (mInfo.mTotalBytes != -1) {
                        innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes);
                    }
                    state.mHeaderETag = mInfo.mETag;
                    state.mContinuingDownload = true;
                    if (Constants.LOGV) {
                        Log.i(Constants.TAG, "resuming download for id: " + mInfo.mId +
                                ", state.mCurrentBytes: " + state.mCurrentBytes +
                                ", and setting mContinuingDownload to true: ");
                    }
                }
            } else {
                /// M: Add to to fix [ALPS00423697]. @{
                state.mCurrentBytes = 0;
                ContentValues values = new ContentValues();
                values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, state.mCurrentBytes);
                mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
               /// @}
            }
            Xlog.d(Constants.TAG, "state.mCurrentBytes : " + state.mCurrentBytes);
        }

        if (state.mStream != null && mInfo.mDestination == Downloads.Impl.DESTINATION_EXTERNAL) {
            closeDestination(state);
        }
    }

    /**
     * Add custom headers for this download to the HTTP request.
     */
    private void addRequestHeaders(State state, HttpGet request) {
        for (Pair<String, String> header : mInfo.getHeaders()) {
            request.addHeader(header.first, header.second);
        }

        if (state.mContinuingDownload) {
            /*
            if (state.mHeaderETag != null) {
                 request.addHeader("If-Match", state.mHeaderETag);  CR:269281
            }*/
            request.addHeader("Range", "bytes=" + state.mCurrentBytes + "-");
            if (Constants.LOGV) {
                Log.i(Constants.TAG, "Adding Range header: " +
                        "bytes=" + state.mCurrentBytes + "-");
                Log.i(Constants.TAG, "  totalBytes = " + state.mTotalBytes);
            }
     Xlog.d(Constants.TAG, "Adding Range header: " +
                        "bytes=" + state.mCurrentBytes + "-");
        }
    }

    /**
     * Stores information about the completed download, and notifies the initiating application.
     */
    private void notifyDownloadCompleted(
            int status, boolean countRetry, int retryAfter, boolean gotData,
            String filename, String uri, String mimeType, String errorMsg) {
        notifyThroughDatabase(
                status, countRetry, retryAfter, gotData, filename, uri, mimeType,
                errorMsg);
        if (Downloads.Impl.isStatusCompleted(status) && !mDownloadAlreadyCompleted) {
            mInfo.sendIntentIfRequested();
        }
    }

    private void notifyThroughDatabase(
            int status, boolean countRetry, int retryAfter, boolean gotData,
            String filename, String uri, String mimeType, String errorMsg) {
        ContentValues values = new ContentValues();
        values.put(Downloads.Impl.COLUMN_STATUS, status);
        values.put(Downloads.Impl._DATA, filename);
        if (uri != null) {
            values.put(Downloads.Impl.COLUMN_URI, uri);
        }
        values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimeType);
        values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, mSystemFacade.currentTimeMillis());
        values.put(Constants.RETRY_AFTER_X_REDIRECT_COUNT, retryAfter);
        if (!countRetry) {
            values.put(Constants.FAILED_CONNECTIONS, 0);
        } else if (gotData) {
            values.put(Constants.FAILED_CONNECTIONS, 1);
        } else {
            values.put(Constants.FAILED_CONNECTIONS, mInfo.mNumFailed + 1);
        }
        // save the error message. could be useful to developers.
        if (!TextUtils.isEmpty(errorMsg)) {
            values.put(Downloads.Impl.COLUMN_ERROR_MSG, errorMsg);
        }
        mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
    }

    /**
     * M: 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 static 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;
        }
    }

    private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
        @Override
        public void onUidRulesChanged(int uid, int uidRules) {
            // caller is NPMS, since we only register with them
            if (uid == mInfo.mUid) {
                mPolicyDirty = true;
            }
        }

        @Override
        public void onMeteredIfacesChanged(String[] meteredIfaces) {
            // caller is NPMS, since we only register with them
            mPolicyDirty = true;
        }

        @Override
        public void onRestrictBackgroundChanged(boolean restrictBackground) {
            // caller is NPMS, since we only register with them
            mPolicyDirty = true;
        }
    };
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值