org.chromium.chrome.browser.download.OMADownloadHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.chromium.chrome.browser.download.OMADownloadHandler.java

Source

// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.download;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.support.v4.util.LongSparseArray;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromiumApplication;
import org.chromium.content.browser.DownloadInfo;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class handles OMA downloads according to the steps described in
 * http://xml.coverpages.org/OMA-Download-OTA-V10-20020620.pdf:
 * 1. Receives a download descriptor xml file.
 * 2. Parses all the contents.
 * 3. Checks device capability to see if it is able to handle the content.
 * 4. Find the objectURI value from the download descriptor and prompt user with
 *    a dialog to proceed with the download.
 * 5. On positive confirmation, sends a request to the download manager.
 * 6. Once the download is completed, sends a message to the server if installNotifyURI
 *    is present in the download descriptor.
 * 7. Prompts user with a dialog to open the NextURL specified in the download descriptor.
 * If steps 2 - 6 fails, a warning dialog will be prompted to the user to let him
 * know the error. Steps 6-7 will be executed afterwards.
 * If installNotifyURI is present in the download descriptor, the downloaded content will
 * be saved to the app directory first. If step 6 completes successfully, the content will
 * be moved to the public external storage. Otherwise, it will be removed from the device.
 */
public class OMADownloadHandler {
    private static final String TAG = "OMADownloadHandler";

    // MIME types for OMA downloads.
    public static final String OMA_DOWNLOAD_DESCRIPTOR_MIME = "application/vnd.oma.dd+xml";
    public static final String OMA_DRM_MESSAGE_MIME = "application/vnd.oma.drm.message";
    public static final String OMA_DRM_CONTENT_MIME = "application/vnd.oma.drm.content";
    public static final String OMA_DRM_RIGHTS_MIME = "application/vnd.oma.drm.rights+wbxml";

    // Valid download descriptor attributes.
    protected static final String OMA_TYPE = "type";
    protected static final String OMA_SIZE = "size";
    protected static final String OMA_OBJECT_URI = "objectURI";
    protected static final String OMA_INSTALL_NOTIFY_URI = "installNotifyURI";
    protected static final String OMA_NEXT_URL = "nextURL";
    protected static final String OMA_DD_VERSION = "DDVersion";
    protected static final String OMA_NAME = "name";
    protected static final String OMA_DESCRIPTION = "description";
    protected static final String OMA_VENDOR = "vendor";
    protected static final String OMA_INFO_URL = "infoURL";
    protected static final String OMA_ICON_URI = "iconURI";
    protected static final String OMA_INSTALL_PARAM = "installParam";

    // Error message to send to the notification server.
    private static final String DOWNLOAD_STATUS_SUCCESS = "900 Success \n\r";
    private static final String DOWNLOAD_STATUS_INSUFFICIENT_MEMORY = "901 insufficient memory \n\r";
    private static final String DOWNLOAD_STATUS_USER_CANCELLED = "902 User Cancelled \n\r";
    private static final String DOWNLOAD_STATUS_LOSS_OF_SERVICE = "903 Loss of Service \n\r";
    private static final String DOWNLOAD_STATUS_ATTRIBUTE_MISMATCH = "905 Attribute mismatch \n\r";
    private static final String DOWNLOAD_STATUS_INVALID_DESCRIPTOR = "906 Invalid descriptor \n\r";
    private static final String DOWNLOAD_STATUS_INVALID_DDVERSION = "951 Invalid DDVersion \n\r";
    private static final String DOWNLOAD_STATUS_DEVICE_ABORTED = "952 Device Aborted \n\r";
    private static final String DOWNLOAD_STATUS_NON_ACCEPTABLE_CONTENT = "953 Non-Acceptable Content \n\r";
    private static final String DOWNLOAD_STATUS_LOADER_ERROR = "954 Loader Error \n\r";

    private final Context mContext;
    private final LongSparseArray<OMAInfo> mPendingOMADownloads = new LongSparseArray<OMAInfo>();

    /**
     * Information about the OMA content. The object is parsed from the download
     * descriptor. There can be multiple MIME types for the object.
     */
    @VisibleForTesting
    protected static class OMAInfo {
        private final Map<String, String> mDescription;
        private final List<String> mTypes;

        OMAInfo() {
            mDescription = new HashMap<String, String>();
            mTypes = new ArrayList<String>();
        }

