Bug 1253684 - Introduce preference for forwarding OMA downloads to Android's download manager. r=margaret, a=lizzard
authorSebastian Kaspari <s.kaspari@gmail.com>
Thu, 10 Mar 2016 10:51:25 +0100
changeset 310345 0d35bb93eda6e42aefe822fd8106496450f33168
parent 310344 435e4e199131c10583596524bf79cf1120e6a795
child 310346 e8c5bba77ee526b19c21731905093f76cd0868db
push id9338
push usercbook@mozilla.com
push dateWed, 30 Mar 2016 13:58:47 +0000
treeherdermozilla-aurora@876a1f819d83 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, lizzard
bugs1253684
milestone47.0a2
Bug 1253684 - Introduce preference for forwarding OMA downloads to Android's download manager. r=margaret, a=lizzard MozReview-Commit-ID: u9j3CGex2q
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/components/HelperAppDialog.js
modules/libpref/init/all.js
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1,16 +1,18 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.Manifest;
+import android.app.DownloadManager;
+import android.os.Environment;
 import android.support.annotation.NonNull;
 import org.json.JSONArray;
 import org.mozilla.gecko.adjust.AdjustHelperInterface;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.PinReason;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
@@ -677,16 +679,17 @@ public class BrowserApp extends GeckoApp
             "Menu:Update",
             "LightweightTheme:Update",
             "Search:Keyword",
             "Prompt:ShowTop");
 
         EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
             "CharEncoding:Data",
             "CharEncoding:State",
+            "Download:AndroidDownloadManager",
             "Experiments:GetActive",
             "Favicon:CacheLoad",
             "Feedback:LastUrl",
             "Feedback:MaybeLater",
             "Feedback:OpenPlayStore",
             "Menu:Add",
             "Menu:Remove",
             "Sanitize:ClearHistory",
@@ -1424,16 +1427,17 @@ public class BrowserApp extends GeckoApp
             "Menu:Update",
             "LightweightTheme:Update",
             "Search:Keyword",
             "Prompt:ShowTop");
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
             "CharEncoding:Data",
             "CharEncoding:State",
+            "Download:AndroidDownloadManager",
             "Experiments:GetActive",
             "Favicon:CacheLoad",
             "Feedback:LastUrl",
             "Feedback:MaybeLater",
             "Feedback:OpenPlayStore",
             "Menu:Add",
             "Menu:Remove",
             "Sanitize:ClearHistory",
