Introduce authentication service.

This service allows to request oauth2 token. The only current
implementation is for Android.

Also the implementation is currently using a single project for
authentication.

R=ppi@chromium.org, tonyg@chromium.org
BUG=https://github.com/domokit/mojo/issues/94

Review URL: https://codereview.chromium.org/1116653002
diff --git a/services/BUILD.gn b/services/BUILD.gn
index 86ee32f..d0a487b 100644
--- a/services/BUILD.gn
+++ b/services/BUILD.gn
@@ -30,6 +30,7 @@
   if (is_android) {
     deps += [
       "//services/android:java_handler",
+      "//services/authentication",
       "//services/location",
       "//services/sensors",
     ]
diff --git a/services/android/intent_receiver.mojom b/services/android/intent_receiver.mojom
index a61df69..caa2f3f 100644
--- a/services/android/intent_receiver.mojom
+++ b/services/android/intent_receiver.mojom
@@ -12,13 +12,23 @@
   // 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);
+  // 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).
+  RegisterIntentReceiver(IntentReceiver receiver) => (array<uint8>? intent);
+
+  // This method takes an |IntentReceiver| and returns a serialized intent.
+  // The serialized intent can be deserialized using an android parcel. The
+  // caller can then add an intent it wants the system to send using
+  // |Activity#startActivityForResult| and then send it using
+  // |Context#startService|. Whenever the started activity sends a result, the
+  // receiver will be called with the content of the received intent. If the
+  // activity is cancelled, the receiver will be closed.
+  RegisterActivityResultReceiver(IntentReceiver receiver) =>
+      (array<uint8>? intent);
 };
 
-// Receiver interface, to be used with
-// |IntentReceiverManager.RegisterReceiver|.
+// Receiver interface, to be used with |IntentReceiverManager|.
 interface IntentReceiver {
   OnIntent(array<uint8> intent);
 };
