Introduce intent manager.

The intent manager is an Android specific service that allows privileged
android mojo application to listen to external intents.

R=ppi@chromium.org

Review URL: https://codereview.chromium.org/1061313003
diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn
index d2ee4cd..4abfc3c 100644
--- a/mojo/public/java/BUILD.gn
+++ b/mojo/public/java/BUILD.gn
@@ -62,6 +62,7 @@
     "application/src/org/chromium/mojo/application/ApplicationImpl.java",
     "application/src/org/chromium/mojo/application/ApplicationRunner.java",
     "application/src/org/chromium/mojo/application/ServiceFactoryBinder.java",
+    "application/src/org/chromium/mojo/application/ShellHelper.java",
   ]
   deps = [
     ":bindings",
diff --git a/mojo/public/java/application/src/org/chromium/mojo/application/ShellHelper.java b/mojo/public/java/application/src/org/chromium/mojo/application/ShellHelper.java
new file mode 100644
index 0000000..705e22e
--- /dev/null
+++ b/mojo/public/java/application/src/org/chromium/mojo/application/ShellHelper.java
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.application;
+
+import org.chromium.mojo.bindings.Interface;
+import org.chromium.mojo.bindings.Interface.Proxy;
+import org.chromium.mojo.bindings.InterfaceRequest;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Pair;
+import org.chromium.mojom.mojo.ServiceProvider;
+import org.chromium.mojom.mojo.Shell;
+
+/**
+ * Helper class to help connecting to other application through the shell.
+ */
+public class ShellHelper {
+    /**
+     * Connects to a service in another application.
+     *
+     * @param core Implementation of the {@link Core} api.
+     * @param shell Instance of the shell.
+     * @param application URL to the application to use.
+     * @param manager {@link org.chromium.mojo.bindings.Interface.Manager} for the service to
+     *            connect to.
+     * @return a proxy to the service.
+     */
+    public static <I extends Interface, P extends Proxy> P connectToService(
+            Core core, Shell shell, String application, Interface.Manager<I, P> manager) {
+        Pair<ServiceProvider.Proxy, InterfaceRequest<ServiceProvider>> providerRequest =
+                ServiceProvider.MANAGER.getInterfaceRequest(core);
+        try (ServiceProvider.Proxy provider = providerRequest.first) {
+            shell.connectToApplication(application, providerRequest.second, null);
+            Pair<P, InterfaceRequest<I>> serviceRequest = manager.getInterfaceRequest(core);
+            provider.connectToService(manager.getName(), serviceRequest.second.passHandle());
+            return serviceRequest.first;
+        }
+    }
+}
diff --git a/services/android/BUILD.gn b/services/android/BUILD.gn
index 588b0f0..b4401be 100644
--- a/services/android/BUILD.gn
+++ b/services/android/BUILD.gn
@@ -4,6 +4,7 @@
 
 import("//build/config/android/rules.gni")
 import("//mojo/public/mojo_application.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
 
 shared_library("libjava_handler") {
   deps = [
@@ -60,3 +61,9 @@
   input_so = "$root_out_dir/lib.stripped/libjava_handler.so"
   input_dex_jar = dex_output_path
 }
+
+mojom("bindings") {
+  sources = [
+    "intent_receiver.mojom",
+  ]
+}
diff --git a/services/android/intent_receiver.mojom b/services/android/intent_receiver.mojom
new file mode 100644
index 0000000..a61df69
--- /dev/null
+++ b/services/android/intent_receiver.mojom
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[JavaPackage="org.chromium.mojo.intent"]
+module intent_receiver;
+
+// Service to interact with android intents.
+interface IntentReceiverManager {
+  // This method takes an |IntentReceiver| and returns a serialized intent.
+  // The serialized intent can be deserialized using an android parcel. The
+  // caller can then transform this intent into a PendingIntent using
+  // |PendingIntent#getService| and send it to another android application.
+  // Whenever the pending intent is executed, the receiver will be called with
+  // the content of the received intent. To be noted: this will fail if the
+  // received intent is active (contains either a Binder or a file descriptor).
+  RegisterReceiver(IntentReceiver receiver) => (array<uint8> intent);
+};
+
+// Receiver interface, to be used with
+// |IntentReceiverManager.RegisterReceiver|.
+interface IntentReceiver {
+  OnIntent(array<uint8> intent);
+};
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
index b84cba0..3b19445 100644
--- a/shell/BUILD.gn
+++ b/shell/BUILD.gn
@@ -178,6 +178,10 @@
       "android/android_handler_loader.h",
       "android/background_application_loader.cc",
       "android/background_application_loader.h",
+      "android/intent_receiver_manager_factory.cc",
+      "android/intent_receiver_manager_factory.h",
+      "android/intent_receiver_manager_impl.cc",
+      "android/intent_receiver_manager_impl.h",
       "android/keyboard_impl.cc",
       "android/keyboard_impl.h",
       "android/native_viewport_application_loader.cc",
@@ -191,6 +195,7 @@
       ":run_android_application_function",
       "//mojo/application:content_handler",
       "//mojo/services/keyboard/public/interfaces",
+      "//services/android:bindings",
       "//services/gles2",
       "//services/native_viewport:lib",
     ]
@@ -245,6 +250,7 @@
     sources = [
       "android/apk/src/org/chromium/mojo/shell/AndroidHandler.java",
       "android/apk/src/org/chromium/mojo/shell/Bootstrap.java",
+      "android/apk/src/org/chromium/mojo/shell/IntentReceiverRegistry.java",
       "android/apk/src/org/chromium/mojo/shell/Keyboard.java",
       "android/apk/src/org/chromium/mojo/shell/ShellMain.java",
       "android/tests/src/org/chromium/mojo/shell/ShellTestBase.java",
@@ -288,7 +294,9 @@
   android_library("java") {
     java_files = [
       "android/apk/src/org/chromium/mojo/shell/AndroidHandler.java",
+      "android/apk/src/org/chromium/mojo/shell/IntentReceiverService.java",
       "android/apk/src/org/chromium/mojo/shell/FileHelper.java",
+      "android/apk/src/org/chromium/mojo/shell/IntentReceiverRegistry.java",
       "android/apk/src/org/chromium/mojo/shell/Keyboard.java",
       "android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java",
       "android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java",
diff --git a/shell/android/android_handler.cc b/shell/android/android_handler.cc
index a29759d..47b95c7 100644
--- a/shell/android/android_handler.cc
+++ b/shell/android/android_handler.cc
@@ -100,6 +100,7 @@
 bool AndroidHandler::ConfigureIncomingConnection(
     ApplicationConnection* connection) {
   connection->AddService(&content_handler_factory_);
+  connection->AddService(&intent_receiver_manager_factory_);
   return true;
 }
 
diff --git a/shell/android/android_handler.h b/shell/android/android_handler.h
index 1e1ad96..3e7d98c 100644
--- a/shell/android/android_handler.h
+++ b/shell/android/android_handler.h
@@ -11,6 +11,7 @@
 #include "mojo/public/cpp/application/application_delegate.h"
 #include "mojo/public/cpp/application/interface_factory_impl.h"
 #include "mojo/services/content_handler/public/interfaces/content_handler.mojom.h"
+#include "shell/android/intent_receiver_manager_factory.h"
 
 namespace base {
 class FilePath;
@@ -35,6 +36,8 @@
                       URLResponsePtr response) override;
 
   ContentHandlerFactory content_handler_factory_;
+  IntentReceiverManagerFactory intent_receiver_manager_factory_;
+
   MOJO_DISALLOW_COPY_AND_ASSIGN(AndroidHandler);
 };
 
diff --git a/shell/android/apk/AndroidManifest.xml b/shell/android/apk/AndroidManifest.xml
index 9e368b9..ff3a3d2 100644
--- a/shell/android/apk/AndroidManifest.xml
+++ b/shell/android/apk/AndroidManifest.xml
@@ -27,6 +27,7 @@
                 <action android:name="android.intent.action.VIEW"/>
             </intent-filter>
         </activity>
+        <service android:name=".IntentReceiverService"/>
     </application>
 
 </manifest>
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/IntentReceiverRegistry.java b/shell/android/apk/src/org/chromium/mojo/shell/IntentReceiverRegistry.java
new file mode 100644
index 0000000..ba9ffda
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo/shell/IntentReceiverRegistry.java
@@ -0,0 +1,84 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.content.Intent;
+import android.os.Parcel;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Java helper class for services/android/intent_receiver.mojom. This class creates intents for
+ * privileged mojo applications and routes received intents back to the mojo application. The
+ * implementation of the IntentReceiverManager service is in C++, but calls back to this class to
+ * access the android framework.
+ */
+@JNINamespace("mojo::shell")
+public class IntentReceiverRegistry {
+    private static class LazyHolder {
+        private static final IntentReceiverRegistry INSTANCE = new IntentReceiverRegistry();
+    }
+
+    /**
+     * Returns the instance.
+     */
+    public static IntentReceiverRegistry getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    private final Map<String, Long> mReceiversByUuid = new HashMap<>();
+    private final Map<Long, String> mUuidsByReceiver = new HashMap<>();
+
+    private IntentReceiverRegistry() {}
+
+    public void onIntentReceived(Intent intent) {
+        String uuid = intent.getAction();
+        if (uuid == null) return;
+        Long ptr = mReceiversByUuid.get(uuid);
+        if (ptr == null) return;
+        nativeOnIntentReceived(ptr, intentToBuffer(intent));
+    }
+
+    private static ByteBuffer intentToBuffer(Intent intent) {
+        Parcel p = Parcel.obtain();
+        intent.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        byte[] b = p.marshall();
+        ByteBuffer result = ByteBuffer.allocateDirect(b.length);
+        result.put(b);
+        result.flip();
+        return result;
+    }
+
+    @CalledByNative
+    private static ByteBuffer registerReceiver(long intentDispatcher) {
+        IntentReceiverRegistry registry = getInstance();
+        String uuid = UUID.randomUUID().toString();
+        Intent intent = new Intent(
+                uuid, null, ApplicationStatus.getApplicationContext(), IntentReceiverService.class);
+        registry.mReceiversByUuid.put(uuid, intentDispatcher);
+        registry.mUuidsByReceiver.put(intentDispatcher, uuid);
+        return intentToBuffer(intent);
+    }
+
+    @CalledByNative
+    private static void unregisterReceiver(long intentDispatcher) {
+        IntentReceiverRegistry registry = getInstance();
+        String uuid = registry.mUuidsByReceiver.get(intentDispatcher);
+        if (uuid != null) {
+            registry.mUuidsByReceiver.remove(intentDispatcher);
+            registry.mReceiversByUuid.remove(uuid);
+        }
+    }
+
+    private static native void nativeOnIntentReceived(long intentDispatcher, ByteBuffer intent);
+}
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/IntentReceiverService.java b/shell/android/apk/src/org/chromium/mojo/shell/IntentReceiverService.java
new file mode 100644
index 0000000..0a7a3ad
--- /dev/null
+++ b/shell/android/apk/src/org/chromium/mojo/shell/IntentReceiverService.java
@@ -0,0 +1,25 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.mojo.shell;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+/**
+ * IntentService that will forward received intents to the {@link IntentReceiverRegistry}.
+ */
+public class IntentReceiverService extends IntentService {
+    public IntentReceiverService() {
+        super("IntentReceiverService");
+    }
+
+    /**
+     * @see IntentService#onHandleIntent(Intent)
+     */
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        IntentReceiverRegistry.getInstance().onIntentReceived(intent);
+    }
+}
diff --git a/shell/android/intent_receiver_manager_factory.cc b/shell/android/intent_receiver_manager_factory.cc
new file mode 100644
index 0000000..94ad166
--- /dev/null
+++ b/shell/android/intent_receiver_manager_factory.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shell/android/intent_receiver_manager_factory.h"
+
+namespace mojo {
+namespace shell {
+
+void IntentReceiverManagerFactory::Create(
+    ApplicationConnection* connection,
+    InterfaceRequest<intent_receiver::IntentReceiverManager> request) {
+  intent_manager_.Bind(request.Pass());
+}
+
+}  // namespace shell
+}  // namespace mojo
diff --git a/shell/android/intent_receiver_manager_factory.h b/shell/android/intent_receiver_manager_factory.h
new file mode 100644
index 0000000..775ce18
--- /dev/null
+++ b/shell/android/intent_receiver_manager_factory.h
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_ANDROID_INTENT_RECEIVER_MANAGER_FACTORY_H_
+#define SHELL_ANDROID_INTENT_RECEIVER_MANAGER_FACTORY_H_
+
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "services/android/intent_receiver.mojom.h"
+#include "shell/android/intent_receiver_manager_impl.h"
+
+namespace mojo {
+namespace shell {
+class IntentReceiverManagerFactory
+    : public InterfaceFactory<intent_receiver::IntentReceiverManager> {
+ private:
+  // From InterfaceFactory:
+  void Create(ApplicationConnection* connection,
+              InterfaceRequest<intent_receiver::IntentReceiverManager> request)
+      override;
+
+  IntentReceiverManagerImpl intent_manager_;
+};
+}  // namespace shell
+}  // namespace mojo
+
+#endif  // SHELL_ANDROID_INTENT_RECEIVER_MANAGER_FACTORY_H_
diff --git a/shell/android/intent_receiver_manager_impl.cc b/shell/android/intent_receiver_manager_impl.cc
new file mode 100644
index 0000000..2a89191
--- /dev/null
+++ b/shell/android/intent_receiver_manager_impl.cc
@@ -0,0 +1,82 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shell/android/intent_receiver_manager_impl.h"
+
+#include "base/android/jni_android.h"
+#include "jni/IntentReceiverRegistry_jni.h"
+#include "mojo/public/cpp/bindings/error_handler.h"
+
+namespace mojo {
+namespace shell {
+
+namespace {
+
+mojo::Array<uint8> BufferToArray(JNIEnv* env, jobject buffer) {
+  const size_t size = env->GetDirectBufferCapacity(buffer);
+  Array<uint8> result(size);
+  memcpy(&result.front(), env->GetDirectBufferAddress(buffer), size);
+  return result.Pass();
+}
+
+class IntentDispatcher : public ErrorHandler {
+ public:
+  IntentDispatcher(intent_receiver::IntentReceiverPtr intent_receiver)
+      : intent_receiver_(intent_receiver.Pass()) {
+    intent_receiver_.set_error_handler(this);
+  }
+
+  ~IntentDispatcher() {
+    Java_IntentReceiverRegistry_unregisterReceiver(
+        base::android::AttachCurrentThread(),
+        reinterpret_cast<uintptr_t>(this));
+  }
+
+  void OnIntentReceived(JNIEnv* env, jobject intent) {
+    intent_receiver_->OnIntent(BufferToArray(env, intent));
+  }
+
+ private:
+  // Overriden from ErrorHandler
+  void OnConnectionError() {
+    intent_receiver_.set_error_handler(nullptr);
+    delete this;
+  }
+
+  intent_receiver::IntentReceiverPtr intent_receiver_;
+};
+
+}  // namespace
+
+void IntentReceiverManagerImpl::Bind(
+    InterfaceRequest<intent_receiver::IntentReceiverManager> request) {
+  bindings_.AddBinding(this, request.Pass());
+}
+
+void IntentReceiverManagerImpl::RegisterReceiver(
+    intent_receiver::IntentReceiverPtr receiver,
+    const RegisterReceiverCallback& callback) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  base::android::ScopedJavaLocalRef<jobject> buffer =
+      Java_IntentReceiverRegistry_registerReceiver(
+          env,
+          reinterpret_cast<uintptr_t>(new IntentDispatcher(receiver.Pass())));
+  callback.Run(BufferToArray(env, buffer.obj()));
+}
+
+bool RegisterIntentReceiverRegistry(JNIEnv* env) {
+  return RegisterNativesImpl(env);
+}
+
+void OnIntentReceived(JNIEnv* env,
+                      jclass jcaller,
+                      jlong intent_dispatcher_ptr,
+                      jobject intent) {
+  IntentDispatcher* intent_dispatcher =
+      reinterpret_cast<IntentDispatcher*>(intent_dispatcher_ptr);
+  intent_dispatcher->OnIntentReceived(env, intent);
+}
+
+}  // namespace shell
+}  // namespace mojo
diff --git a/shell/android/intent_receiver_manager_impl.h b/shell/android/intent_receiver_manager_impl.h
new file mode 100644
index 0000000..769eee5
--- /dev/null
+++ b/shell/android/intent_receiver_manager_impl.h
@@ -0,0 +1,33 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_ANDROID_INTENT_RECEIVER_MANAGER_IMPL_H_
+#define SHELL_ANDROID_INTENT_RECEIVER_MANAGER_IMPL_H_
+
+#include "base/android/jni_android.h"
+#include "mojo/common/weak_binding_set.h"
+#include "services/android/intent_receiver.mojom.h"
+
+namespace mojo {
+namespace shell {
+
+class IntentReceiverManagerImpl
+    : public intent_receiver::IntentReceiverManager {
+ public:
+  void Bind(InterfaceRequest<intent_receiver::IntentReceiverManager> request);
+
+ private:
+  // Implementation of intent_receiver::IntentReceiverManager
+  void RegisterReceiver(intent_receiver::IntentReceiverPtr receiver,
+                        const RegisterReceiverCallback& callback) override;
+
+  WeakBindingSet<intent_receiver::IntentReceiverManager> bindings_;
+};
+
+bool RegisterIntentReceiverRegistry(JNIEnv* env);
+
+}  // namespace shell
+}  // namespace mojo
+
+#endif  // SHELL_ANDROID_INTENT_RECEIVER_MANAGER_IMPL_H_
diff --git a/shell/android/library_loader.cc b/shell/android/library_loader.cc
index d5230ac..8479b2f 100644
--- a/shell/android/library_loader.cc
+++ b/shell/android/library_loader.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "services/native_viewport/platform_viewport_android.h"
 #include "shell/android/android_handler.h"
+#include "shell/android/intent_receiver_manager_impl.h"
 #include "shell/android/keyboard_impl.h"
 #include "shell/android/main.h"
 
@@ -16,6 +17,7 @@
 
 base::android::RegistrationMethod kMojoRegisteredMethods[] = {
     {"AndroidHandler", mojo::shell::RegisterAndroidHandlerJni},
+    {"IntentReceiverRegistry", mojo::shell::RegisterIntentReceiverRegistry},
     {"Keyboard", mojo::shell::RegisterKeyboardJni},
     {"PlatformViewportAndroid",
      native_viewport::PlatformViewportAndroid::Register},