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:
@@ -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 \
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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
|
||||
|
||||
178
core/java/android/service/autofill/AutoFillService.java
Normal file
178
core/java/android/service/autofill/AutoFillService.java
Normal 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()");
|
||||
}
|
||||
}
|
||||
71
core/java/android/service/autofill/AutoFillServiceInfo.java
Normal file
71
core/java/android/service/autofill/AutoFillServiceInfo.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
30
core/java/android/service/autofill/IAutoFillService.aidl
Normal file
30
core/java/android/service/autofill/IAutoFillService.aidl
Normal 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();
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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()) -->
|
||||
|
||||
@@ -22,6 +22,7 @@ services := \
|
||||
core \
|
||||
accessibility \
|
||||
appwidget \
|
||||
autofill \
|
||||
backup \
|
||||
devicepolicy \
|
||||
midi \
|
||||
|
||||
12
services/autofill/Android.mk
Normal file
12
services/autofill/Android.mk
Normal 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)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() + "]";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user