// 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.DownloadManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.Browser; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; import android.view.LayoutInflater; import android.view.View; import android.webkit.URLUtil; import android.widget.TextView; import org.chromium.base.ApplicationStatus; import org.chromium.base.VisibleForTesting; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeApplication; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; 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) { 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); } manager.remove(mDownloadId); 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, DownloadItem.INVALID_DOWNLOAD_ID, 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, DownloadItem.INVALID_DOWNLOAD_ID, 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 downloadId Download Id from the Android DownloadManager. * @param notifyURI The previously saved installNotifyURI attribute. */ public void onDownloadCompleted(DownloadInfo downloadInfo, long downloadId, String notifyURI) { OMAInfo omaInfo = mPendingOMADownloads.get(downloadId); if (omaInfo == null) { omaInfo = new OMAInfo(); omaInfo.addAttributeValue(OMA_INSTALL_NOTIFY_URI, notifyURI); } sendInstallNotificationAndNextStep( omaInfo, downloadInfo, downloadId, DOWNLOAD_STATUS_SUCCESS); mPendingOMADownloads.remove(downloadId); } /** * Called when android DownloadManager fails to download the content. * * @param downloadInfo The information about the download. * @param downloadId Download Id from the Android DownloadManager. * @param reason The reason of failure. * @param notifyURI The previously saved installNotifyURI attribute. */ public void onDownloadFailed( DownloadInfo downloadInfo, long downloadId, 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; } 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, downloadId, 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 downloadId Id of the download in Android DownloadManager. * @param statusMessage The message to send to the notification server. */ private void sendInstallNotificationAndNextStep( OMAInfo omaInfo, DownloadInfo downloadInfo, long downloadId, String statusMessage) { if (!sendNotification(omaInfo, downloadInfo, downloadId, statusMessage)) { showNextUrlDialog(omaInfo); } } /** * Sends the install notification to the server. * * @param omaInfo Information about the OMA content. * @param downloadInfo Information about the download. * @param downloadId Id of the download in Android DownloadManager. * @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, long downloadId, String statusMessage) { if (omaInfo == null) return false; if (omaInfo.isValueEmpty(OMA_INSTALL_NOTIFY_URI)) return false; PostStatusTask task = new PostStatusTask(omaInfo, downloadInfo, downloadId, 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 { sendNotification(omaInfo, downloadInfo, DownloadItem.INVALID_DOWNLOAD_ID, DOWNLOAD_STATUS_USER_CANCELLED); } } }; new AlertDialog.Builder( ApplicationStatus.getLastTrackedFocusedActivity(), R.style.AlertDialogTheme) .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, DownloadItem.INVALID_DOWNLOAD_ID, statusMessage); } } }; new AlertDialog.Builder( ApplicationStatus.getLastTrackedFocusedActivity(), R.style.AlertDialogTheme) .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); 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) .setMessage(nextUrl) .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); if (!pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { 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); } String fileName = omaInfo.getValue(OMA_NAME); String url = omaInfo.getValue(OMA_OBJECT_URI); if (TextUtils.isEmpty(fileName)) { fileName = URLUtil.guessFileName(url, null, mimeType); } DownloadInfo newInfo = DownloadInfo.Builder.fromDownloadInfo(downloadInfo) .setFileName(fileName) .setUrl(url) .setMimeType(mimeType) .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. DownloadItem item = new DownloadItem(true, newInfo); item.setSystemDownloadId(downloadId); DownloadManagerService.getDownloadManagerService(mContext).enqueueDownloadManagerRequest( item, 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 oldDownloadId Old download Id from the DownloadManager. * @param newDownloadId New download Id from the DownloadManager. */ public void updateDownloadInfo(long oldDownloadId, long newDownloadId) { OMAInfo omaInfo = mPendingOMADownloads.get(oldDownloadId); mPendingOMADownloads.remove(oldDownloadId); mPendingOMADownloads.put(newDownloadId, omaInfo); } /** * 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; private final long mDownloadId; public PostStatusTask( OMAInfo omaInfo, DownloadInfo downloadInfo, long downloadId, String statusMessage) { mOMAInfo = omaInfo; mDownloadInfo = downloadInfo; mStatusMessage = statusMessage; mDownloadId = downloadId; } @Override protected Boolean doInBackground(Void...voids) { HttpURLConnection urlConnection = null; try { URL url = new URL(mOMAInfo.getValue(OMA_INSTALL_NOTIFY_URI)); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setDoOutput(true); urlConnection.setUseCaches(false); urlConnection.setRequestMethod("POST"); String userAgent = mDownloadInfo.getUserAgent(); if (TextUtils.isEmpty(userAgent)) { userAgent = ChromeApplication.getBrowserUserAgent(); } urlConnection.setRequestProperty("User-Agent", userAgent); urlConnection.setRequestProperty("cookie", mDownloadInfo.getCookie()); DataOutputStream dos = new DataOutputStream(urlConnection.getOutputStream()); try { dos.writeBytes(mStatusMessage); dos.flush(); } catch (IOException e) { Log.w(TAG, "Cannot write status message.", e); } finally { dos.close(); } int responseCode = urlConnection.getResponseCode(); if (responseCode == 200 || responseCode == -1) { return true; } return false; } catch (MalformedURLException e) { Log.w(TAG, "Invalid notification URL.", e); } catch (IOException e) { Log.w(TAG, "Cannot connect to server.", e); } catch (IllegalStateException e) { Log.w(TAG, "Cannot connect to server.", e); } finally { if (urlConnection != null) urlConnection.disconnect(); } 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 (mDownloadId != DownloadItem.INVALID_DOWNLOAD_ID) { // Remove the downloaded content. manager.remove(mDownloadId); } } } }