        /**
         * Inserts an attribute-value pair about the OMA content. If the attribute already
         * exists, the new value will replace the old one. For MIME type, it will be appended
         * to the existing MIME types.
         *
         * @param attribute The attribute to be inserted.
         * @param value The new value of the attribute.
         */
        void addAttributeValue(String attribute, String value) {
            if (attribute.equals(OMA_TYPE)) {
                mTypes.add(value);
            } else {
                // TODO(qinmin): Handle duplicate attributes
                mDescription.put(attribute, value);
            }
        }

        /**
         * Gets the value for an attribute.
         *
         * @param attribute The attribute to be retrieved.
         * @return value of the attribute.
         */
        String getValue(String attribute) {
            return mDescription.get(attribute);
        }

        /**
         * Checks whether the value is empty for an attribute.
         *
         * @param attribute The attribute to be retrieved.
         * @return true if it is empty, or false otherwise.
         */
        boolean isValueEmpty(String attribute) {
            return TextUtils.isEmpty(getValue(attribute));
        }

        /**
         * Gets the list of MIME types of the OMA content.
         *
         * @return List of MIME types.
         */
        List<String> getTypes() {
            return mTypes;
        }

        /**
         * Checks whether the information about the OMA content is empty.
         *
         * @return true if all attributes are empty, or false otherwise.
         */
        boolean isEmpty() {
            return mDescription.isEmpty() && mTypes.isEmpty();
        }

        /**
         * Gets the DRM MIME type of this object.
         *
         * @return the DRM MIME type if it is found, or null otherwise.
         */
        String getDrmType() {
            for (String type : mTypes) {
                if (type.equalsIgnoreCase(OMA_DRM_MESSAGE_MIME) || type.equalsIgnoreCase(OMA_DRM_CONTENT_MIME)) {
                    return type;
                }
            }
            return null;
        }
    }

    public OMADownloadHandler(Context context) {
        mContext = context;
    }

    /**
     * Starts handling the OMA download.
     *
     * @param downloadInfo The information about the download.
     * @param downloadId The unique identifier maintained by the Android DownloadManager.
     */
    public void handleOMADownload(DownloadInfo downloadInfo, long downloadId) {
        OMAParserTask task = new OMAParserTask(downloadInfo, downloadId);
        task.execute();
    }

    /**
     * Async task to parse an OMA download descriptor.
     */
    private class OMAParserTask extends AsyncTask<Void, Void, OMAInfo> {
        private final DownloadInfo mDownloadInfo;
        private final long mDownloadId;

        public OMAParserTask(DownloadInfo downloadInfo, long downloadId) {
            mDownloadInfo = downloadInfo;
            mDownloadId = downloadId;
        }

        @Override
        public OMAInfo doInBackground(Void... voids) {
            OMAInfo omaInfo = null;
            final DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
            try {
                ParcelFileDescriptor fd = manager.openDownloadedFile(mDownloadId);
                if (fd == null)
                    return null;
                omaInfo = parseDownloadDescriptor(new FileInputStream(fd.getFileDescriptor()));
                fd.close();
            } catch (FileNotFoundException e) {
                Log.w(TAG, "File not found.", e);
            } catch (IOException e) {
                Log.w(TAG, "Cannot read file.", e);
            }
            return omaInfo;
        }

        @Override
        protected void onPostExecute(OMAInfo omaInfo) {
            if (omaInfo == null)
                return;
            // Send notification if required attributes are missing.
            if (omaInfo.getTypes().isEmpty() || getSize(omaInfo) <= 0 || omaInfo.isValueEmpty(OMA_OBJECT_URI)) {
                sendNotification(omaInfo, mDownloadInfo, DOWNLOAD_STATUS_INVALID_DESCRIPTOR);
                return;
            }
            // Check version. Null version are treated as 1.0.
            String version = omaInfo.getValue(OMA_DD_VERSION);
            if (version != null && !version.startsWith("1.")) {
                sendNotification(omaInfo, mDownloadInfo, DOWNLOAD_STATUS_INVALID_DDVERSION);
                return;
            }
            // Check device capabilities.
            if (Environment.getExternalStorageDirectory().getUsableSpace() < getSize(omaInfo)) {
                showDownloadWarningDialog(R.string.oma_download_insufficient_memory, omaInfo, mDownloadInfo,
                        DOWNLOAD_STATUS_INSUFFICIENT_MEMORY);
                return;
            }
            if (getOpennableType(mContext.getPackageManager(), omaInfo) == null) {
                showDownloadWarningDialog(R.string.oma_download_non_acceptable_content, omaInfo, mDownloadInfo,
                        DOWNLOAD_STATUS_NON_ACCEPTABLE_CONTENT);
                return;
            }
            showOMAInfoDialog(mDownloadId, mDownloadInfo, omaInfo);
        }
    }

