Index: chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..830902eed8053f9391c73cf075df475d7edc28cc |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java |
@@ -0,0 +1,727 @@ |
+// 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()); |
+ } |
+ } |
+ } |
+} |