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
This commit is contained in:
Felipe Leme
2016-10-13 09:02:32 -07:00
parent 8f68c8b9a8
commit 5381aa4b58
19 changed files with 1281 additions and 0 deletions

View File

@@ -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 \

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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}.

View File

@@ -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

View File

@@ -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()");
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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"

View File

@@ -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()) -->

View File

@@ -22,6 +22,7 @@ services := \
core \
accessibility \
appwidget \
autofill \
backup \
devicepolicy \
midi \

View File

@@ -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)

View File

@@ -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);
}
}
}
}

View File

@@ -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() + "]";
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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");