    /**
     * Called when the content is successfully downloaded by the Android DownloadManager.
     *
     * @param downloadInfo The information about the download.
     * @param notifyURI The previously saved installNotifyURI attribute.
     */
    public void onDownloadCompleted(DownloadInfo downloadInfo, String notifyURI) {
        long downloadId = (long) downloadInfo.getDownloadId();
        OMAInfo omaInfo = mPendingOMADownloads.get(downloadId);
        if (omaInfo == null) {
            omaInfo = new OMAInfo();
            omaInfo.addAttributeValue(OMA_INSTALL_NOTIFY_URI, notifyURI);
        }
        sendInstallNotificationAndNextStep(omaInfo, downloadInfo, DOWNLOAD_STATUS_SUCCESS);
        mPendingOMADownloads.remove(downloadId);
    }

    /**
     * Called when android DownloadManager fails to download the content.
     *
     * @param downloadInfo The information about the download.
     * @param reason The reason of failure.
     * @param notifyURI The previously saved installNotifyURI attribute.
     */
    public void onDownloadFailed(DownloadInfo downloadInfo, int reason, String notifyURI) {
        String status = DOWNLOAD_STATUS_DEVICE_ABORTED;
        switch (reason) {
        case DownloadManager.ERROR_CANNOT_RESUME:
            status = DOWNLOAD_STATUS_LOSS_OF_SERVICE;
            break;
        case DownloadManager.ERROR_HTTP_DATA_ERROR:
        case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
        case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
            status = DOWNLOAD_STATUS_LOADER_ERROR;
            break;
        case DownloadManager.ERROR_INSUFFICIENT_SPACE:
            status = DOWNLOAD_STATUS_INSUFFICIENT_MEMORY;
            break;
        default:
            break;
        }
        long downloadId = (long) downloadInfo.getDownloadId();
        OMAInfo omaInfo = mPendingOMADownloads.get(downloadId);
        if (omaInfo == null) {
            // Just send the notification in this case.
            omaInfo = new OMAInfo();
            omaInfo.addAttributeValue(OMA_INSTALL_NOTIFY_URI, notifyURI);
            sendInstallNotificationAndNextStep(omaInfo, downloadInfo, status);
            return;
        }
        showDownloadWarningDialog(R.string.oma_download_failed, omaInfo, downloadInfo, status);
        mPendingOMADownloads.remove(downloadId);
    }

    /**
     * Sends the install notification and then opens the nextURL if they are provided.
     * If the install notification is sent, nextURL will be opened after the server
     * response is received.
     *
     * @param omaInfo Information about the OMA content.
     * @param downloadInfo Information about the download.
     * @param statusMessage The message to send to the notification server.
     */
    private void sendInstallNotificationAndNextStep(OMAInfo omaInfo, DownloadInfo downloadInfo,
            String statusMessage) {
        if (!sendNotification(omaInfo, downloadInfo, statusMessage)) {
            showNextUrlDialog(omaInfo);
        }
    }

    /**
     * Sends the install notification to the server.
     *
     * @param omaInfo Information about the OMA content.
     * @param downloadInfo Information about the download.
     * @param statusMessage The message to send to the notification server.
     * @return true if the notification ise sent, or false otherwise.
     */
    private boolean sendNotification(OMAInfo omaInfo, DownloadInfo downloadInfo, String statusMessage) {
        if (omaInfo == null)
            return false;
        if (omaInfo.isValueEmpty(OMA_INSTALL_NOTIFY_URI))
            return false;
        PostStatusTask task = new PostStatusTask(omaInfo, downloadInfo, statusMessage);
        task.execute();
        return true;
    }

