Bug 776027 - Map and launch web activities as Android intents. r=wesj
authorJosh Dover <gerfuls@gmail.com>
Fri, 25 Apr 2014 16:29:00 +0200
changeset 180951 774890574009c36aaa0bb47301dde18ae3f7cc82
parent 180950 fac267a41e5c011df9f92cbf2688e9bfba2e05bb
child 180952 8d73271599832c379e8665dc2cca566a68fafb88
push id26687
push usercbook@mozilla.com
push dateWed, 30 Apr 2014 13:02:09 +0000
treeherdermozilla-central@727238c13756 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs776027
milestone32.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/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
@@ -65,16 +65,17 @@ gujar.sources += [
     'util/NativeJSContainer.java',
     'util/NativeJSObject.java',
     'util/NonEvictingLruCache.java',
     'util/ProxySelector.java',
     'util/RawResource.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 ]
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);
+        }
+    }
+}