diff --git a/services/authentication/BUILD.gn b/services/authentication/BUILD.gn
new file mode 100644
index 0000000..146942c
--- /dev/null
+++ b/services/authentication/BUILD.gn
@@ -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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+if (is_android) {
+  import("//services/android/rules.gni")
+  import("//build/config/android/config.gni")
+
+  mojo_android_java_application("authentication") {
+    sources = [
+      "src/org/chromium/mojo/authentication/AuthenticationApp.java",
+      "src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java",
+    ]
+
+    mojo_main = "org.chromium.mojo.authentication.AuthenticationApp"
+
+    deps = [
+      ":bindings_java",
+      "//mojo/public/interfaces/application:application_java",
+      "//mojo/public/java:application",
+      "//services/android:bindings_java",
+      "//third_party/android_tools:google_play_services_default_java",
+    ]
+  }
+}
+
+mojom("bindings") {
+  sources = [
+    "authentication.mojom",
+  ]
+}
diff --git a/services/authentication/authentication.mojom b/services/authentication/authentication.mojom
new file mode 100644
index 0000000..a69e7de
--- /dev/null
+++ b/services/authentication/authentication.mojom
@@ -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.
+
+[JavaPackage="org.chromium.mojo.authentication"]
+module authentication;
+
+// Interface to handle user identity and authentication tokens.
+// TODO(qsr): This API only handles google accounts at this time. It will need
+//            to be extended to allow generic account manager on the platform.
+interface AuthenticationService {
+  // Requests a Google account to use. In case of success, error will be null.
+  // In case of error, username will be null and error will contain a
+  // description of the error.
+  SelectAccount() => (string? username, string? error);
+
+  // Requests an oauth2 token for the given Google account with the given
+  // scopes.  In case of error, username will be null and error will contain a
+  // description of the error.
+  GetOAuth2Token(string username, array<string> scopes) =>
+      (string? token, string? error);
+
+  // Requests to clear a previously acquired token. This should be called when a
+  // token is refused by a server component before requesting a new token to
+  // clear the token from any cache.
+  ClearOAuth2Token(string token);
+};
diff --git a/services/authentication/src/org/chromium/mojo/authentication/AuthenticationApp.java b/services/authentication/src/org/chromium/mojo/authentication/AuthenticationApp.java
new file mode 100644
index 0000000..3737648
--- /dev/null
+++ b/services/authentication/src/org/chromium/mojo/authentication/AuthenticationApp.java
@@ -0,0 +1,70 @@
+// 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.authentication;
+
+import android.content.Context;
+
+import org.chromium.mojo.application.ApplicationConnection;
+import org.chromium.mojo.application.ApplicationDelegate;
+import org.chromium.mojo.application.ApplicationRunner;
+import org.chromium.mojo.application.ServiceFactoryBinder;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MessagePipeHandle;
+import org.chromium.mojom.mojo.Shell;
+
+/**
+ * Android service application implementing the AuthenticationService interface.
+ */
+public class AuthenticationApp implements ApplicationDelegate {
+    private final Context mContext;
+    private final Core mCore;
+    private Shell mShell;
+
+    public AuthenticationApp(Context context, Core core) {
+        mContext = context;
+        mCore = core;
+    }
+
+    /**
+     * @see ApplicationDelegate#initialize(Shell, String[], String)
+     */
+    @Override
+    public void initialize(Shell shell, String[] args, String url) {
+        mShell = shell;
+    }
+
+    /**
+     * @see ApplicationDelegate#configureIncomingConnection(ApplicationConnection)
+     */
+    @Override
+    public boolean configureIncomingConnection(final ApplicationConnection connection) {
+        connection.addService(new ServiceFactoryBinder<AuthenticationService>() {
+
+            @Override
+            public void bindNewInstanceToMessagePipe(MessagePipeHandle pipe) {
+                AuthenticationService.MANAGER.bind(new AuthenticationServiceImpl(mContext, mCore,
+                                                           connection.getRequestorUrl(), mShell),
+                        pipe);
+            }
+
+            @Override
+            public String getInterfaceName() {
+                return AuthenticationService.MANAGER.getName();
+            }
+        });
+        return true;
+    }
+
+    /**
+     * @see ApplicationDelegate#quit()
+     */
+    @Override
+    public void quit() {}
+
+    public static void mojoMain(
+            Context context, Core core, MessagePipeHandle applicationRequestHandle) {
+        ApplicationRunner.run(new AuthenticationApp(context, core), core, applicationRequestHandle);
+    }
+}
diff --git a/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java b/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java
new file mode 100644
index 0000000..90be06b
--- /dev/null
+++ b/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java
@@ -0,0 +1,195 @@
+// 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.authentication;
+
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+
+import com.google.android.gms.auth.GoogleAuthException;
+import com.google.android.gms.auth.GoogleAuthUtil;
+import com.google.android.gms.auth.UserRecoverableAuthException;
+import com.google.android.gms.common.AccountPicker;
+
+import org.chromium.mojo.application.ShellHelper;
+import org.chromium.mojo.bindings.SideEffectFreeCloseable;
+import org.chromium.mojo.intent.IntentReceiver;
+import org.chromium.mojo.intent.IntentReceiverManager;
+import org.chromium.mojo.intent.IntentReceiverManager.RegisterActivityResultReceiverResponse;
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.MojoException;
+import org.chromium.mojom.mojo.Shell;
+
+import java.io.IOException;
+
+/**
+ * Implementation of AuthenticationService from services/authentication/authentication.mojom
+ */
+public class AuthenticationServiceImpl
+        extends SideEffectFreeCloseable implements AuthenticationService {
+    /**
+     * An callback that takes a serialized intent, add the intent the shell needs to send and start
+     * the container intent.
+     */
+    private final class RegisterActivityResultReceiverCallback
+            implements RegisterActivityResultReceiverResponse {
+        /**
+         * The intent that the requesting application needs to be run by shell on its
+behalf.
+         */
+        private final Intent mIntent;
+
+        private RegisterActivityResultReceiverCallback(Intent intent) {
+            mIntent = intent;
+        }
+
+        /**
+         * @see RegisterActivityResultReceiverResponse#call(byte[])
+         */
+        @Override
+        public void call(byte[] serializedIntent) {
+            Intent trampolineIntent = bytesToIntent(serializedIntent);
+            trampolineIntent.putExtra("intent", mIntent);
+            mContext.startService(trampolineIntent);
+        }
+    }
+
+    private final Activity mContext;
+    private final String mConsumerURL;
+    private final IntentReceiverManager mIntentReceiverManager;
+
+    public AuthenticationServiceImpl(Context context, Core core, String consumerURL, Shell shell) {
+        mContext = (Activity) context;
+        mConsumerURL = consumerURL;
+        mIntentReceiverManager = ShellHelper.connectToService(
+                core, shell, "mojo:android_handler", IntentReceiverManager.MANAGER);
+    }
+
+    /**
+     * @see AuthenticationService#onConnectionError(MojoException)
+     */
+    @Override
+    public void onConnectionError(MojoException e) {}
+
+    /**
+     * @see AuthenticationService#getOAuth2Token(String, String[],
+     *      AuthenticationService.GetOAuth2TokenResponse)
+     */
+    @Override
+    public void getOAuth2Token(
+            final String username, final String[] scopes, final GetOAuth2TokenResponse callback) {
+        if (scopes.length == 0) {
+            callback.call(null, "scopes cannot be empty");
+            return;
+        }
+        StringBuilder scope = new StringBuilder("oauth2:");
+        for (int i = 0; i < scopes.length; ++i) {
+            if (i > 0) {
+                scope.append(" ");
+            }
+            scope.append(scopes[i]);
+        }
+        try {
+            callback.call(GoogleAuthUtil.getToken(mContext, username, scope.toString()), null);
+        } catch (final UserRecoverableAuthException e) {
+            // If an error occurs that the user can recover from, this exception will contain the
+            // intent to run to allow the user to act. This will use the intent manager to start it.
+            mIntentReceiverManager.registerActivityResultReceiver(new IntentReceiver() {
+                GetOAuth2TokenResponse mPendingCallback = callback;
+
+                @Override
+                public void close() {
+                    call(null, "User denied the request.");
+                }
+
+                @Override
+                public void onConnectionError(MojoException e) {
+                    call(null, e.getMessage());
+                }
+
+                @Override
+                public void onIntent(byte[] bytes) {
+                    if (mPendingCallback == null) {
+                        return;
+                    }
+                    getOAuth2Token(username, scopes, mPendingCallback);
+                    mPendingCallback = null;
+                }
+
+                private void call(String token, String error) {
+                    if (mPendingCallback == null) {
+                        return;
+                    }
+                    mPendingCallback.call(token, error);
+                    mPendingCallback = null;
+                }
+            }, new RegisterActivityResultReceiverCallback(e.getIntent()));
+            return;
+        } catch (IOException | GoogleAuthException e) {
+            // Unrecoverable error.
+            callback.call(null, e.getMessage());
+        }
+    }
+
+    /**
+     * @see AuthenticationService#selectAccount(AuthenticationService.SelectAccountResponse)
+     */
+    @Override
+    public void selectAccount(final SelectAccountResponse callback) {
+        String[] accountTypes = new String[] {"com.google"};
+        String message = "Select an account to use with application: " + mConsumerURL;
+        Intent accountPickerIntent = AccountPicker.newChooseAccountIntent(
+                null, null, accountTypes, false, message, null, null, null);
+
+        mIntentReceiverManager.registerActivityResultReceiver(new IntentReceiver() {
+            SelectAccountResponse mPendingCallback = callback;
+
+            @Override
+            public void close() {
+                call(null, "User denied the request.");
+            }
+
+            @Override
+            public void onConnectionError(MojoException e) {
+                call(null, e.getMessage());
+            }
+
+            @Override
+            public void onIntent(byte[] bytes) {
+                Intent intent = bytesToIntent(bytes);
+                call(intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME), null);
+            }
+
+            private void call(String username, String error) {
+                if (mPendingCallback == null) {
+                    return;
+                }
+                mPendingCallback.call(username, error);
+                mPendingCallback = null;
+            }
+        }, new RegisterActivityResultReceiverCallback(accountPickerIntent));
+    }
+
+    /**
+     * @see AuthenticationService#clearOAuth2Token(String)
+     */
+    @Override
+    public void clearOAuth2Token(String token) {
+        try {
+            GoogleAuthUtil.clearToken(mContext, token);
+        } catch (GoogleAuthException | IOException e) {
+            // Nothing to do.
+        }
+    }
+
+    private static Intent bytesToIntent(byte[] bytes) {
+        Parcel p = Parcel.obtain();
+        p.unmarshall(bytes, 0, bytes.length);
+        p.setDataPosition(0);
+        return Intent.CREATOR.createFromParcel(p);
+    }
+}
diff --git a/services/device_info/src/org/chromium/services/device_info/DeviceInfoService.java b/services/device_info/src/org/chromium/services/device_info/DeviceInfoService.java
index f7747b8..d0fe6ef 100644
--- a/services/device_info/src/org/chromium/services/device_info/DeviceInfoService.java
+++ b/services/device_info/src/org/chromium/services/device_info/DeviceInfoService.java
@@ -77,8 +77,7 @@
      * @see ApplicationDelegate#configureIncomingConnection(String, ApplicationConnection)
      */
     @Override
