From 5381aa4b585f3fa2a315d88e910111173e2ef77d Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Thu, 13 Oct 2016 09:02:32 -0700 Subject: [PATCH] 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 --- Android.mk | 2 + api/current.txt | 15 + api/system-current.txt | 16 + api/test-current.txt | 16 + core/java/android/content/Context.java | 8 + core/java/android/provider/Settings.java | 7 + .../service/autofill/AutoFillService.java | 178 ++++++++++ .../service/autofill/AutoFillServiceInfo.java | 71 ++++ .../autofill/IAutoFillManagerService.aidl | 54 +++ .../service/autofill/IAutoFillService.aidl | 30 ++ core/res/AndroidManifest.xml | 12 + packages/Shell/AndroidManifest.xml | 1 + services/Android.mk | 1 + services/autofill/Android.mk | 12 + .../autofill/AutoFillManagerService.java | 310 ++++++++++++++++++ .../autofill/AutoFillManagerServiceImpl.java | 280 ++++++++++++++++ .../AutoFillManagerServiceShellCommand.java | 127 +++++++ .../server/autofill/AutoFillSession.java | 135 ++++++++ .../java/com/android/server/SystemServer.java | 6 + 19 files changed, 1281 insertions(+) create mode 100644 core/java/android/service/autofill/AutoFillService.java create mode 100644 core/java/android/service/autofill/AutoFillServiceInfo.java create mode 100644 core/java/android/service/autofill/IAutoFillManagerService.aidl create mode 100644 core/java/android/service/autofill/IAutoFillService.aidl create mode 100644 services/autofill/Android.mk create mode 100644 services/autofill/java/com/android/server/autofill/AutoFillManagerService.java create mode 100644 services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java create mode 100644 services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java create mode 100644 services/autofill/java/com/android/server/autofill/AutoFillSession.java diff --git a/Android.mk b/Android.mk index 6f96803476558..bd04214e427e6 100644 --- a/Android.mk +++ b/Android.mk @@ -251,6 +251,8 @@ LOCAL_SRC_FILES += \ 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 f70458ba3f2c2..9a1a4f2997b72 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18,6 +18,7 @@ package android { 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.security.keystore { } +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 6622ea51be5f0..a41a132dd43e3 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -28,6 +28,7 @@ package android { 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 @@ package android { 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.security.keystore { } +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 de57dbafc1f6c..9393e1d75f36d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -18,6 +18,7 @@ package android { 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 @@ package android.provider { 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.security.keystore { } +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 322cc7b9beccb..3964e0a8bbd92 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3337,6 +3337,14 @@ public abstract class Context { */ 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 1b905a0204a4f..73eeccda690b4 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4714,6 +4714,13 @@ public final class Settings { @TestApi 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 0000000000000..14ce009bedc64 --- /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. + * + *

You should generally do initialization here rather than in {@link #onCreate}. + * + *

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. + * + *

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 0000000000000..fe2161521b82c --- /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 0000000000000..2c0623413be61 --- /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 0000000000000..73d8d5db5004e --- /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 4eebea6da46d2..3ec9de319b3f3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2323,6 +2323,13 @@ + + + @@ -3118,6 +3125,11 @@ + + + + diff --git a/services/Android.mk b/services/Android.mk index 3385bed0ba8b9..291198303548d 100644 --- a/services/Android.mk +++ b/services/Android.mk @@ -22,6 +22,7 @@ services := \ core \ accessibility \ appwidget \ + autofill \ backup \ devicepolicy \ midi \ diff --git a/services/autofill/Android.mk b/services/autofill/Android.mk new file mode 100644 index 0000000000000..a1f19fd4c72d0 --- /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 0000000000000..3b41877bd36a6 --- /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. + * + *

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. + *

+ * 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). + *

+ * This map is filled on demand in the following scenarios: + *

    + *
  1. On start, it sets the value for the default user. + *
  2. When an auto-fill service app is removed, its entries are removed. + *
  3. When the current user changes. + *
  4. When the {@link android.provider.Settings.Secure#AUTO_FILL_SERVICE} changes. + *
+ */ + // 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 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 0000000000000..c780062c567c5 --- /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. + * + *

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 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 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 0000000000000..4e08ed6c76538 --- /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 [--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 0000000000000..44637c3f5f46b --- /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 cc96f565564af..211e3b8d7f0b8 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -184,6 +184,8 @@ public final class SystemServer { "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 @@ public final class SystemServer { 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");