Bug 1406903 Part 2: Added support for opening HTML files. r=JanH
authorAndrei Lazar <andrei.a.lazar@softvision.ro>
Mon, 15 Oct 2018 11:05:26 +0000
changeset 489610 8a510f2052d3e6be52a72dc045a2ce03a0a952ae
parent 489609 05d8e65ff651affb56489cce5910f57939926561
child 489611 a3411c7915f70c9d420a62c19d4e39d6269e31b4
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersJanH
bugs1406903
milestone64.0a1
Bug 1406903 Part 2: Added support for opening HTML files. r=JanH Added support for opening HTML files from internal storage when user is choosing fennec in the dialog picker. Depends on D7167 Differential Revision: https://phabricator.services.mozilla.com/D7490
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
mobile/android/components/ContentDispatchChooser.js
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -94,16 +94,17 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.BROWSABLE" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="file" />
                 <data android:scheme="http" />
                 <data android:scheme="https" />
+                <data android:scheme="content" />
                 <data android:mimeType="text/html"/>
                 <data android:mimeType="text/plain"/>
                 <data android:mimeType="application/xhtml+xml"/>
                 <data android:mimeType="image/svg+xml"/>
             </intent-filter>
 
             <meta-data android:name="com.sec.minimode.icon.portrait.normal"
                        android:resource="@drawable/icon"/>
--- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
@@ -6,16 +6,17 @@
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
 
 import android.app.Activity;
 import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
@@ -34,16 +35,17 @@ import android.webkit.MimeTypeMap;
 import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.List;
 import java.util.Locale;
 
 import static org.mozilla.gecko.Tabs.INTENT_EXTRA_SESSION_UUID;
 import static org.mozilla.gecko.Tabs.INTENT_EXTRA_TAB_ID;
+import static org.mozilla.gecko.util.FileUtils.getFilePathFromUri;
 
 public final class IntentHelper implements BundleEventListener {
 
     private static final String LOGTAG = "GeckoIntentHelper";
     private static final String[] GECKO_EVENTS = {
         // Need to be on Gecko thread for synchronous callback.
         "Intent:GetHandlers",
     };
@@ -493,16 +495,23 @@ public final class IntentHelper implemen
         } catch (final URISyntaxException e) {
             // Don't log the exception to prevent leaking URIs.
             Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
             errorResponse.putBoolean("isFallback", false);
             callback.sendError(errorResponse);
             return;
         }
 
