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

Java tutorial

Introduction

Here is the source code for org.chromium.chrome.browser.download.DownloadManagerService.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.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.util.LongSparseArray;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.content.browser.DownloadController;
import org.chromium.content.browser.DownloadInfo;

import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Chrome implementation of the DownloadNotificationService interface. This class is responsible for
 * keeping track of which downloads are in progress. It generates updates for progress of downloads
 * and handles cleaning up of interrupted progress notifications.
 */
public class DownloadManagerService extends BroadcastReceiver
        implements DownloadController.DownloadNotificationService {
    private static final String TAG = "DownloadNotificationService";
    private static final String DOWNLOAD_NOTIFICATION_IDS = "DownloadNotificationIds";
    private static final String DOWNLOAD_DIRECTORY = "Download";
    protected static final String PENDING_OMA_DOWNLOADS = "PendingOMADownloads";
    private static final long UPDATE_DELAY_MILLIS = 1000;

    private static DownloadManagerService sDownloadManagerService;

    private final SharedPreferences mSharedPrefs;
    private final ConcurrentHashMap<Integer, DownloadProgress> mDownloadProgressMap = new ConcurrentHashMap<Integer, DownloadProgress>(
            4, 0.75f, 2);

    private final DownloadNotifier mDownloadNotifier;
    // Delay between UI updates.
    private final long mUpdateDelayInMillis;

    // Flag to track if we need to post a task to update download notifications.
    private final AtomicBoolean mIsUIUpdateScheduled;
    private final Handler mHandler;
    private final Context mContext;

    private final LongSparseArray<DownloadInfo> mPendingAutoOpenDownloads = new LongSparseArray<DownloadInfo>();
    private OMADownloadHandler mOMADownloadHandler;

    /**
     * Enum representing status of a download.
     */
    private enum DownloadStatus {
        IN_PROGRESS, COMPLETE, FAILED
    }

    /**
     * Class representing progress of a download.
     */
    private static class DownloadProgress {
        final long mStartTimeInMillis;
        volatile DownloadInfo mDownloadInfo;
        volatile DownloadStatus mDownloadStatus;

        DownloadProgress(long startTimeInMillis, DownloadInfo downloadInfo, DownloadStatus downloadStatus) {
            mStartTimeInMillis = startTimeInMillis;
            mDownloadInfo = downloadInfo;
            mDownloadStatus = downloadStatus;
        }
    }

    /**
     * Class representing an OMA download entry to be stored in SharedPrefs.
     */
    @VisibleForTesting
    protected static class OMAEntry {
        final long mDownloadId;
        final String mInstallNotifyURI;

        OMAEntry(long downloadId, String installNotifyURI) {
            mDownloadId = downloadId;
            mInstallNotifyURI = installNotifyURI;
        }

        /**
         * Parse OMA entry from the SharedPrefs String
         * TODO(qinmin): use a file instead of SharedPrefs to store the OMA entry.
         *
         * @param entry String contains the OMA information.
         * @return an OMAEntry object.
         */
        @VisibleForTesting
        static OMAEntry parseOMAEntry(String entry) {
            int index = entry.indexOf(",");
            long downloadId = Long.parseLong(entry.substring(0, index));
            return new OMAEntry(downloadId, entry.substring(index + 1));
        }

        /**
         * Generates a string for an OMA entry to be inserted into the SharedPrefs.
         * TODO(qinmin): use a file instead of SharedPrefs to store the OMA entry.
         *
         * @return a String representing the download entry.
         */
        String generateSharedPrefsString() {
            return String.valueOf(mDownloadId) + "," + mInstallNotifyURI;
        }
    }

    /**
     * Creates DownloadManagerService.
     */
    public static DownloadManagerService getDownloadManagerService(final Context context) {
        ThreadUtils.assertOnUiThread();
        assert context == context.getApplicationContext();
        if (sDownloadManagerService == null) {
            sDownloadManagerService = new DownloadManagerService(context, new SystemDownloadNotifier(context),
                    new Handler(), UPDATE_DELAY_MILLIS);
        }
        return sDownloadManagerService;
    }

    /**
     * For tests only: sets the DownloadManagerService.
     * @param service An instance of DownloadManagerService.
     * @return Null or a currently set instance of DownloadManagerService.
     */
    @VisibleForTesting
    public static DownloadManagerService setDownloadManagerService(DownloadManagerService service) {
        ThreadUtils.assertOnUiThread();
        DownloadManagerService prev = sDownloadManagerService;
        sDownloadManagerService = service;
        return prev;
    }

    @VisibleForTesting
    protected DownloadManagerService(Context context, DownloadNotifier downloadNotifier, Handler handler,
            long updateDelayInMillis) {
        mContext = context;
        mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
        mDownloadNotifier = downloadNotifier;
        mUpdateDelayInMillis = updateDelayInMillis;
        mHandler = handler;
        mIsUIUpdateScheduled = new AtomicBoolean(false);
        mOMADownloadHandler = new OMADownloadHandler(context);
    }

    @Override
    public void onDownloadCompleted(final DownloadInfo downloadInfo) {
        DownloadStatus status = DownloadStatus.COMPLETE;
        if (!downloadInfo.isSuccessful() || downloadInfo.getContentLength() == 0) {
            status = DownloadStatus.FAILED;
        }
        updateDownloadProgress(downloadInfo, status);
        scheduleUpdateIfNeeded();
    }

    @Override
    public void onDownloadUpdated(final DownloadInfo downloadInfo) {
        updateDownloadProgress(downloadInfo, DownloadStatus.IN_PROGRESS);
        scheduleUpdateIfNeeded();
    }

    /**
     * Clear any pending notifications for incomplete downloads by reading them from shared prefs.
     * When Clank is restarted it clears any old notifications for incomplete downloads.
     */
    public void clearPendingDownloadNotifications() {
        if (mSharedPrefs.contains(DOWNLOAD_NOTIFICATION_IDS)) {
            Set<String> downloadIds = getStoredDownloadInfo(DOWNLOAD_NOTIFICATION_IDS);
            for (String id : downloadIds) {
                int notificationId = parseNotificationId(id);
                if (notificationId > 0) {
                    mDownloadNotifier.cancelNotification(notificationId);
                    Log.w(TAG, "Download failed: Cleared download id:" + id);
                }
            }
            mSharedPrefs.edit().remove(DOWNLOAD_NOTIFICATION_IDS);
            mSharedPrefs.edit().apply();
        }
        if (mSharedPrefs.contains(PENDING_OMA_DOWNLOADS)) {
            Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS);
            for (String omaDownload : omaDownloads) {
                OMAEntry entry = OMAEntry.parseOMAEntry(omaDownload);
                clearPendingOMADownload(entry.mDownloadId, entry.mInstallNotifyURI);
            }
        }
    }

    /**
     * Async task to clear the pending OMA download from SharedPrefs and inform
     * the OMADownloadHandler about download status.
     */
    private class ClearPendingOMADownloadTask extends AsyncTask<Void, Void, Integer> {
        private DownloadInfo mDownloadInfo;
        private final long mDownloadId;
        private final String mInstallNotifyURI;
        private int mFailureReason;

        public ClearPendingOMADownloadTask(long downloadId, String installNotifyURI) {
            mDownloadId = downloadId;
            mInstallNotifyURI = installNotifyURI;
            mDownloadInfo = mPendingAutoOpenDownloads.get(downloadId);
        }

        @Override
        public Integer doInBackground(Void... voids) {
            DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
            Cursor c = manager.query(new DownloadManager.Query().setFilterById(mDownloadId));
            int statusIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
            int reasonIndex = c.getColumnIndex(DownloadManager.COLUMN_REASON);
            int filenameIndex = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
            while (c.moveToNext()) {
                int status = c.getInt(statusIndex);
                if (mDownloadInfo == null) {
                    // Chrome has been killed, reconstruct a DownloadInfo.
                    mDownloadInfo = new DownloadInfo.Builder().setDownloadId((int) mDownloadId)
                            .setDescription(c.getString(c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION)))
                            .setMimeType(c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE)))
                            .setContentLength(Long.parseLong(
                                    c.getString(c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))))
                            .build();
                }
                if (status == DownloadManager.STATUS_SUCCESSFUL) {
                    mDownloadInfo = DownloadInfo.Builder.fromDownloadInfo(mDownloadInfo)
                            .setFilePath(c.getString(filenameIndex)).build();
                } else if (status == DownloadManager.STATUS_FAILED) {
                    mFailureReason = c.getInt(reasonIndex);
                    manager.remove(mDownloadId);
                }
                return status;
            }
            return DownloadManager.STATUS_FAILED;
        }

        @Override
        protected void onPostExecute(Integer result) {
            if (result == DownloadManager.STATUS_SUCCESSFUL) {
                mOMADownloadHandler.onDownloadCompleted(mDownloadInfo, mInstallNotifyURI);
                removeOMADownloadFromSharedPrefs(mDownloadId);
            } else if (result == DownloadManager.STATUS_FAILED) {
                mOMADownloadHandler.onDownloadFailed(mDownloadInfo, mFailureReason, mInstallNotifyURI);
                removeOMADownloadFromSharedPrefs(mDownloadId);
            }
        }
    }

    /**
     * Clear pending OMA downloads for a particular download ID.
     *
     * @param downloadId Download identifier.
     * @param info Information about the download.
     * @param installNotifyURI URI to notify after installation.
     */
    private void clearPendingOMADownload(long downloadId, String installNotifyURI) {
        ClearPendingOMADownloadTask task = new ClearPendingOMADownloadTask(downloadId, installNotifyURI);
        task.execute();
    }

    /**
     * Parse the notification ID from a String object.
     *
     * @param id String containing the notification ID.
     * @return notification ID.
     */
    private static int parseNotificationId(String id) {
        try {
            return Integer.parseInt(id);
        } catch (NumberFormatException nfe) {
            Log.w(TAG, "Exception while parsing download id:" + id);
            return -1;
        }
    }

    /**
     * Broadcast that a download was successful.
     * @param downloadInfo info about the download.
     */
    protected void broadcastDownloadSuccessful(DownloadInfo downloadInfo) {
    }

    /**
     * Gets download information from SharedPrefs.
     * @param type Type of the information to retrieve.
     * @return download information saved to the SharedPrefs for the given type.
     */
    @VisibleForTesting
    protected Set<String> getStoredDownloadInfo(String type) {
        return new HashSet<String>(mSharedPrefs.getStringSet(type, new HashSet<String>()));
    }

    /**
     * Removes a donwload Id from SharedPrefs.
     * @param downloadId ID to be removed.
     */
    private void removeDownloadIdFromSharedPrefs(int downloadId) {
        Set<String> downloadIds = getStoredDownloadInfo(DOWNLOAD_NOTIFICATION_IDS);
        String id = Integer.toString(downloadId);
        if (downloadIds.contains(id)) {
            downloadIds.remove(id);
            storeDownloadInfo(DOWNLOAD_NOTIFICATION_IDS, downloadIds);
        }
    }

    /**
     * Add download ID to SharedPrefs.
     * @param downloadId ID to be stored.
     */
    private void addDownloadIdToSharedPrefs(int downloadId) {
        Set<String> downloadIds = getStoredDownloadInfo(DOWNLOAD_NOTIFICATION_IDS);
        downloadIds.add(Integer.toString(downloadId));
        storeDownloadInfo(DOWNLOAD_NOTIFICATION_IDS, downloadIds);
    }

    /**
     * Add OMA download info to SharedPrefs.
     * @param omaInfo OMA download information to save.
     */
    @VisibleForTesting
    protected void addOMADownloadToSharedPrefs(String omaInfo) {
        Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS);
        omaDownloads.add(omaInfo);
        storeDownloadInfo(PENDING_OMA_DOWNLOADS, omaDownloads);
    }

    /**
     * Remove OMA download info from SharedPrefs.
     * @param downloadId ID to be removed.
     */
    private void removeOMADownloadFromSharedPrefs(long downloadId) {
        Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS);
        for (String omaDownload : omaDownloads) {
            OMAEntry entry = OMAEntry.parseOMAEntry(omaDownload);
            if (entry.mDownloadId == downloadId) {
                omaDownloads.remove(omaDownload);
                storeDownloadInfo(PENDING_OMA_DOWNLOADS, omaDownloads);
                return;
            }
        }
    }

    /**
     * Check if a download ID is in OMA SharedPrefs.
     * @param downloadId Download identifier to check.
     * @param true if it is in the SharedPrefs, or false otherwise.
     */
    private boolean isDownloadIdInOMASharedPrefs(long downloadId) {
        Set<String> omaDownloads = getStoredDownloadInfo(PENDING_OMA_DOWNLOADS);
        for (String omaDownload : omaDownloads) {
            OMAEntry entry = OMAEntry.parseOMAEntry(omaDownload);
            if (entry.mDownloadId == downloadId) {
                return true;
            }
        }
        return false;
    }

    /**
     * Stores download information to shared preferences. The information can be
     * either pending download IDs, or pending OMA downloads.
     *
     * @param type Type of the information.
     * @param downloadInfo Information to be saved.
     */
    private void storeDownloadInfo(String type, Set<String> downloadInfo) {
        SharedPreferences.Editor editor = mSharedPrefs.edit();
        if (downloadInfo.isEmpty()) {
            editor.remove(type);
        } else {
            editor.putStringSet(type, downloadInfo);
        }
        editor.apply();
    }

    /**
     * Updates notifications for all current downloads. Should not be called from UI thread.
     */
    private void updateAllNotifications() {
        assert !ThreadUtils.runningOnUiThread();
        for (DownloadProgress progress : mDownloadProgressMap.values()) {
            if (progress != null) {
                switch (progress.mDownloadStatus) {
                case COMPLETE:
                    removeProgressNotificationForDownload(progress.mDownloadInfo.getDownloadId());
                    mDownloadNotifier.notifyDownloadSuccessful(progress.mDownloadInfo);
                    broadcastDownloadSuccessful(progress.mDownloadInfo);
                    break;
                case FAILED:
                    removeProgressNotificationForDownload(progress.mDownloadInfo.getDownloadId());
                    mDownloadNotifier.notifyDownloadFailed(progress.mDownloadInfo);
                    Log.w(TAG, "Download failed: " + progress.mDownloadInfo.getFilePath());
                    break;
                case IN_PROGRESS:
                    mDownloadNotifier.notifyDownloadProgress(progress.mDownloadInfo, progress.mStartTimeInMillis);
                }
            }
        }
    }

    /**
     * Schedule an update if there is no update scheduled.
     */
    private void scheduleUpdateIfNeeded() {
        if (mIsUIUpdateScheduled.compareAndSet(false, true)) {
            Runnable updateTask = new Runnable() {
                @Override
                public void run() {
                    new AsyncTask<Void, Void, Void>() {
                        @Override
                        public Void doInBackground(Void... params) {
                            updateAllNotifications();
                            return null;
                        }
                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                    mIsUIUpdateScheduled.set(false);
                }
            };
            mHandler.postDelayed(updateTask, mUpdateDelayInMillis);
        }
    }

    /**
     * Cancel the progress notification of download and clear any cached information about this
     * download.
     *
     * @param downloadId Download Identifier.
     */
    private void removeProgressNotificationForDownload(int downloadId) {
        mDownloadProgressMap.remove(downloadId);
        mDownloadNotifier.cancelNotification(downloadId);
        removeDownloadIdFromSharedPrefs(downloadId);
    }

    /**
     * Updates the progress of a download.
     *
     * @param downloadInfo Information about the download.
     * @param status Status of the download.
     */
    private void updateDownloadProgress(DownloadInfo downloadInfo, DownloadStatus status) {
        assert downloadInfo.hasDownloadId();
        int downloadId = downloadInfo.getDownloadId();
        DownloadProgress progress = mDownloadProgressMap.get(downloadId);
        if (progress == null) {
            progress = new DownloadProgress(System.currentTimeMillis(), downloadInfo, status);
            if (status == DownloadStatus.IN_PROGRESS) {
                // A new in-progress download, add an entry to shared prefs to make sure
                // to clear the notification.
                addDownloadIdToSharedPrefs(downloadId);
            }
            mDownloadProgressMap.putIfAbsent(downloadId, progress);
        } else {
            progress.mDownloadStatus = status;
            progress.mDownloadInfo = downloadInfo;
        }
    }

    /**
     * Sets the download handler for OMA downloads, for testing purpose.
     *
     * @param omaDownloadHandler Download handler for OMA contents.
     */
    @VisibleForTesting
    protected void setOMADownloadHandler(OMADownloadHandler omaDownloadHandler) {
        mOMADownloadHandler = omaDownloadHandler;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (!DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action))
            return;
        final DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);

        long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (downloadId == -1)
            return;
        boolean isPendingOMADownload = mOMADownloadHandler.isPendingOMADownload(downloadId);
        boolean isInOMASharedPrefs = isDownloadIdInOMASharedPrefs(downloadId);
        if (isPendingOMADownload || isInOMASharedPrefs) {
            clearPendingOMADownload(downloadId, null);
            mPendingAutoOpenDownloads.remove(downloadId);
        } else if (mPendingAutoOpenDownloads.get(downloadId) != null) {
            Cursor c = manager.query(new DownloadManager.Query().setFilterById(downloadId));
            int statusIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
            while (c.moveToNext()) {
                int status = c.getInt(statusIndex);
                DownloadInfo info = mPendingAutoOpenDownloads.get(downloadId);
                switch (status) {
                case DownloadManager.STATUS_SUCCESSFUL:
                    try {
                        mPendingAutoOpenDownloads.remove(downloadId);
                        if (OMADownloadHandler.OMA_DOWNLOAD_DESCRIPTOR_MIME.equalsIgnoreCase(info.getMimeType())) {
                            mOMADownloadHandler.handleOMADownload(info, downloadId);
                            manager.remove(downloadId);
                            break;
                        }
                        Uri uri = manager.getUriForDownloadedFile(downloadId);
                        Intent launchIntent = new Intent(Intent.ACTION_VIEW);

                        launchIntent.setDataAndType(uri, manager.getMimeTypeForDownloadedFile(downloadId));
                        launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                        mContext.startActivity(launchIntent);
                    } catch (ActivityNotFoundException e) {
                        Log.w(TAG, "Activity not found.");
                    }
                    break;
                case DownloadManager.STATUS_FAILED:
                    mPendingAutoOpenDownloads.remove(downloadId);
                    break;
                default:
                    break;
                }
            }
        }

        if (mPendingAutoOpenDownloads.size() == 0) {
            mContext.unregisterReceiver(this);
        }
    }

    /**
     * Sends the download request to Android download manager. If |notifyCompleted| is true,
     * a notification will be sent to the user once download is complete and the downloaded
     * content will be saved to the public directory on external storage. Otherwise, the
     * download will be saved in the app directory and user will not get any notifications
     * after download completion.
     *
     * @param info Download information about the download.
     * @param notifyCompleted Whether to notify the user after Downloadmanager completes the
     *                        download.
     */
    public void enqueueDownloadManagerRequest(final DownloadInfo info, boolean notifyCompleted) {
        EnqueueDownloadRequestTask task = new EnqueueDownloadRequestTask(info);
        task.execute(notifyCompleted);
    }

    /**
     * Async task to enqueue a download request into DownloadManager.
     */
    private class EnqueueDownloadRequestTask extends AsyncTask<Boolean, Void, Boolean> {
        private int mErrorId;
        private long mDownloadId;
        private DownloadInfo mDownloadInfo;

        public EnqueueDownloadRequestTask(DownloadInfo downloadInfo) {
            mDownloadInfo = downloadInfo;
        }

        @Override
        public Boolean doInBackground(Boolean... booleans) {
            boolean notifyCompleted = booleans[0];
            Uri uri = Uri.parse(mDownloadInfo.getUrl());
            DownloadManager.Request request;
            try {
                request = new DownloadManager.Request(uri);
            } catch (IllegalArgumentException e) {
                mErrorId = R.string.cannot_download_http_or_https;
                return false;
            }

            request.setMimeType(mDownloadInfo.getMimeType());
            try {
                if (notifyCompleted) {
                    // Set downloaded file destination to /sdcard/Download or, should it be
                    // set to one of several Environment.DIRECTORY* dirs depending on mimetype?
                    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,
                            mDownloadInfo.getFileName());
                } else {
                    File dir = new File(mContext.getExternalFilesDir(null), DOWNLOAD_DIRECTORY);
                    if (dir.mkdir() || dir.isDirectory()) {
                        File file = new File(dir, mDownloadInfo.getFileName());
                        request.setDestinationUri(Uri.fromFile(file));
                    } else {
                        mErrorId = R.string.cannot_create_download_directory_title;
                        return false;
                    }
                }
            } catch (IllegalStateException e) {
                mErrorId = R.string.cannot_create_download_directory_title;
                return false;
            }

            if (notifyCompleted) {
                // Let this downloaded file be scanned by MediaScanner - so that it can
                // show up in Gallery app, for example.
                request.allowScanningByMediaScanner();
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            } else {
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
            }
            String description = mDownloadInfo.getDescription();
            if (TextUtils.isEmpty(description)) {
                description = mDownloadInfo.getFileName();
            }
            request.setDescription(description);
            request.addRequestHeader("Cookie", mDownloadInfo.getCookie());
            request.addRequestHeader("Referer", mDownloadInfo.getReferer());
            request.addRequestHeader("User-Agent", mDownloadInfo.getUserAgent());

            DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
            try {
                mDownloadId = manager.enqueue(request);
            } catch (IllegalArgumentException e) {
                // See crbug.com/143499 for more details.
                Log.e(TAG, "Download failed: " + e);
                mErrorId = R.string.cannot_download_generic;
                return false;
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            boolean isPendingOMADownload = mOMADownloadHandler
                    .isPendingOMADownload((long) mDownloadInfo.getDownloadId());
            if (!result) {
                Toast.makeText(mContext, mErrorId, Toast.LENGTH_SHORT).show();
                if (isPendingOMADownload) {
                    mOMADownloadHandler.onDownloadFailed(mDownloadInfo, DownloadManager.ERROR_UNKNOWN, null);
                }
                return;
            }
            Toast.makeText(mContext, R.string.download_pending, Toast.LENGTH_SHORT).show();
            if (isPendingOMADownload) {
                // A new downloadId is generated, needs to update the OMADownloadHandler
                // about this.
                mDownloadInfo = mOMADownloadHandler.updateDownloadInfo(mDownloadInfo, mDownloadId);
                // TODO(qinmin): use a file instead of shared prefs to save the
                // OMA information in case chrome is killed. This will allow us to
                // save more information like cookies and user agent.
                String notifyUri = mOMADownloadHandler.getInstallNotifyInfo(mDownloadId);
                if (!TextUtils.isEmpty(notifyUri)) {
                    OMAEntry entry = new OMAEntry(mDownloadId, notifyUri);
                    addOMADownloadToSharedPrefs(entry.generateSharedPrefsString());
                }
            }
            if (shouldOpenAfterDownload(mDownloadInfo) || isPendingOMADownload) {
                if (mPendingAutoOpenDownloads.size() == 0) {
                    mContext.registerReceiver(DownloadManagerService.this,
                            new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
                }
                mPendingAutoOpenDownloads.put(mDownloadId, mDownloadInfo);
            }
        }
    }

    /**
     * Determines if the download should be immediately opened after
     * downloading.
     *
     * @param downloadInfo Information about the download.
     * @return true if the downloaded content should be opened, or false otherwise.
     */
    @VisibleForTesting
    static boolean shouldOpenAfterDownload(DownloadInfo downloadInfo) {
        String type = downloadInfo.getMimeType();
        return downloadInfo.hasUserGesture() && !isAttachment(downloadInfo.getContentDisposition())
                && (type.equalsIgnoreCase("application/pdf")
                        || type.equalsIgnoreCase(OMADownloadHandler.OMA_DOWNLOAD_DESCRIPTOR_MIME));
    }

    /**
     * Returns true if the download meant to be treated as an attachment.
     *
     * @param contentDisposition Content disposition of the download.
     * @return true if the downloaded is an attachment, or false otherwise.
     */
    public static boolean isAttachment(String contentDisposition) {
        return contentDisposition != null && contentDisposition.regionMatches(true, 0, "attachment", 0, 10);
    }
}