-    public boolean configureIncomingConnection(
-            final String requestorUrl, ApplicationConnection connection) {
+    public boolean configureIncomingConnection(ApplicationConnection connection) {
         final DeviceInfo info = this;
         connection.addService(new ServiceFactoryBinder<DeviceInfo>() {
             @Override
diff --git a/services/location/src/org/chromium/services/location/LocationServiceApp.java b/services/location/src/org/chromium/services/location/LocationServiceApp.java
index 545b927..685b8fa 100644
--- a/services/location/src/org/chromium/services/location/LocationServiceApp.java
+++ b/services/location/src/org/chromium/services/location/LocationServiceApp.java
@@ -98,11 +98,10 @@
     }
 
     /**
-     * @see ApplicationDelegate#configureIncomingConnection(String, ApplicationConnection)
+     * @see ApplicationDelegate#configureIncomingConnection(ApplicationConnection)
      */
     @Override
-    public boolean configureIncomingConnection(
-            final String requestorUrl, ApplicationConnection connection) {
+    public boolean configureIncomingConnection(ApplicationConnection connection) {
         connection.addService(new ServiceFactoryBinder<LocationService>() {
             @Override
             public void bindNewInstanceToMessagePipe(MessagePipeHandle pipe) {
diff --git a/services/sensors/src/org/chromium/mojo/sensors/Sensors.java b/services/sensors/src/org/chromium/mojo/sensors/Sensors.java
index 526d78f..b182a00 100644
--- a/services/sensors/src/org/chromium/mojo/sensors/Sensors.java
+++ b/services/sensors/src/org/chromium/mojo/sensors/Sensors.java
@@ -35,8 +35,7 @@
      * @see ApplicationDelegate#configureIncomingConnection(String, ApplicationConnection)
      */
     @Override
-    public boolean configureIncomingConnection(
-            final String requestorUrl, ApplicationConnection connection) {
+    public boolean configureIncomingConnection(ApplicationConnection connection) {
         connection.addService(new ServiceFactoryBinder<SensorService>() {
             @Override
             public void bindNewInstanceToMessagePipe(MessagePipeHandle pipe) {