+        if (FileUtils.isContentUri(uri)) {
+            errorResponse.putString("uri", getFilePathFromUri(getContext(), intent.getData()));
+            errorResponse.putBoolean("isFallback", true);
+            callback.sendError(errorResponse);
+            return;
+        }
+
         // For this flow, we follow Chrome's lead:
         //   https://developer.chrome.com/multidevice/android/intents
         final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
         if (isFallbackUrlValid(fallbackUrl)) {
             errorResponse.putString("uri", fallbackUrl);
             errorResponse.putBoolean("isFallback", true);
             // Opens the page in JS.
             callback.sendError(errorResponse);
--- a/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
@@ -12,16 +12,17 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.customtabs.CustomTabsIntent;
 import android.util.Log;
 
 import org.mozilla.gecko.home.HomeConfig;
 import org.mozilla.gecko.mma.MmaDelegate;
 import org.mozilla.gecko.switchboard.SwitchBoard;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.webapps.WebAppActivity;
 import org.mozilla.gecko.webapps.WebAppIndexer;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
 import org.mozilla.gecko.tabqueue.TabQueueService;
@@ -37,16 +38,17 @@ import static org.mozilla.gecko.deeplink
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_PREFERENCES_GENERAL;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_PREFERENCES_NOTIFICATIONS;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_PREFERENCES_PRIAVACY;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_PREFERENCES_SEARCH;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_SAVE_AS_PDF;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_SIGN_UP;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.SUMO_DEFAULT_BROWSER;
 import static org.mozilla.gecko.deeplink.DeepLinkContract.LINK_FXA_SIGNIN;
+import static org.mozilla.gecko.util.FileUtils.isContentUri;
 
 import org.mozilla.gecko.deeplink.DeepLinkContract;
 
 /**
  * Activity that receives incoming Intents and dispatches them to the appropriate activities (e.g. browser, custom tabs, web app).
  */
 public class LauncherActivity extends Activity {
     private static final String TAG = LauncherActivity.class.getSimpleName();
@@ -62,18 +64,19 @@ public class LauncherActivity extends Ac
             dispatchDeepLink(safeIntent);
 
         } else if (isShutdownIntent(safeIntent)) {
             dispatchShutdownIntent();
         // Is this web app?
         } else if (isWebAppIntent(safeIntent)) {
             dispatchWebAppIntent();
 
-        // If it's not a view intent, it won't be a custom tabs intent either. Just launch!
-        } else if (!isViewIntentWithURL(safeIntent)) {
+        // If it's not a view intent, it won't be a custom tabs intent either, and for content URI
+        // let's handle only with normal tabs for the moment
+        } else if (!isViewIntentWithURL(safeIntent) || isContentUri(safeIntent.getData())) {
             dispatchNormalIntent();
 
         } else if (isCustomTabsIntent(safeIntent) && isCustomTabsEnabled(this) ) {
             dispatchCustomTabsIntent();
 
         // Can we dispatch this VIEW action intent to the tab queue service?
         } else if (!safeIntent.getBooleanExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, false)
                 && TabQueueHelper.TAB_QUEUE_ENABLED
--- a/mobile/android/components/ContentDispatchChooser.js
+++ b/mobile/android/components/ContentDispatchChooser.js
@@ -40,59 +40,62 @@ ContentDispatchChooser.prototype =
 
   ask: function ask(aHandler, aWindowContext, aURI, aReason) {
     let window = null;
     try {
       if (aWindowContext)
         window = aWindowContext.getInterface(Ci.nsIDOMWindow);
     } catch (e) { /* it's OK to not have a window */ }
 
-    // The current list is based purely on the scheme. Redo the query using the url to get more
-    // specific results.
-    aHandler = this.protoSvc.getProtocolHandlerInfoFromOS(aURI.spec, {});
+    if (!aURI.schemeIs("content")) {
+
+      // The current list is based purely on the scheme. Redo the query using the url to get more
+      // specific results.
+      aHandler = this.protoSvc.getProtocolHandlerInfoFromOS(aURI.spec, {});
+
+      if (aHandler.possibleApplicationHandlers.length > 1) {
 
-    // The first handler in the set is the Android Application Chooser (which will fall back to a default if one is set)
-    // If we have more than one option, let the OS handle showing a list (if needed).
-    if (aHandler.possibleApplicationHandlers.length > 1) {
-      aHandler.launchWithURI(aURI, aWindowContext);
+        // The first handler in the set is the Android Application Chooser (which will fall back to a default if one is set)
+        // If we have more than one option, let the OS handle showing a list (if needed).
+        aHandler.launchWithURI(aURI, aWindowContext);
+        this._closeBlankWindow(window);
+        return;
+      }
+    }
+    // xpcshell tests do not have an Android Bridge but we require Android
+    // Bridge when using Messaging so we guard against this case. xpcshell
+    // tests also do not have a window, so we use this state to guard.
+    let win = this._getChromeWin();
+    if (!win) {
+      return;
+    }
+
+    let msg = {
+      type: "Intent:OpenNoHandler",
+      uri: aURI.spec,
+    };
+
+    EventDispatcher.instance.sendRequestForResult(msg).then(() => {
+      // Java opens an app on success: take no action.
       this._closeBlankWindow(window);
 
-    } else {
-      // xpcshell tests do not have an Android Bridge but we require Android
-      // Bridge when using Messaging so we guard against this case. xpcshell
-      // tests also do not have a window, so we use this state to guard.
-      let win = this._getChromeWin();
-      if (!win) {
+    }, (data) => {
+      if (data.isFallback) {
+        // We always want to open a fallback url
+        window.location.href = data.uri;
         return;
       }
 
-      let msg = {
-        type: "Intent:OpenNoHandler",
-        uri: aURI.spec,
-      };
-
-      EventDispatcher.instance.sendRequestForResult(msg).then(() => {
-        // Java opens an app on success: take no action.
+      // We couldn't open this. If this was from a click, it's likely that we just
+      // want this to fail silently. If the user entered this on the address bar, though,
+      // we want to show the neterror page.
+      let dwu = window.windowUtils;
+      let millis = dwu.millisSinceLastUserInput;
+      if (millis < 0 || millis >= 1000) {
+        window.docShell.displayLoadError(Cr.NS_ERROR_UNKNOWN_PROTOCOL, aURI, null);
+      } else {
         this._closeBlankWindow(window);
-
-      }, (data) => {
-        if (data.isFallback) {
-          // We always want to open a fallback url
-          window.location.href = data.uri;
-          return;
-        }
-
-        // We couldn't open this. If this was from a click, it's likely that we just
-        // want this to fail silently. If the user entered this on the address bar, though,
-        // we want to show the neterror page.
-        let dwu = window.windowUtils;
-        let millis = dwu.millisSinceLastUserInput;
-        if (millis < 0 || millis >= 1000) {
-          window.docShell.displayLoadError(Cr.NS_ERROR_UNKNOWN_PROTOCOL, aURI, null);
-        } else {
-          this._closeBlankWindow(window);
-        }
-      });
-    }
+      }
+    });
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentDispatchChooser]);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
@@ -1,14 +1,16 @@
 /* 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.util;
 
+import android.content.Context;
+import android.net.Uri;
 import android.util.Log;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.FilenameFilter;
@@ -22,18 +24,23 @@ import java.util.Random;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.RobocopTarget;
 
+import static org.mozilla.gecko.util.ContentUriUtils.getPath;
+
 public class FileUtils {
     private static final String LOGTAG = "GeckoFileUtils";
+    private static final String FILE_SCHEME = "file";
+    private static final String CONTENT_SCHEME = "content";
+    private static final String FILE_ABSOLUTE_URI = FILE_SCHEME + "://%s";
 
     /*
     * A basic Filter for checking a filename and age.
     **/
     static public class NameAndAgeFilter implements FilenameFilter {
         final private String mName;
         final private double mMaxAge;
 
@@ -274,9 +281,21 @@ public class FileUtils {
         }
         File result;
         Random random = new Random();
         do {
             result = new File(directory, prefix + random.nextInt());
         } while (!result.mkdirs());
         return result;
     }
+
+    public static String getFilePathFromUri(final Context context, final Uri uri) {
+        return String.format(FILE_ABSOLUTE_URI, getPath(context, uri));
+    }
+
+    public static boolean isContentUri(Uri uri) {
+        return uri != null && uri.getScheme() != null && CONTENT_SCHEME.equals(uri.getScheme());
+    }
+
+    public static boolean isContentUri(String sUri) {
+        return sUri != null && sUri.startsWith(CONTENT_SCHEME);
+    }
 }