    /**
     * Shows the OMA information to the user and ask whether user want to proceed.
     *
     * @param downloadId The unique identifier maintained by the Android DownloadManager.
     * @param downloadInfo Information about the download.
     * @param omaInfo Information about the OMA content.
     */
    private void showOMAInfoDialog(final long downloadId, final DownloadInfo downloadInfo, final OMAInfo omaInfo) {
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflater.inflate(R.layout.confirm_oma_download, null);

        TextView textView = (TextView) v.findViewById(R.id.oma_download_name);
        textView.setText(omaInfo.getValue(OMA_NAME));
        textView = (TextView) v.findViewById(R.id.oma_download_vendor);
        textView.setText(omaInfo.getValue(OMA_VENDOR));
        textView = (TextView) v.findViewById(R.id.oma_download_size);
        textView.setText(omaInfo.getValue(OMA_SIZE));
        textView = (TextView) v.findViewById(R.id.oma_download_type);
        textView.setText(getOpennableType(mContext.getPackageManager(), omaInfo));
        textView = (TextView) v.findViewById(R.id.oma_download_description);
        textView.setText(omaInfo.getValue(OMA_DESCRIPTION));

        DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == AlertDialog.BUTTON_POSITIVE) {
                    downloadOMAContent(downloadId, downloadInfo, omaInfo);
                } else {
                    sendInstallNotificationAndNextStep(omaInfo, downloadInfo, DOWNLOAD_STATUS_USER_CANCELLED);
                }
            }
        };
        new AlertDialog.Builder(ApplicationStatus.getLastTrackedFocusedActivity())
                .setTitle(R.string.proceed_oma_download_message).setPositiveButton(R.string.ok, clickListener)
                .setNegativeButton(R.string.cancel, clickListener).setView(v).setCancelable(false).show();
    }

    /**
     * Shows a warning dialog indicating that download has failed. When user confirms
     * the warning, a message will be sent to the notification server to  inform about the
     * error.
     *
     * @param titleId The resource identifier for the title.
     * @param omaInfo Information about the OMA content.
     * @param downloadInfo Information about the download.
     * @param statusMessage Message to be sent to the notification server.
     */
    private void showDownloadWarningDialog(int titleId, final OMAInfo omaInfo, final DownloadInfo downloadInfo,
            final String statusMessage) {
        DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == AlertDialog.BUTTON_POSITIVE) {
                    sendInstallNotificationAndNextStep(omaInfo, downloadInfo, statusMessage);
                }
            }
        };
        new AlertDialog.Builder(ApplicationStatus.getLastTrackedFocusedActivity()).setTitle(titleId)
                .setPositiveButton(R.string.ok, clickListener).setCancelable(false).show();
    }

    /**
     * Shows a dialog to ask whether user wants to open the nextURL.
     *
     * @param omaInfo Information about the OMA content.
     */
    private void showNextUrlDialog(OMAInfo omaInfo) {
        if (omaInfo.isValueEmpty(OMA_NEXT_URL)) {
            return;
        }
        final String nextUrl = omaInfo.getValue(OMA_NEXT_URL);
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflater.inflate(R.layout.next_url_post_oma_download, null);
        TextView textView = (TextView) v.findViewById(R.id.oma_download_next_url);
        textView.setText(nextUrl);
        final Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
        DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == AlertDialog.BUTTON_POSITIVE) {
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(nextUrl));
                    intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
                    intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
                    intent.setPackage(mContext.getPackageName());
                    activity.startActivity(intent);
                }
            }
        };
        new AlertDialog.Builder(activity).setTitle(R.string.open_url_post_oma_download)
                .setPositiveButton(R.string.ok, clickListener).setNegativeButton(R.string.cancel, clickListener)
                .setView(v).setCancelable(false).show();
    }

    /**
     * Returns the first MIME type in the OMA download that can be opened on the device.
     *
     * @param pm PackageManger for the current context.
     * @param omaInfo Information about the OMA content.
     * @return the MIME type can be opened by the device.
     */
    static String getOpennableType(PackageManager pm, OMAInfo omaInfo) {
        if (omaInfo.isValueEmpty(OMA_OBJECT_URI)) {
            return null;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        Uri uri = Uri.parse(omaInfo.getValue(OMA_OBJECT_URI));
        for (String type : omaInfo.getTypes()) {
            if (!type.equalsIgnoreCase(OMA_DRM_MESSAGE_MIME) && !type.equalsIgnoreCase(OMA_DRM_CONTENT_MIME)
                    && !type.equalsIgnoreCase(OMA_DOWNLOAD_DESCRIPTOR_MIME)
                    && !type.equalsIgnoreCase(OMA_DRM_RIGHTS_MIME)) {
                intent.setDataAndType(uri, type);
                ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
                if (resolveInfo != null) {
                    return type;
                }
            }
        }
        return null;
    }

    /**
     * Parses the input stream and returns the OMA information.
     *
     * @param is The input stream to the parser.
     * @return OMA information about the download content, or null if an error is found.
     */
    @VisibleForTesting
    static OMAInfo parseDownloadDescriptor(InputStream is) {
        try {
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            factory.setNamespaceAware(true);
            XmlPullParser parser = factory.newPullParser();
            parser.setInput(is, null);
            int eventType = parser.getEventType();
            String currentAttribute = null;
            OMAInfo info = new OMAInfo();
            StringBuilder sb = null;
            List<String> attributeList = new ArrayList<String>(Arrays.asList(OMA_TYPE, OMA_SIZE, OMA_OBJECT_URI,
                    OMA_INSTALL_NOTIFY_URI, OMA_NEXT_URL, OMA_DD_VERSION, OMA_NAME, OMA_DESCRIPTION, OMA_VENDOR,
                    OMA_INFO_URL, OMA_ICON_URI, OMA_INSTALL_PARAM));
            while (eventType != XmlPullParser.END_DOCUMENT) {
                if (eventType == XmlPullParser.START_DOCUMENT) {
                    if (!info.isEmpty())
                        return null;
                } else if (eventType == XmlPullParser.START_TAG) {
                    String tagName = parser.getName();
                    if (attributeList.contains(tagName)) {
                        if (currentAttribute != null) {
                            Log.w(TAG, "Nested attributes was found in the download descriptor");
                            return null;
                        }
                        sb = new StringBuilder();
                        currentAttribute = tagName;
                    }
                } else if (eventType == XmlPullParser.END_TAG) {
                    if (currentAttribute != null) {
                        if (!currentAttribute.equals(parser.getName())) {
                            Log.w(TAG, "Nested attributes was found in the download descriptor");
                            return null;
                        }
                        info.addAttributeValue(currentAttribute, sb.toString().trim());
                        currentAttribute = null;
                        sb = null;
                    }
                } else if (eventType == XmlPullParser.TEXT) {
                    if (currentAttribute != null) {
                        sb.append(parser.getText());
                    }
                }
                eventType = parser.next();
            }
            return info;
        } catch (XmlPullParserException e) {
            Log.w(TAG, "Failed to parse download descriptor.", e);
            return null;
        } catch (IOException e) {
            Log.w(TAG, "Failed to read download descriptor.", e);
            return null;
        }
    }

    /**
     * Returns the size of the OMA content.
     *
     * @param omaInfo OMA information about the download content
     * @return size in bytes or 0 if the omaInfo doesn't contain size info.
     */
    @VisibleForTesting
    protected static long getSize(OMAInfo omaInfo) {
        String sizeString = omaInfo.getValue(OMA_SIZE);
        try {
            long size = sizeString == null ? 0 : Long.parseLong(sizeString.replace(",", ""));
            return size;
        } catch (NumberFormatException e) {
            Log.w(TAG, "Cannot parse size information.", e);
        }
        return 0;
    }

    /**
     * Enqueue a download request to the DownloadManager and starts downloading the OMA content.
     *
     * @param downloadId The unique identifier maintained by the Android DownloadManager.
     * @param downloadInfo Information about the download.
     * @param omaInfo Information about the OMA content.
     */
    private void downloadOMAContent(long downloadId, DownloadInfo downloadInfo, OMAInfo omaInfo) {
        if (omaInfo == null)
            return;
        String mimeType = omaInfo.getDrmType();
        if (mimeType == null) {
            mimeType = getOpennableType(mContext.getPackageManager(), omaInfo);
        }
        DownloadInfo newInfo = DownloadInfo.Builder.fromDownloadInfo(downloadInfo)
                .setFileName(omaInfo.getValue(OMA_NAME)).setUrl(omaInfo.getValue(OMA_OBJECT_URI))
                .setMimeType(mimeType).setDownloadId((int) downloadId)
                .setDescription(omaInfo.getValue(OMA_DESCRIPTION)).setContentLength(getSize(omaInfo)).build();
        // If installNotifyURI is not empty, the downloaded content cannot
        // be used until the PostStatusTask gets a 200-series response.
        // Don't show complete notification until that happens.
        DownloadManagerService.getDownloadManagerService(mContext).enqueueDownloadManagerRequest(newInfo,
                omaInfo.isValueEmpty(OMA_INSTALL_NOTIFY_URI));
        mPendingOMADownloads.put(downloadId, omaInfo);
    }

    /**
     * Checks if an OMA download is currently pending.
     *
     * @param downloadId Download identifier.
     * @return true if the download is in progress, or false otherwise.
     */
    public boolean isPendingOMADownload(long downloadId) {
        return mPendingOMADownloads.get(downloadId) != null;
    }

    /**
     * Updates the download information with the new download Id.
     *
     * @param downloadInfo Information about the download.
     * @param newDownloadId New download Id from the DownloadManager.
     * @return the new download information with the new Id.
     */
    public DownloadInfo updateDownloadInfo(DownloadInfo downloadInfo, long newDownloadId) {
        long oldDownloadId = (long) downloadInfo.getDownloadId();
        OMAInfo omaInfo = mPendingOMADownloads.get(oldDownloadId);
        mPendingOMADownloads.remove(oldDownloadId);
        mPendingOMADownloads.put(newDownloadId, omaInfo);
        return DownloadInfo.Builder.fromDownloadInfo(downloadInfo).setDownloadId((int) newDownloadId).build();
    }

    /**
     * Returns the installation notification URI for the OMA download.
     *
     * @param downloadId Download Identifier.
     * @return String containing the installNotifyURI.
     */
    public String getInstallNotifyInfo(long downloadId) {
        OMAInfo omaInfo = mPendingOMADownloads.get(downloadId);
        return omaInfo.getValue(OMA_INSTALL_NOTIFY_URI);
    }

    /**
     * This class is responsible for posting the status message to the notification server.
     */
    private class PostStatusTask extends AsyncTask<Void, Void, Boolean> {
        private static final String TAG = "PostStatusTask";
        private final OMAInfo mOMAInfo;
        private final DownloadInfo mDownloadInfo;
        private final String mStatusMessage;

        public PostStatusTask(OMAInfo omaInfo, DownloadInfo downloadInfo, String statusMessage) {
            mOMAInfo = omaInfo;
            mDownloadInfo = downloadInfo;
            mStatusMessage = statusMessage;
        }

        @Override
        protected Boolean doInBackground(Void... voids) {
            HttpClient httpClient = new DefaultHttpClient();
            HttpPost httpPost = new HttpPost(mOMAInfo.getValue(OMA_INSTALL_NOTIFY_URI));

            try {
                String userAgent = mDownloadInfo.getUserAgent();
                if (TextUtils.isEmpty(userAgent)) {
                    userAgent = ChromiumApplication.getBrowserUserAgent();
                }
                httpPost.setHeader("Accept", "*/*");
                httpPost.setHeader("User-Agent", userAgent);
                httpPost.setHeader("Cookie", mDownloadInfo.getCookie());
                httpPost.setEntity(new StringEntity(mStatusMessage));

                // Execute HTTP Post Request
                HttpResponse response = httpClient.execute(httpPost);

                byte[] responseBody;
                int responseCode = response.getStatusLine().getStatusCode();
                if (responseCode == 200 || responseCode == -1) {
                    return true;
                }
                return false;
            } catch (ClientProtocolException e) {
                Log.w(TAG, "Invalid protocol detected.", e);
            } catch (IOException e) {
                Log.w(TAG, "Cannot connect to server.", e);
            } catch (IllegalStateException e) {
                Log.w(TAG, "Cannot connect to server.", e);
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean success) {
            DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
            if (success) {
                String path = mDownloadInfo.getFilePath();
                if (!TextUtils.isEmpty(path)) {
                    // Move the downloaded content from the app directory to public directory.
                    File fromFile = new File(path);
                    String fileName = fromFile.getName();
                    File toFile = new File(
                            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
                            fileName);
                    if (fromFile.renameTo(toFile)) {
                        manager.addCompletedDownload(fileName, mDownloadInfo.getDescription(), false,
                                mDownloadInfo.getMimeType(), toFile.getPath(), mDownloadInfo.getContentLength(),
                                true);
                    } else if (fromFile.delete()) {
                        Log.w(TAG, "Failed to rename the file.");
                        return;
                    } else {
                        Log.w(TAG, "Failed to rename and delete the file.");
                    }
                }
                showNextUrlDialog(mOMAInfo);
            } else if (mDownloadInfo.getDownloadId() > 0) {
                // Remove the downloaded content.
                manager.remove(mDownloadInfo.getDownloadId());
            }
        }
    }
}