Bug 776027 - Map and launch web activities as Android intents. r=wesj
☠☠ backed out by c2f254dc00d2 ☠ ☠
authorJosh Dover <gerfuls@gmail.com>
Mon, 14 Apr 2014 17:48:00 +0200
changeset 197013 46b4833e84c3788ffbb898af6f10c0e02334293b
parent 197012 ec54368243da36dacccca850735876ea454a0fdf
child 197014 b90d50c9d8bea84dc93d8691ed00aa060eb1ac58
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs776027
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 776027 - Map and launch web activities as Android intents. r=wesj
mobile/android/base/IntentHelper.java
mobile/android/base/moz.build
mobile/android/base/util/JSONUtils.java
mobile/android/base/util/WebActivityMapper.java
--- a/mobile/android/base/IntentHelper.java
+++ b/mobile/android/base/IntentHelper.java
@@ -3,16 +3,17 @@
  * 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 org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.JSONUtils;
+import org.mozilla.gecko.util.WebActivityMapper;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
@@ -21,17 +22,18 @@ import android.util.Log;
 import java.util.Arrays;
 import java.util.List;
 
 public final class IntentHelper implements GeckoEventListener {
     private static final String LOGTAG = "GeckoIntentHelper";
     private static final String[] EVENTS = {
         "Intent:GetHandlers",
         "Intent:Open",
-        "Intent:OpenForResult"
+        "Intent:OpenForResult",
+        "WebActivity:Open"
     };
     private static IntentHelper instance;
 
     private Activity activity;
 
     private IntentHelper(Activity activity) {
         this.activity = activity;
         for (String event : EVENTS) {
@@ -63,16 +65,18 @@ public final class IntentHelper implemen
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Intent:GetHandlers")) {
                 getHandlers(message);
             } else if (event.equals("Intent:Open")) {
                 open(message);
             } else if (event.equals("Intent:OpenForResult")) {
                 openForResult(message);
+            } else if (event.equals("WebActivity:Open")) {
+                openWebActivity(message);
             }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     private void getHandlers(JSONObject message) throws JSONException {
         final Intent intent = GeckoAppShell.getOpenURIIntent(activity,
@@ -99,35 +103,41 @@ public final class IntentHelper implemen
     private void openForResult(final JSONObject message) throws JSONException {
         Intent intent = GeckoAppShell.getOpenURIIntent(activity,
                                                        message.optString("url"),
                                                        message.optString("mime"),
                                                        message.optString("action"),
                                                        message.optString("title"));
         intent.setClassName(message.optString("packageName"), message.optString("className"));
         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
+    }
 
+    private void openWebActivity(JSONObject message) throws JSONException {
+        final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
     }
 
     private static class ResultHandler implements ActivityResultHandler {
         private final JSONObject message;
 
         public ResultHandler(JSONObject message) {
             this.message = message;
         }
 
         @Override
-        public void onActivityResult (int resultCode, Intent data) {
+        public void onActivityResult(int resultCode, Intent data) {
             JSONObject response = new JSONObject();
 
             try {
                 if (data != null) {
                     response.put("extras", JSONUtils.bundleToJSON(data.getExtras()));
+                    response.put("uri", data.getData().toString());
                 }
+
                 response.put("resultCode", resultCode);
             } catch (JSONException e) {
                 Log.w(LOGTAG, "Error building JSON response.", e);
             }
 
             EventDispatcher.sendResponse(message, response);
         }
     }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -64,16 +64,17 @@ gujar.sources += [
     'util/NativeEventListener.java',
     'util/NativeJSContainer.java',
     'util/NativeJSObject.java',
     'util/NonEvictingLruCache.java',
     'util/ProxySelector.java',
     'util/StringUtils.java',
     'util/ThreadUtils.java',
     'util/UiAsyncTask.java',
+    'util/WebActivityMapper.java',
 ]
 gujar.extra_jars = [
     'gecko-mozglue.jar'
 ]
 gujar.javac_flags += ['-Xlint:all,-deprecation']
 
 stjar = add_java_jar('sync-thirdparty')
 stjar.sources += [ thirdparty_source_dir + f for f in sync_thirdparty_java_files ]
--- a/mobile/android/base/util/JSONUtils.java
+++ b/mobile/android/base/util/JSONUtils.java
@@ -52,21 +52,21 @@ public final class JSONUtils {
         try {
             json.put(name, uuidString);
         } catch (JSONException e) {
             throw new IllegalArgumentException(name + "=" + uuidString, e);
         }
     }
 
     public static JSONObject bundleToJSON(Bundle bundle) {
-        JSONObject json = new JSONObject();
-        if (bundle == null) {
-            return json;
+        if (bundle == null || bundle.isEmpty()) {
+            return null;
         }
 
+        JSONObject json = new JSONObject();
         for (String key : bundle.keySet()) {
             try {
                 json.put(key, bundle.get(key));
             } catch (JSONException e) {
                 Log.w(LOGTAG, "Error building JSON response.", e);
             }
         }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/util/WebActivityMapper.java
@@ -0,0 +1,151 @@
+/* -*- 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.util;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class WebActivityMapper {
+    private static final Map<String, WebActivityMapping> activityMap = new HashMap<String, WebActivityMapping>();
+    static {
+        activityMap.put("dial", new DialMapping());
+        activityMap.put("open", new OpenMapping());
+        activityMap.put("pick", new PickMapping());
+        activityMap.put("send", new SendMapping());
+        activityMap.put("view", new ViewMapping());
+    };
+
+    private static abstract class WebActivityMapping {
+        // Cannot return null
+        public abstract String getAction();
+
+        public String getMime(JSONObject data) throws JSONException {
+            return null;
+        }
+
+        public String getUri(JSONObject data) throws JSONException {
+            return null;
+        }
+
+        public void putExtras(JSONObject data, Intent intent) throws JSONException {}
+    }
+
+    /**
+     * Provides useful defaults for mime type and uri.
+     */
+    private static abstract class BaseMapping extends WebActivityMapping {
+        /**
+         * If 'type' is present in data object, uses the value as the MIME type.
+         */
+        public String getMime(JSONObject data) throws JSONException {
+            return data.optString("type", null);
+        }
+
+        /**
+         * If 'uri' or 'url' is present in data object, uses the respecitve value as the Uri.
+         */
+        public String getUri(JSONObject data) throws JSONException {
+            // Will return uri or url if present.
+            String uri = data.optString("uri", null);
+            return uri != null ? uri : data.optString("url", null);
+        }
+    }
+
+    public static Intent getIntentForWebActivity(JSONObject message) throws JSONException {
+        final String name = message.getString("name").toLowerCase();
+        final JSONObject data = message.getJSONObject("data");
+
+        final WebActivityMapping mapping = activityMap.get(name);
+        final Intent intent = new Intent(mapping.getAction());
+
+        final String mime = mapping.getMime(data);
+        if (!TextUtils.isEmpty(mime)) {
+            intent.setType(mime);
+        }
+
+        final String uri = mapping.getUri(data);
+        if (!TextUtils.isEmpty(uri)) {
+            intent.setData(Uri.parse(uri));
+        }
+
+        mapping.putExtras(data, intent);
+
+        return intent;
+    }
+
+    private static class DialMapping extends WebActivityMapping {
+        @Override
+        public String getAction() {
+            return Intent.ACTION_DIAL;
+        }
+
+        @Override
+        public String getUri(JSONObject data) throws JSONException {
+            return "tel:" + data.getString("number");
+        }
+    }
+
+    private static class OpenMapping extends BaseMapping {
+        @Override
+        public String getAction() {
+            return Intent.ACTION_VIEW;
+        }
+    }
+
+    private static class PickMapping extends BaseMapping {
+        @Override
+        public String getAction() {
+            return Intent.ACTION_GET_CONTENT;
+        }
+    }
+
+    private static class SendMapping extends BaseMapping {
+        @Override
+        public String getAction() {
+            return Intent.ACTION_SEND;
+        }
+
+        @Override
+        public void putExtras(JSONObject data, Intent intent) throws JSONException {
+            optPutExtra("text", Intent.EXTRA_TEXT, data, intent);
+            optPutExtra("html_text", Intent.EXTRA_HTML_TEXT, data, intent);
+            optPutExtra("stream", Intent.EXTRA_STREAM, data, intent);
+        }
+    }
+
+    private static class ViewMapping extends BaseMapping {
+        @Override
+        public String getAction() {
+            return Intent.ACTION_VIEW;
+        }
+
+        @Override
+        public String getMime(JSONObject data) {
+            // MozActivity adds a type 'url' here, we don't want to set the MIME to 'url'.
+            String type = data.optString("type", null);
+            if ("url".equals(type) || "uri".equals(type)) {
+                return null;
+            } else {
+                return type;
+            }
+        }
+    }
+
+    private static void optPutExtra(String key, String extraName, JSONObject data, Intent intent) {
+        final String extraValue = data.optString(key);
+        if (!TextUtils.isEmpty(extraValue)) {
+            intent.putExtra(extraName, extraValue);
+        }
+    }
+}