Initial implementation of the Auto-Fill Framework classes.
This CL provides the initial, skeleton implementation of the Auto-Fill
Framework classes:
- Defines the system service and app-based
AIDL (IAutoFillManagerService.aidl and IAutoFillService.aidl respectively).
- Defines the 'adb shell cmd' interface.
- Defines the permission required to access the service.
- Registers the service on SystemServer.
- Adds the code to bind the app-specified service to system_server.
- Defines the service class (AutoFillService) required by providers.
- Implements the initial startSession() method.
This is still a very early, "work-in-progress" change:
- It has many TODOs.
- It does not have unit or CTS tests yet.
- It does not provide a callback method to auto-fill the fields.
- In fact, it has a lot of TODOs.
Despite these adversities, it can be tested by following the steps
below:
1.Create an app with a service extending AutoFillService
2.Implement the onNewSession() method
3.In the manifest:
- Listen to android.service.autofill.AutoFillService intents.
- Require the android.permission.BIND_AUTO_FILL permission.
4.Explicitly set the app as an autofill-service by running:
adb shell settings put secure auto_fill_service MY_APP/.MY_SERVICE
5.Start a session against the top activity:
adb shell cmd autofill start session
BUG: 31001899
Test: manually built and ran it
Change-Id: I00f4822159b31ddddba8f513e57c4474bc74eb89
diff --git a/Android.mk b/Android.mk
index 6f96803..bd04214 100644
--- a/Android.mk
+++ b/Android.mk
@@ -251,6 +251,8 @@
core/java/android/os/storage/IObbActionListener.aidl \
core/java/android/security/IKeystoreService.aidl \
core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl \
+ core/java/android/service/autofill/IAutoFillManagerService.aidl \
+ core/java/android/service/autofill/IAutoFillService.aidl \
core/java/android/service/carrier/ICarrierService.aidl \
core/java/android/service/carrier/ICarrierMessagingCallback.aidl \
core/java/android/service/carrier/ICarrierMessagingService.aidl \
diff --git a/api/current.txt b/api/current.txt
index f70458b..9a1a4f2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18,6 +18,7 @@
field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
+ field public static final java.lang.String BIND_AUTO_FILL = "android.permission.BIND_AUTO_FILL";
field public static final deprecated java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
field public static final java.lang.String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
@@ -34628,6 +34629,20 @@
}
+package android.service.autofill {
+
+ public abstract class AutoFillService extends android.app.Service {
+ ctor public AutoFillService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNewSession(java.lang.String, android.os.Bundle, int, android.app.assist.AssistStructure);
+ method public void onReady();
+ method public void onSessionFinished(java.lang.String);
+ method public void onShutdown();
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ }
+
+}
+
package android.service.carrier {
public class CarrierIdentifier implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 6622ea5..a41a132 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -28,6 +28,7 @@
field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
+ field public static final java.lang.String BIND_AUTO_FILL = "android.permission.BIND_AUTO_FILL";
field public static final deprecated java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
field public static final java.lang.String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
@@ -130,6 +131,7 @@
field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS";
field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
+ field public static final java.lang.String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -37396,6 +37398,20 @@
}
+package android.service.autofill {
+
+ public abstract class AutoFillService extends android.app.Service {
+ ctor public AutoFillService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNewSession(java.lang.String, android.os.Bundle, int, android.app.assist.AssistStructure);
+ method public void onReady();
+ method public void onSessionFinished(java.lang.String);
+ method public void onShutdown();
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ }
+
+}
+
package android.service.carrier {
public class CarrierIdentifier implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index de57dba..9393e1d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -18,6 +18,7 @@
field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
+ field public static final java.lang.String BIND_AUTO_FILL = "android.permission.BIND_AUTO_FILL";
field public static final deprecated java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
field public static final java.lang.String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
@@ -32764,6 +32765,7 @@
field public static final java.lang.String ALLOWED_GEOLOCATION_ORIGINS = "allowed_geolocation_origins";
field public static final deprecated java.lang.String ALLOW_MOCK_LOCATION = "mock_location";
field public static final java.lang.String ANDROID_ID = "android_id";
+ field public static final java.lang.String AUTO_FILL_SERVICE = "auto_fill_service";
field public static final deprecated java.lang.String BACKGROUND_DATA = "background_data";
field public static final deprecated java.lang.String BLUETOOTH_ON = "bluetooth_on";
field public static final android.net.Uri CONTENT_URI;
@@ -34717,6 +34719,20 @@
}
+package android.service.autofill {
+
+ public abstract class AutoFillService extends android.app.Service {
+ ctor public AutoFillService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNewSession(java.lang.String, android.os.Bundle, int, android.app.assist.AssistStructure);
+ method public void onReady();
+ method public void onSessionFinished(java.lang.String);
+ method public void onShutdown();
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+ }
+
+}
+
package android.service.carrier {
public class CarrierIdentifier implements android.os.Parcelable {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 322cc7b..3964e0a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3338,6 +3338,14 @@
public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
/**
+ * Official published name of the (internal) auto-fill service.
+ *
+ * @hide
+ * @see #getSystemService
+ */
+ public static final String AUTO_FILL_MANAGER_SERVICE = "autofill";
+
+ /**
* Use with {@link #getSystemService} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1b905a0..73eeccd 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4715,6 +4715,13 @@
public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
/**
+ * The currently selected auto-fill service flattened ComponentName.
+ * @hide
+ */
+ @TestApi
+ public static final String AUTO_FILL_SERVICE = "auto_fill_service";
+
+ /**
* bluetooth HCI snoop log configuration
* @hide
*/
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
new file mode 100644
index 0000000..14ce009b
--- /dev/null
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.app.assist.AssistStructure;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Top-level service of the current auto-fill service for a given user.
+ */
+// TODO: expand documentation
+public abstract class AutoFillService extends Service {
+
+ private static final String TAG = "AutoFillService";
+ private static final boolean DEBUG = true; // TODO: set to false once stable
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_AUTO_FILL} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
+
+ private static final int MSG_READY = 1;
+ private static final int MSG_NEW_SESSION = 2;
+ private static final int MSG_SESSION_FINISHED = 3;
+ private static final int MSG_SHUTDOWN = 4;
+
+ // TODO: add metadata?
+
+ private final IAutoFillService mInterface = new IAutoFillService.Stub() {
+ @Override
+ public void ready() {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_READY));
+ }
+
+ @Override
+ public void newSession(String token, Bundle data, int flags,
+ AssistStructure structure) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(MSG_NEW_SESSION,
+ flags, token, data, structure));
+ }
+
+ @Override
+ public void finishSession(String token) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_SESSION_FINISHED, token));
+ }
+
+ @Override
+ public void shutdown() {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_SHUTDOWN));
+ }
+ };
+
+ private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() {
+
+ @Override
+ public void executeMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_READY: {
+ onReady();
+ break;
+ } case MSG_NEW_SESSION: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int flags = args.argi1;
+ final String token = (String) args.arg1;
+ final Bundle data = (Bundle) args.arg2;
+ final AssistStructure assistStructure = (AssistStructure) args.arg3;
+ onNewSession(token, data, flags, assistStructure);
+ break;
+ } case MSG_SESSION_FINISHED: {
+ final String token = (String) msg.obj;
+ onSessionFinished(token);
+ break;
+ } case MSG_SHUTDOWN: {
+ onShutdown();
+ break;
+ } default: {
+ Log.w(TAG, "MyCallbacks received invalid message type: " + msg);
+ }
+ }
+ }
+ };
+
+ private HandlerCaller mHandlerCaller;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ return null;
+ }
+
+ /**
+ * Called during service initialization to tell you when the system is ready
+ * to receive interaction from it.
+ *
+ * <p>You should generally do initialization here rather than in {@link #onCreate}.
+ *
+ * <p>Sub-classes should call it first, since it sets the reference to the sytem-server service.
+ */
+ // TODO: rename to onConnected() / add onDisconnected()?
+ public void onReady() {
+ if (DEBUG) Log.d(TAG, "onReady()");
+ }
+
+ /**
+ * Called to receive data from the application that the user was requested auto-fill for.
+ *
+ * @param token unique token identifying the auto-fill session, it should be used when providing
+ * the auto-filled fields.
+ * @param data Arbitrary data supplied by the app through
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+ * May be {@code null} if data has been disabled by the user or device policy.
+ * @param startFlags currently always 0.
+ * @param structure If available, the structure definition of all windows currently
+ * displayed by the app. May be {@code null} if auto-fill data has been disabled by the user
+ * or device policy; will be an empty stub if the application has disabled auto-fill
+ * by marking its window as secure.
+ */
+ @SuppressWarnings("unused")
+ // TODO: take the factory approach where this method return a session, and move the callback
+ // methods (like autofill()) to the session.
+ public void onNewSession(String token, Bundle data, int startFlags, AssistStructure structure) {
+ if (DEBUG) Log.d(TAG, "onNewSession(): token=" + token);
+ }
+
+ /**
+ * Called when an auto-fill session is finished.
+ */
+ @SuppressWarnings("unused")
+ public void onSessionFinished(String token) {
+ if (DEBUG) Log.d(TAG, "onSessionFinished(): token=" + token);
+ }
+
+ /**
+ * Called during service de-initialization to tell you when the system is shutting the
+ * service down.
+ *
+ * <p> At this point this service may no longer be an active {@link AutoFillService}.
+ */
+ public void onShutdown() {
+ if (DEBUG) Log.d(TAG, "onShutdown()");
+ }
+}
diff --git a/core/java/android/service/autofill/AutoFillServiceInfo.java b/core/java/android/service/autofill/AutoFillServiceInfo.java
new file mode 100644
index 0000000..fe21615
--- /dev/null
+++ b/core/java/android/service/autofill/AutoFillServiceInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.autofill;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+
+/** @hide */
+public final class AutoFillServiceInfo {
+
+ private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
+ throws PackageManager.NameNotFoundException {
+ try {
+ final ServiceInfo si =
+ AppGlobals.getPackageManager().getServiceInfo(comp, 0, userHandle);
+ if (si != null) {
+ return si;
+ }
+ } catch (RemoteException e) {
+ }
+ throw new PackageManager.NameNotFoundException(comp.toString());
+ }
+
+ private String mParseError;
+
+ private ServiceInfo mServiceInfo;
+
+ private AutoFillServiceInfo(ServiceInfo si) {
+ if (si == null) {
+ mParseError = "Service not available";
+ return;
+ }
+ if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) {
+ mParseError = "Service does not require permission "
+ + Manifest.permission.BIND_AUTO_FILL;
+ return;
+ }
+
+ mServiceInfo = si;
+ }
+
+ public AutoFillServiceInfo(ComponentName comp, int userHandle)
+ throws PackageManager.NameNotFoundException {
+ this(getServiceInfoOrThrow(comp, userHandle));
+ }
+
+ public String getParseError() {
+ return mParseError;
+ }
+
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+}
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
new file mode 100644
index 0000000..2c06234
--- /dev/null
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.Bundle;
+
+/**
+ * Intermediator between apps being auto-filled and auto-fill service implementations.
+ *
+ * {@hide}
+ */
+interface IAutoFillManagerService {
+
+ /**
+ * Starts an auto-fill session for the top activities for a given user.
+ *
+ * It's used to start a new session from system affordances.
+ *
+ * @param userId user handle.
+ * @param args the bundle to pass as arguments to the voice interaction session.
+ * @param flags flags indicating optional session behavior.
+ * @param activityToken optional token of activity that needs to be on top.
+ *
+ * @return session token, or null if session was not created (for example, if the activity's
+ * user does not have an auto-fill service associated with).
+ */
+ // TODO: pass callback providing an onAutoFill() method
+ String startSession(int userId, in Bundle args, int flags, IBinder activityToken);
+
+ /**
+ * Finishes an auto-fill session.
+ *
+ * @param userId user handle.
+ * @param token session token.
+ *
+ * @return true if session existed and was finished.
+ */
+ boolean finishSession(int userId, String token);
+
+}
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
new file mode 100644
index 0000000..73d8d5d
--- /dev/null
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.Bundle;
+import android.app.assist.AssistStructure;
+
+/**
+ * @hide
+ */
+oneway interface IAutoFillService {
+ void ready();
+ void newSession(String token, in Bundle data, int flags, in AssistStructure structure);
+ void finishSession(String token);
+ void shutdown();
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4eebea6..3ec9de3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2323,6 +2323,13 @@
<permission android:name="android.permission.BIND_VOICE_INTERACTION"
android:protectionLevel="signature" />
+ <!-- Must be required by a {@link android.service.autofill.AutoFillService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_AUTO_FILL"
+ android:protectionLevel="signature" />
+
<!-- Must be required by hotword enrollment application,
to ensure that only the system can interact with it.
@hide <p>Not for use by third-party applications.</p> -->
@@ -3118,6 +3125,11 @@
<permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to manage auto-fill sessions.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_AUTO_FILL"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 227d0e9..d4c7c7a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -112,6 +112,7 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+ <uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
<!-- Permission needed to rename bugreport notifications (so they're not shown as Shell) -->
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<!-- Permission needed to hold a wakelock in dumpstate.cpp (drop_root_user()) -->
diff --git a/services/Android.mk b/services/Android.mk
index 3385bed..2911983 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -22,6 +22,7 @@
core \
accessibility \
appwidget \
+ autofill \
backup \
devicepolicy \
midi \
diff --git a/services/autofill/Android.mk b/services/autofill/Android.mk
new file mode 100644
index 0000000..a1f19fd
--- /dev/null
+++ b/services/autofill/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.autofill
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
new file mode 100644
index 0000000..3b41877
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static android.Manifest.permission.MANAGE_AUTO_FILL;
+import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.autofill.IAutoFillManagerService;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.FgThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Entry point service for auto-fill management.
+ *
+ * <p>This service provides the {@link IAutoFillManagerService} implementation and keeps a list of
+ * {@link AutoFillManagerServiceImpl} per user; the real work is done by
+ * {@link AutoFillManagerServiceImpl} itself.
+ */
+public final class AutoFillManagerService extends SystemService {
+
+ private static final String TAG = "AutoFillManagerService";
+ private static final boolean DEBUG = true; // TODO: change to false once stable
+
+ private final AutoFillManagerServiceStub mServiceStub;
+ private final Context mContext;
+ private final ContentResolver mResolver;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mSafeMode;
+
+ /**
+ * Map of {@link AutoFillManagerServiceImpl} per user id.
+ * <p>
+ * It has to be mapped by user id because the same current user could have simultaneous sessions
+ * associated to different user profiles (for example, in a multi-window environment).
+ * <p>
+ * This map is filled on demand in the following scenarios:
+ * <ol>
+ * <li>On start, it sets the value for the default user.
+ * <li>When an auto-fill service app is removed, its entries are removed.
+ * <li>When the current user changes.
+ * <li>When the {@link android.provider.Settings.Secure#AUTO_FILL_SERVICE} changes.
+ * </ol>
+ */
+ // TODO: make sure all cases listed above are handled
+ // TODO: should entries be removed when there is no section and have not be used for a while?
+ @GuardedBy("mLock")
+ private SparseArray<AutoFillManagerServiceImpl> mImplByUser = new SparseArray<>();
+
+ // TODO: should disable it on low-memory devices? if not, this attribute should be removed...
+ private final boolean mEnableService = true;
+
+ public AutoFillManagerService(Context context) {
+ super(context);
+
+ mContext = context;
+ mResolver = context.getContentResolver();
+ mServiceStub = new AutoFillManagerServiceStub();
+ }
+
+ @Override
+ public void onStart() {
+ if (DEBUG)
+ Slog.d(TAG, "onStart(): binding as " + AUTO_FILL_MANAGER_SERVICE);
+ publishBinderService(AUTO_FILL_MANAGER_SERVICE, mServiceStub);
+ }
+
+ // TODO: refactor so it's bound on demand, in which case it can use isSafeMode() from PM.
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ systemRunning(isSafeMode());
+ }
+ }
+
+ // TODO: refactor so it's bound on demand, in which case it can use isSafeMode() from PM.
+ @Override
+ public void onStartUser(int userHandle) {
+ if (DEBUG) Slog.d(TAG, "onStartUser(): userHandle=" + userHandle);
+
+ updateImplementationIfNeeded(userHandle, false);
+ }
+
+ @Override
+ public void onUnlockUser(int userHandle) {
+ if (DEBUG) Slog.d(TAG, "onUnlockUser(): userHandle=" + userHandle);
+
+ updateImplementationIfNeeded(userHandle, false);
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ if (DEBUG) Slog.d(TAG, "onSwitchUser(): userHandle=" + userHandle);
+
+ updateImplementationIfNeeded(userHandle, false);
+ }
+
+ private void systemRunning(boolean safeMode) {
+ if (DEBUG) Slog.d(TAG, "systemRunning(): safeMode=" + safeMode);
+
+ // TODO: register a PackageMonitor
+ new SettingsObserver(BackgroundThread.getHandler());
+
+ synchronized (mLock) {
+ mSafeMode = safeMode;
+ updateImplementationIfNeededLocked(ActivityManager.getCurrentUser(), false);
+ }
+ }
+
+ private void updateImplementationIfNeeded(int user, boolean force) {
+ synchronized (mLock) {
+ updateImplementationIfNeededLocked(user, force);
+ }
+ }
+
+ private void updateImplementationIfNeededLocked(int user, boolean force) {
+ if (DEBUG)
+ Slog.d(TAG, "updateImplementationIfNeededLocked(" + user + ", " + force + ")");
+
+ if (mSafeMode) {
+ if (DEBUG) Slog.d(TAG, "skipping on safe mode");
+ return;
+ }
+
+ final String curService = Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.AUTO_FILL_SERVICE, user);
+ if (DEBUG)
+ Slog.d(TAG, "Current service settings for user " + user + ": " + curService);
+ ComponentName serviceComponent = null;
+ ServiceInfo serviceInfo = null;
+ if (!TextUtils.isEmpty(curService)) {
+ try {
+ serviceComponent = ComponentName.unflattenFromString(curService);
+ serviceInfo =
+ AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, user);
+ } catch (RuntimeException | RemoteException e) {
+ Slog.wtf(TAG, "Bad auto-fill service name " + curService, e);
+ serviceComponent = null;
+ serviceInfo = null;
+ }
+ }
+
+ final AutoFillManagerServiceImpl impl = mImplByUser.get(user);
+ if (DEBUG) Slog.d(TAG, "Current impl: " + impl + " component: " + serviceComponent
+ + " info: " + serviceInfo);
+
+ if (force || impl == null || !impl.mComponent.equals(serviceComponent)) {
+ if (impl != null) {
+ impl.shutdownLocked();
+ }
+ if (serviceInfo != null) {
+ final AutoFillManagerServiceImpl newImpl = new AutoFillManagerServiceImpl(mContext,
+ mLock, mServiceStub, FgThread.getHandler(), user, serviceComponent);
+ if (DEBUG) Slog.d(TAG, "Setting impl for user " + user + " as: " + newImpl);
+ mImplByUser.put(user, newImpl);
+ newImpl.startLocked();
+ } else {
+ if (DEBUG) Slog.d(TAG, "Removing impl for user " + user + ": " + impl);
+ mImplByUser.remove(user);
+ }
+ }
+ }
+
+ // TODO: might need to return null instead of throw exception
+ private AutoFillManagerServiceImpl getImplOrThrowLocked(int userId) {
+ final AutoFillManagerServiceImpl impl = mImplByUser.get(userId);
+ if (impl == null) {
+ throw new IllegalStateException("no auto-fill service for user " + userId);
+ }
+ return impl;
+ }
+
+ final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
+
+ @Override
+ public String startSession(int userId, Bundle args, int flags, IBinder activityToken) {
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+
+ synchronized (mLock) {
+ return getImplOrThrowLocked(userId).startSession(args, flags, activityToken);
+ }
+ }
+
+ @Override
+ public boolean finishSession(int userId, String token) {
+ mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
+
+ synchronized (mLock) {
+ return getImplOrThrowLocked(userId).finishSessionLocked(token);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingPermission(
+ Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump autofill from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ if (args.length > 0) {
+ if ("--sessions".equals(args[0])) {
+ dumpSessions(pw);
+ return;
+ }
+ }
+ synchronized (mLock) {
+ pw.print("mEnableService: "); pw.println(mEnableService);
+ pw.print("mSafeMode: "); pw.println(mSafeMode);
+ final int size = mImplByUser.size();
+ pw.print("Number of implementations: ");
+ if (size == 0) {
+ pw.println("none");
+ } else {
+ pw.println(size);
+ for (int i = 0; i < size; i++) {
+ pw.print("\nImplementation at index "); pw.println(i);
+ final AutoFillManagerServiceImpl impl = mImplByUser.valueAt(i);
+ impl.dumpLocked(" ", pw);
+ }
+ }
+ }
+ }
+
+ private void dumpSessions(PrintWriter pw) {
+ boolean foundOne = false;
+ synchronized (mLock) {
+ final int size = mImplByUser.size();
+ for (int i = 0; i < size; i++) {
+ final AutoFillManagerServiceImpl impl = mImplByUser.valueAt(i);
+ foundOne |= impl.dumpSessionsLocked("", pw);
+ }
+ }
+ if (!foundOne) {
+ pw.println("No active sessions");
+ }
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ (new AutoFillManagerServiceShellCommand(this)).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.AUTO_FILL_SERVICE), false, this,
+ UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ synchronized (mLock) {
+ updateImplementationIfNeededLocked(userId, false);
+ }
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
new file mode 100644
index 0000000..c780062
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.autofill.AutoFillService;
+import android.service.autofill.AutoFillServiceInfo;
+import android.service.autofill.IAutoFillService;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.autofill.AutoFillManagerService.AutoFillManagerServiceStub;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Bridge between the {@code system_server}'s {@link AutoFillManagerService} and the
+ * app's {@link IAutoFillService} implementation.
+ *
+ * <p>It keeps a list of auto-fill sessions for a specifc user.
+ */
+final class AutoFillManagerServiceImpl {
+
+ private static final String TAG = "AutoFillManagerServiceImpl";
+ private static final boolean DEBUG = true; // TODO: change to false once stable
+
+ final int mUser;
+ final ComponentName mComponent;
+
+ private final Context mContext;
+ private final Object mLock;
+ private final AutoFillManagerServiceStub mServiceStub;
+ private final AutoFillServiceInfo mInfo;
+
+ // Map of sessions keyed by session tokens.
+ @GuardedBy("mLock")
+ private Map<String, AutoFillSession> mSessions = new HashMap<>();
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ final String reason = intent.getStringExtra("reason");
+ if (DEBUG) Slog.d(TAG, "close system dialogs: " + reason);
+ // TODO: close any pending UI like account selection
+ }
+ }
+ };
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Log.d(TAG, "onServiceConnected():" + name);
+ synchronized (mLock) {
+ mService = IAutoFillService.Stub.asInterface(service);
+ try {
+ mService.ready();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception on service.ready(): " + e);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Log.d(TAG, name + " disconnected");
+ mService = null;
+ }
+ };
+
+ @GuardedBy("mLock")
+ private IAutoFillService mService;
+ private boolean mBound;
+ private boolean mValid;
+
+ AutoFillManagerServiceImpl(Context context, Object lock, AutoFillManagerServiceStub stub,
+ Handler handler, int user, ComponentName component) {
+ mContext = context;
+ mLock = lock;
+ mServiceStub = stub;
+ mUser = user;
+ mComponent = component;
+
+ AutoFillServiceInfo info;
+ try {
+ info = new AutoFillServiceInfo(component, mUser);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Auto-fill service not found: " + component, e);
+ mInfo = null;
+ mValid = false;
+ return;
+ }
+ mInfo = info;
+ if (mInfo.getParseError() != null) {
+ Slog.w(TAG, "Bad auto-fill service: " + mInfo.getParseError());
+ mValid = false;
+ return;
+ }
+
+ mValid = true;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mContext.registerReceiver(mBroadcastReceiver, filter, null, handler);
+ }
+
+ void startLocked() {
+ if (DEBUG) Slog.d(TAG, "startLocked()");
+
+ final Intent intent = new Intent(AutoFillService.SERVICE_INTERFACE);
+ intent.setComponent(mComponent);
+ mBound = mContext.bindServiceAsUser(intent, mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUser));
+ if (!mBound) {
+ Slog.w(TAG, "Failed binding to auto-fill service " + mComponent);
+ return;
+ }
+ if (DEBUG) Slog.d(TAG, "Bound to " + mComponent);
+ }
+
+ String startSession(Bundle args, int flags, IBinder activityToken) {
+
+ if (!mBound) {
+ // TODO: should it bind on demand? Or perhaps always run when on on low-memory?
+ Slog.w(TAG, "startSession() failed because it's not bound to service");
+ return null;
+ }
+
+ // TODO: session should have activity ids, so same session is reused when called again
+ // for the same activity.
+
+ // TODO: activityToken should probably not be null, but we need to wait until the UI is
+ // triggering the call (for now it's trough 'adb shell cmd autofill start session'
+ if (activityToken == null) {
+ // Let's get top activities from all visible stacks.
+
+ // TODO: overload getTopVisibleActivities() to take userId, otherwise it could return
+ // activities for different users when a work profile app is displayed in another
+ // window (in a multi-window environment).
+ final List<IBinder> topActivities = LocalServices
+ .getService(ActivityManagerInternal.class).getTopVisibleActivities();
+ if (DEBUG)
+ Slog.d(TAG, "Top activities (" + topActivities.size() + "): " + topActivities);
+ if (topActivities.isEmpty()) {
+ Slog.w(TAG, "Could not get top activity");
+ return null;
+ }
+ activityToken = topActivities.get(0);
+ }
+
+ synchronized (mLock) {
+ return startSessionLocked(args, flags, activityToken);
+ }
+ }
+
+ // TODO: remove args and flags if not needed?
+ private String startSessionLocked(Bundle args, int flags, IBinder activityToken) {
+
+ final String sessionToken = UUID.randomUUID().toString();
+
+ if (DEBUG) Slog.d(TAG, "Starting session for user " + mUser
+ + ": sessionToken=" + sessionToken + ", activityToken=" + activityToken);
+
+ final AutoFillSession session =
+ new AutoFillSession(mService, mLock, sessionToken, activityToken);
+ session.startLocked();
+ mSessions.put(sessionToken, session);
+
+ return sessionToken;
+ }
+
+ // TODO: need a way to automatically call it when the activity is destroyed.
+ boolean finishSessionLocked(String token) {
+ if (DEBUG) Slog.d(TAG, "Removing session " + token + " for user " + mUser);
+ final AutoFillSession session = mSessions.remove(token);
+ if (session != null) {
+ session.finishLocked();
+ }
+ return session != null;
+ }
+
+ void shutdownLocked() {
+ if (DEBUG) Slog.d(TAG, "shutdownLocked()");
+
+ try {
+ if (mService != null) {
+ mService.shutdown();
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in shutdown", e);
+ }
+
+ if (mBound) {
+ mContext.unbindService(mConnection);
+ mBound = false;
+ }
+ if (mValid) {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+ }
+
+ void dumpLocked(String prefix, PrintWriter pw) {
+ if (!mValid) {
+ pw.print(" NOT VALID: ");
+ if (mInfo == null) {
+ pw.println("no info");
+ } else {
+ pw.println(mInfo.getParseError());
+ }
+ return;
+ }
+
+ pw.print(prefix); pw.print("mUser="); pw.println(mUser);
+ pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
+ pw.print(prefix); pw.print("mBound="); pw.println(mBound);
+ pw.print(prefix); pw.print("mService="); pw.println(mService);
+
+ if (DEBUG) {
+ // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
+ pw.print(prefix); pw.println("Service info:");
+ mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
+ }
+
+ if (!dumpSessionsLocked(prefix, pw)) {
+ pw.print(prefix); pw.print("No active sessions for user "); pw.println(mUser);
+ }
+ }
+
+ boolean dumpSessionsLocked(String prefix, PrintWriter pw) {
+ if (mSessions.isEmpty()) {
+ return false;
+ }
+
+ pw.print(mSessions.size());pw.println(" active sessions:");
+ final String sessionPrefix = prefix + prefix;
+ for (AutoFillSession session : mSessions.values()) {
+ pw.println();
+ session.dumpLocked(sessionPrefix, pw);
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "[AutoFillManagerServiceImpl: user=" + mUser
+ + ", component=" + mComponent.flattenToShortString() + "]";
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
new file mode 100644
index 0000000..4e08ed6
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.app.ActivityManager;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.service.autofill.IAutoFillManagerService;
+
+import java.io.PrintWriter;
+
+public final class AutoFillManagerServiceShellCommand extends ShellCommand {
+
+ private final IAutoFillManagerService.Stub mService;
+
+ public AutoFillManagerServiceShellCommand(IAutoFillManagerService.Stub service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "start":
+ return runStart(pw);
+ case "finish":
+ return runFinish(pw);
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("error: " + e);
+ }
+ return -1;
+ }
+
+ @Override
+ public void onHelp() {
+ try (final PrintWriter pw = getOutPrintWriter();) {
+ pw.println("AutoFill Service (autofill) commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" start session [--user USER_ID]");
+ pw.println(" Starts an auto-fill session. "
+ + "Prints 'token:SESSION_TOKEN if successful, or error message");
+ pw.println("");
+ pw.println(" finish session <TOKEN> [--user USER_ID]");
+ pw.println(" Finishes a session with the given TOKEN. "
+ + "Prints empty string if successful, or error message.");
+ pw.println("");
+ }
+ }
+
+ private int runStart(PrintWriter pw) throws RemoteException {
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to start");
+ return -1;
+ }
+ switch (type) {
+ case "session":
+ return startAutoFillSession(pw);
+ }
+ pw.println("Error: unknown start type '" + type + "'");
+ return -1;
+ }
+
+ private int runFinish(PrintWriter pw) throws RemoteException {
+ final String type = getNextArg();
+ if (type == null) {
+ pw.println("Error: didn't specify type of data to finish");
+ return -1;
+ }
+ switch (type) {
+ case "session":
+ return finishAutoFillSession(pw);
+ }
+ pw.println("Error: unknown finish type '" + type + "'");
+ return -1;
+ }
+
+ private int startAutoFillSession(PrintWriter pw) throws RemoteException {
+ final int userId = getUserIdFromArgs();
+ final String token = mService.startSession(userId, null, 0, null);
+ pw.print("token:"); pw.println(token);
+ return 0;
+ }
+
+ private int finishAutoFillSession(PrintWriter pw) throws RemoteException {
+ final String token = getNextArgRequired();
+ final int userId = getUserIdFromArgs();
+
+ boolean finished = mService.finishSession(userId, token);
+ if (!finished) {
+ pw.println("No such session");
+ return 1;
+ }
+ return 0;
+ }
+
+ private int getUserIdFromArgs() {
+ if ("--user".equals(getNextArg())) {
+ return UserHandle.parseUserArg(getNextArgRequired());
+ }
+ return ActivityManager.getCurrentUser();
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillSession.java b/services/autofill/java/com/android/server/autofill/AutoFillSession.java
new file mode 100644
index 0000000..44637c3
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutoFillSession.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.assist.AssistStructure;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.autofill.AutoFillService;
+import android.service.autofill.IAutoFillService;
+import android.service.voice.VoiceInteractionSession;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+
+import java.io.PrintWriter;
+
+/**
+ * An auto-fill session between the system's {@link AutoFillManagerServiceImpl} and the provider's
+ * {@link AutoFillService} implementation.
+ */
+final class AutoFillSession {
+
+ private static final String TAG = "AutoFillSession";
+
+ private static final boolean FOCUSED = true;
+ private static final boolean NEW_SESSION_ID = true;
+
+ private final IAutoFillService mService;
+ private final String mSessionToken;
+ private final IBinder mActivityToken;
+ private final Object mLock;
+ private final IActivityManager mAm;
+
+ private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) throws RemoteException {
+ synchronized (mLock) {
+ mPendingResponse = false;
+ mAssistResponse = resultData;
+ deliverSessionDataLocked();
+ }
+ }
+ };
+
+ // Assist data is filled asynchronously.
+ @GuardedBy("mLock")
+ private Bundle mAssistResponse;
+ @GuardedBy("mLock")
+ private boolean mPendingResponse;
+
+ AutoFillSession(IAutoFillService service, Object lock, String sessionToken,
+ IBinder activityToken) {
+ mService = service;
+ mSessionToken = sessionToken;
+ mActivityToken = activityToken;
+ mLock = lock;
+ mAm = ActivityManagerNative.getDefault();
+ }
+
+ void startLocked() {
+ /*
+ * TODO: apply security checks below:
+ * - checks if disabled by secure settings / device policy
+ * - log operation using noteOp()
+ * - check flags
+ * - display disclosure if needed
+ */
+ mAssistResponse = null;
+ mPendingResponse = true;
+ try {
+ // TODO: add MetricsLogger call
+ if (!mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
+ mAssistReceiver, (Bundle) null, mActivityToken, FOCUSED, NEW_SESSION_ID)) {
+ mPendingResponse = false;
+ Slog.w(TAG, "requestAssistContextExtras() rejected");
+ }
+ } catch (RemoteException e) {
+ // Should happen, it's a local call.
+ }
+ }
+
+ void finishLocked() {
+ try {
+ mService.finishSession(mSessionToken);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "auto-fill service failed to finish session " + mSessionToken, e);
+ }
+ }
+
+ private void deliverSessionDataLocked() {
+ if (mAssistResponse == null) {
+ Slog.w(TAG, "No assist data for session " + mSessionToken);
+ return;
+ }
+
+ final Bundle assistData = mAssistResponse.getBundle(VoiceInteractionSession.KEY_DATA);
+ final AssistStructure structure =
+ mAssistResponse.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
+ try {
+ mService.newSession(mSessionToken, assistData, 0, structure);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "auto-fill service failed to start session " + mSessionToken, e);
+ } finally {
+ mPendingResponse = false;
+ // We could set mAssistResponse to null here, but we don't so it's shown on dump()
+ }
+ }
+
+ void dumpLocked(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mSessionToken="); pw.println(mSessionToken);
+ pw.print(prefix); pw.print("mActivityToken="); pw.println(mActivityToken);
+ pw.print(prefix); pw.print("mPendingResponse="); pw.println(mPendingResponse);
+ pw.print(prefix); pw.print("mAssistResponse="); pw.println(mAssistResponse);
+ }
+
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cc96f565..211e3b8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -184,6 +184,8 @@
"com.android.server.content.ContentService$Lifecycle";
private static final String WALLPAPER_SERVICE_CLASS =
"com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
+ private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
+ "com.android.server.autofill.AutoFillManagerService";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -1362,6 +1364,10 @@
mSystemServiceManager.startService(RetailDemoModeService.class);
traceEnd();
+ traceBeginAndSlog("StartAutoFillService");
+ mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS);
+ traceEnd();
+
// It is now time to start up the app processes...
traceBeginAndSlog("MakeVibratorServiceReady");