@@ -1792,16 +1796,48 @@ public class BrowserApp extends GeckoApp
             if (Restrictions.isRestrictedProfile(this)) {
                 for (Restrictable rest : RestrictedProfileConfiguration.getVisibleRestrictions()) {
                     int value = Restrictions.isAllowed(this, rest) ? 1 : 0;
                     Telemetry.addToKeyedHistogram("FENNEC_RESTRICTED_PROFILE_RESTRICTIONS", rest.name(), value);
                 }
             }
         } else if ("Updater:Launch".equals(event)) {
             handleUpdaterLaunch();
+        } else if ("Download:AndroidDownloadManager".equals(event)) {
+            // Downloading via Android's download manager
+
+            final String uri = message.getString("uri");
+            final String filename = message.getString("filename");
+            final String mimeType = message.getString("mimeType");
+
+            final DownloadManager.Request request = new DownloadManager.Request(Uri.parse(uri));
+            request.setMimeType(mimeType);
+
+            try {
+                request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, filename);
+            } catch (IllegalStateException e) {
+                Log.e(LOGTAG, "Cannot create download directory");
+                return;
+            }
+
+            request.allowScanningByMediaScanner();
+            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+            request.addRequestHeader("User-Agent", HardwareUtils.isTablet() ?
+                    AppConstants.USER_AGENT_FENNEC_TABLET :
+                    AppConstants.USER_AGENT_FENNEC_MOBILE);
+
+            try {
+                DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+                manager.enqueue(request);
+
+                Log.d(LOGTAG, "Enqueued download (Download Manager)");
+            } catch (RuntimeException e) {
+                Log.e(LOGTAG, "Download failed: " + e);
+            }
+
         } else {
             super.handleMessage(event, message, callback);
         }
     }
 
     private void getFaviconFromCache(final EventCallback callback, final String url) {
         final OnFaviconLoadedListener listener = new OnFaviconLoadedListener() {
             @Override
--- a/mobile/android/components/HelperAppDialog.js
+++ b/mobile/android/components/HelperAppDialog.js
@@ -3,28 +3,34 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*globals ContentAreaUtils */
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const APK_MIME_TYPE = "application/vnd.android.package-archive";
+
 const OMA_DOWNLOAD_DESCRIPTOR_MIME_TYPE = "application/vnd.oma.dd+xml";
+const OMA_DRM_MESSAGE_MIME = "application/vnd.oma.drm.message";
+const OMA_DRM_CONTENT_MIME = "application/vnd.oma.drm.content";
+const OMA_DRM_RIGHTS_MIME = "application/vnd.oma.drm.rights+wbxml";
+
 const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
 const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
 
 Cu.import("resource://gre/modules/Downloads.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/HelperApps.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm");
 
 // -----------------------------------------------------------------------
 // HelperApp Launcher Dialog
 // -----------------------------------------------------------------------
 
 XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
@@ -97,22 +103,51 @@ HelperAppLauncherDialog.prototype = {
    */
   _shouldAddSaveToDiskIntent: function(launcher) {
       let mimeType = this._getMimeTypeFromLauncher(launcher);
 
       // We can't handle OMA downloads. So don't even try. (Bug 1219078)
       return mimeType != OMA_DOWNLOAD_DESCRIPTOR_MIME_TYPE;
   },
 
+  /**
+   * Returns true if `launcher`represents a download that should not be handled by Firefox
+   * or a third-party app and instead be forwarded to Android's download manager.
+   */
+  _shouldForwardToAndroidDownloadManager: function(aLauncher) {
+    let forwardDownload = Services.prefs.getBoolPref('browser.download.forward_oma_android_download_manager');
+    if (!forwardDownload) {
+      return false;
+    }
+
+    let mimeType = aLauncher.MIMEInfo.MIMEType;
+    if (!mimeType) {
+      mimeType = ContentAreaUtils.getMIMETypeForURI(aLauncher.source) || "";
+    }
+
+    return [
+      OMA_DOWNLOAD_DESCRIPTOR_MIME_TYPE,
+      OMA_DRM_MESSAGE_MIME,
+      OMA_DRM_CONTENT_MIME,
+      OMA_DRM_RIGHTS_MIME
+    ].indexOf(mimeType) != -1;
+  },
+
   show: function hald_show(aLauncher, aContext, aReason) {
     if (!this._canDownload(aLauncher.source)) {
       this._refuseDownload(aLauncher);
       return;
     }
 
+    if (this._shouldForwardToAndroidDownloadManager(aLauncher)) {
+      this._downloadWithAndroidDownloadManager(aLauncher);
+      aLauncher.cancel(Cr.NS_BINDING_ABORTED);
+      return;
+    }
+
     let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
 
     let defaultHandler = new Object();
     let apps = HelperApps.getAppsForUri(aLauncher.source, {
       mimeType: aLauncher.MIMEInfo.MIMEType,
     });
 
     if (this._shouldAddSaveToDiskIntent(aLauncher)) {
@@ -198,16 +233,30 @@ HelperAppLauncherDialog.prototype = {
     }
 
     Services.console.logStringMessage("Refusing download of non-downloadable file.");
     let bundle = Services.strings.createBundle("chrome://browser/locale/handling.properties");
     let failedText = bundle.GetStringFromName("download.blocked");
     win.toast.show(failedText, "long");
   },
 
+  _downloadWithAndroidDownloadManager(aLauncher) {
+    let mimeType = aLauncher.MIMEInfo.MIMEType;
+    if (!mimeType) {
+      mimeType = ContentAreaUtils.getMIMETypeForURI(aLauncher.source) || "";
+    }
+
+    Messaging.sendRequest({
+      'type': 'Download:AndroidDownloadManager',
+      'uri': aLauncher.source.spec,
+      'mimeType': mimeType,
+      'filename': aLauncher.suggestedFileName
+    });
+  },
+
   _getPrefName: function getPrefName(mimetype) {
     return "browser.download.preferred." + mimetype.replace("\\", ".");
   },
 
   _getMimeTypeFromLauncher: function (launcher) {
     let mime = launcher.MIMEInfo.MIMEType;
     if (!mime)
       mime = ContentAreaUtils.getMIMETypeForURI(launcher.source) || "";
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3755,16 +3755,20 @@ pref("layout.css.scroll-snap.enabled", f
 // temporary popup windows.  Setting this to false would make the default
 // level "parent" which is implemented with managed windows.
 // A problem with using managed windows is that metacity sometimes deactivates
 // the parent window when the managed popup is shown.
 pref("ui.panel.default_level_parent", true);
 
 pref("mousewheel.system_scroll_override_on_root_content.enabled", false);
 
+// Forward downloads with known OMA MIME types to Android's download manager
+// instead of downloading them in the browser.
+pref("browser.download.forward_oma_android_download_manager", false);
+
 # ANDROID
 #endif
 
 #ifndef ANDROID
 #ifndef XP_MACOSX
 #ifdef XP_UNIX
 // Handled differently under Mac/Windows
 pref("network.protocol-handler.warn-external.file", false);