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 180908 774890574009c36aaa0bb47301dde18ae3f7cc82
parent 180907 fac267a41e5c011df9f92cbf2688e9bfba2e05bb
child 180909 8d73271599832c379e8665dc2cca566a68fafb88
push id6576
push usercbook@mozilla.com
push dateWed, 30 Apr 2014 06:53:24 +0000
treeherderfx-team@8d7327159983 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs776027
milestone32.0a1
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);
+        }
+    }
+}