/*
* 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.AUTOFILL_MANAGER_SERVICE;
import static com.android.server.autofill.Helper.bundleToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sPartitionMaxCount;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
import android.service.autofill.FillEventHistory;
import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManagerInternal;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManager;
import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.autofill.ui.AutoFillUI;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Entry point service for autofill management.
*
*
This service provides the {@link IAutoFillManager} 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";
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private final Context mContext;
private final AutoFillUI mUi;
private final Object mLock = new Object();
/**
* Cache of {@link AutofillManagerServiceImpl} per user id.
*
* It has to be mapped by user id because the same current user could have simultaneous sessions
* associated to different user profiles (for example, in a multi-window environment or when
* device has work profiles).
*/
@GuardedBy("mLock")
private SparseArray mServicesCache = new SparseArray<>();
/**
* Users disabled due to {@link UserManager} restrictions.
*/
@GuardedBy("mLock")
private final SparseBooleanArray mDisabledUsers = new SparseBooleanArray();
private final LocalLog mRequestsHistory = new LocalLog(20);
private final LocalLog mUiLatencyHistory = new LocalLog(20);
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
if (sDebug) Slog.d(TAG, "Close system dialogs");
// TODO(b/64940307): we need to destroy all sessions that are finished but showing
// Save UI because there is no way to show the Save UI back when the activity
// beneath it is brought back to top. Ideally, we should just hide the UI and
// bring it back when the activity resumes.
synchronized (mLock) {
for (int i = 0; i < mServicesCache.size(); i++) {
mServicesCache.valueAt(i).destroyFinishedSessionsLocked();
}
}
mUi.hideAll(null);
}
}
};
public AutofillManagerService(Context context) {
super(context);
mContext = context;
mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext());
final boolean debug = Build.IS_DEBUGGABLE;
Slog.i(TAG, "Setting debug to " + debug);
setDebugLocked(debug);
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mContext.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler());
// Hookup with UserManager to disable service when necessary.
final UserManager um = context.getSystemService(UserManager.class);
final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
final List users = um.getUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
final boolean disabled = umi.getUserRestriction(userId, UserManager.DISALLOW_AUTOFILL);
if (disabled) {
if (disabled) {
Slog.i(TAG, "Disabling Autofill for user " + userId);
}
mDisabledUsers.put(userId, disabled);
}
}
umi.addUserRestrictionsListener((userId, newRestrictions, prevRestrictions) -> {
final boolean disabledNow =
newRestrictions.getBoolean(UserManager.DISALLOW_AUTOFILL, false);
synchronized (mLock) {
final boolean disabledBefore = mDisabledUsers.get(userId);
if (disabledBefore == disabledNow) {
// Nothing changed, do nothing.
if (sDebug) {
Slog.d(TAG, "Autofill restriction did not change for user " + userId + ": "
+ bundleToString(newRestrictions));
return;
}
}
Slog.i(TAG, "Updating Autofill for user " + userId + ": disabled=" + disabledNow);
mDisabledUsers.put(userId, disabledNow);
updateCachedServiceLocked(userId, disabledNow);
}
});
startTrackingPackageChanges();
}
private void startTrackingPackageChanges() {
PackageMonitor monitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
synchronized (mLock) {
updateCachedServiceLocked(getChangingUserId());
}
}
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
synchronized (mLock) {
final String activePackageName = getActiveAutofillServicePackageName();
if (packageName.equals(activePackageName)) {
removeCachedServiceLocked(getChangingUserId());
}
}
}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
final int userId = getChangingUserId();
final AutofillManagerServiceImpl userState = peekServiceForUserLocked(userId);
if (userState != null) {
final ComponentName componentName = userState.getServiceComponentName();
if (componentName != null) {
if (packageName.equals(componentName.getPackageName())) {
handleActiveAutofillServiceRemoved(userId);
}
}
}
}
}
@Override
public boolean onHandleForceStop(Intent intent, String[] packages,
int uid, boolean doit) {
synchronized (mLock) {
final String activePackageName = getActiveAutofillServicePackageName();
for (String pkg : packages) {
if (pkg.equals(activePackageName)) {
if (!doit) {
return true;
}
removeCachedServiceLocked(getChangingUserId());
}
}
}
return false;
}
private void handleActiveAutofillServiceRemoved(int userId) {
removeCachedServiceLocked(userId);
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.AUTOFILL_SERVICE, null, userId);
}
private String getActiveAutofillServicePackageName() {
final int userId = getChangingUserId();
final AutofillManagerServiceImpl userState = peekServiceForUserLocked(userId);
if (userState == null) {
return null;
}
final ComponentName serviceComponent = userState.getServiceComponentName();
if (serviceComponent == null) {
return null;
}
return serviceComponent.getPackageName();
}
};
// package changes
monitor.register(mContext, null, UserHandle.ALL, true);
}
@Override
public void onStart() {
publishBinderService(AUTOFILL_MANAGER_SERVICE, new AutoFillManagerServiceStub());
publishLocalService(AutofillManagerInternal.class, new LocalService());
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
new SettingsObserver(BackgroundThread.getHandler());
}
}
@Override
public void onUnlockUser(int userId) {
synchronized (mLock) {
updateCachedServiceLocked(userId);
}
}
@Override
public void onSwitchUser(int userHandle) {
if (sDebug) Slog.d(TAG, "Hiding UI when user switched");
mUi.hideAll(null);
}
@Override
public void onCleanupUser(int userId) {
synchronized (mLock) {
removeCachedServiceLocked(userId);
}
}
/**
* Gets the service instance for an user.
*
* @return service instance.
*/
@NonNull
AutofillManagerServiceImpl getServiceForUserLocked(int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, null, null);
AutofillManagerServiceImpl service = mServicesCache.get(resolvedUserId);
if (service == null) {
service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
mUiLatencyHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
mServicesCache.put(userId, service);
}
return service;
}
/**
* Peeks the service instance for a user.
*
* @return service instance or {@code null} if not already present
*/
@Nullable
AutofillManagerServiceImpl peekServiceForUserLocked(int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, null, null);
return mServicesCache.get(resolvedUserId);
}
// Called by Shell command.
void destroySessions(int userId, IResultReceiver receiver) {
Slog.i(TAG, "destroySessions() for userId " + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
if (userId != UserHandle.USER_ALL) {
AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.destroySessionsLocked();
}
} else {
final int size = mServicesCache.size();
for (int i = 0; i < size; i++) {
mServicesCache.valueAt(i).destroySessionsLocked();
}
}
}
try {
receiver.send(0, new Bundle());
} catch (RemoteException e) {
// Just ignore it...
}
}
// Called by Shell command.
void listSessions(int userId, IResultReceiver receiver) {
Slog.i(TAG, "listSessions() for userId " + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
final Bundle resultData = new Bundle();
final ArrayList sessions = new ArrayList<>();
synchronized (mLock) {
if (userId != UserHandle.USER_ALL) {
AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.listSessionsLocked(sessions);
}
} else {
final int size = mServicesCache.size();
for (int i = 0; i < size; i++) {
mServicesCache.valueAt(i).listSessionsLocked(sessions);
}
}
}
resultData.putStringArrayList(RECEIVER_BUNDLE_EXTRA_SESSIONS, sessions);
try {
receiver.send(0, resultData);
} catch (RemoteException e) {
// Just ignore it...
}
}
// Called by Shell command.
void reset() {
Slog.i(TAG, "reset()");
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
final int size = mServicesCache.size();
for (int i = 0; i < size; i++) {
mServicesCache.valueAt(i).destroyLocked();
}
mServicesCache.clear();
}
}
// Called by Shell command.
void setLogLevel(int level) {
Slog.i(TAG, "setLogLevel(): " + level);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
boolean debug = false;
boolean verbose = false;
if (level == AutofillManager.FLAG_ADD_CLIENT_VERBOSE) {
debug = verbose = true;
} else if (level == AutofillManager.FLAG_ADD_CLIENT_DEBUG) {
debug = true;
}
synchronized (mLock) {
setDebugLocked(debug);
setVerboseLocked(verbose);
}
}
// Called by Shell command.
int getLogLevel() {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
if (sVerbose) return AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
if (sDebug) return AutofillManager.FLAG_ADD_CLIENT_DEBUG;
return 0;
}
}
// Called by Shell command.
public int getMaxPartitions() {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
return sPartitionMaxCount;
}
}
// Called by Shell command.
public void setMaxPartitions(int max) {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
Slog.i(TAG, "setMaxPartitions(): " + max);
synchronized (mLock) {
sPartitionMaxCount = max;
}
}
private void setDebugLocked(boolean debug) {
com.android.server.autofill.Helper.sDebug = debug;
android.view.autofill.Helper.sDebug = debug;
}
private void setVerboseLocked(boolean verbose) {
com.android.server.autofill.Helper.sVerbose = verbose;
android.view.autofill.Helper.sVerbose = verbose;
}
/**
* Removes a cached service for a given user.
*/
private void removeCachedServiceLocked(int userId) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
mServicesCache.delete(userId);
service.destroyLocked();
}
}
/**
* Updates a cached service for a given user.
*/
private void updateCachedServiceLocked(int userId) {
updateCachedServiceLocked(userId, mDisabledUsers.get(userId));
}
/**
* Updates a cached service for a given user.
*/
private void updateCachedServiceLocked(int userId, boolean disabled) {
AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
if (service != null) {
service.destroySessionsLocked();
service.updateLocked(disabled);
if (!service.isEnabled()) {
removeCachedServiceLocked(userId);
}
}
}
private final class LocalService extends AutofillManagerInternal {
@Override
public void onBackKeyPressed() {
if (sDebug) Slog.d(TAG, "onBackKeyPressed()");
mUi.hideAll(null);
}
}
final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
@Override
public int addClient(IAutoFillManagerClient client, int userId) {
synchronized (mLock) {
int flags = 0;
if (getServiceForUserLocked(userId).addClientLocked(client)) {
flags |= AutofillManager.FLAG_ADD_CLIENT_ENABLED;
}
if (sDebug) {
flags |= AutofillManager.FLAG_ADD_CLIENT_DEBUG;
}
if (sVerbose) {
flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
}
return flags;
}
}
@Override
public void setAuthenticationResult(Bundle data, int sessionId, int authenticationId,
int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
service.setAuthenticationResultLocked(data, sessionId, authenticationId,
getCallingUid());
}
}
@Override
public void setHasCallback(int sessionId, int userId, boolean hasIt) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
service.setHasCallback(sessionId, getCallingUid(), hasIt);
}
}
@Override
public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
String packageName) {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
packageName = Preconditions.checkNotNull(packageName, "packageName");
Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
try {
mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, userId);
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException(packageName + " is not a valid package", e);
}
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
autofillId, bounds, value, hasCallback, flags, packageName);
}
}
@Override
public FillEventHistory getFillEventHistory() throws RemoteException {
UserHandle user = getCallingUserHandle();
int uid = getCallingUid();
synchronized (mLock) {
AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
if (service != null) {
return service.getFillEventHistory(uid);
}
}
return null;
}
@Override
public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
throws RemoteException {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
synchronized (mLock) {
final AutofillManagerServiceImpl service = mServicesCache.get(
UserHandle.getCallingUserId());
if (service != null) {
return service.restoreSession(sessionId, getCallingUid(), activityToken,
appCallback);
}
}
return false;
}
@Override
public void updateSession(int sessionId, AutofillId autoFillId, Rect bounds,
AutofillValue value, int action, int flags, int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.updateSessionLocked(sessionId, getCallingUid(), autoFillId, bounds,
value, action, flags);
}
}
}
@Override
public int updateOrRestartSession(IBinder activityToken, IBinder appCallback,
AutofillId autoFillId, Rect bounds, AutofillValue value, int userId,
boolean hasCallback, int flags, String packageName, int sessionId, int action) {
boolean restart = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
restart = service.updateSessionLocked(sessionId, getCallingUid(), autoFillId,
bounds, value, action, flags);
}
}
if (restart) {
return startSession(activityToken, appCallback, autoFillId, bounds, value, userId,
hasCallback, flags, packageName);
}
// Nothing changed...
return sessionId;
}
@Override
public void finishSession(int sessionId, int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.finishSessionLocked(sessionId, getCallingUid());
}
}
}
@Override
public void cancelSession(int sessionId, int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.cancelSessionLocked(sessionId, getCallingUid());
}
}
}
@Override
public void disableOwnedAutofillServices(int userId) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
service.disableOwnedAutofillServicesLocked(Binder.getCallingUid());
}
}
}
@Override
public boolean isServiceSupported(int userId) {
synchronized (mLock) {
return !mDisabledUsers.get(userId);
}
}
@Override
public boolean isServiceEnabled(int userId, String packageName) {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service == null) return false;
return Objects.equals(packageName, service.getServicePackageName());
}
}
@Override
public void onPendingSaveUi(int operation, IBinder token) {
Preconditions.checkNotNull(token, "token");
Preconditions.checkArgument(operation == AutofillManager.PENDING_UI_OPERATION_CANCEL
|| operation == AutofillManager.PENDING_UI_OPERATION_RESTORE,
"invalid operation: %d", operation);
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(
UserHandle.getCallingUserId());
if (service != null) {
service.onPendingSaveUi(operation, token);
}
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
boolean showHistory = true;
boolean uiOnly = false;
if (args != null) {
for (String arg : args) {
switch(arg) {
case "--no-history":
showHistory = false;
break;
case "--ui-only":
uiOnly = true;
break;
case "--help":
pw.println("Usage: dumpsys autofill [--ui-only|--no-history]");
return;
default:
Slog.w(TAG, "Ignoring invalid dump arg: " + arg);
}
}
}
if (uiOnly) {
mUi.dump(pw);
return;
}
boolean oldDebug = sDebug;
try {
synchronized (mLock) {
oldDebug = sDebug;
setDebugLocked(true);
pw.print("Debug mode: "); pw.println(oldDebug);
pw.print("Verbose mode: "); pw.println(sVerbose);
pw.print("Disabled users: "); pw.println(mDisabledUsers);
pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount);
final int size = mServicesCache.size();
pw.print("Cached services: ");
if (size == 0) {
pw.println("none");
} else {
pw.println(size);
for (int i = 0; i < size; i++) {
pw.print("\nService at index "); pw.println(i);
final AutofillManagerServiceImpl impl = mServicesCache.valueAt(i);
impl.dumpLocked(" ", pw);
}
}
mUi.dump(pw);
}
if (showHistory) {
pw.println("Requests history:");
mRequestsHistory.reverseDump(fd, pw, args);
pw.println("UI latency history:");
mUiLatencyHistory.reverseDump(fd, pw, args);
}
} finally {
setDebugLocked(oldDebug);
}
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new AutofillManagerServiceShellCommand(AutofillManagerService.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.AUTOFILL_SERVICE), false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE), false, this, UserHandle.USER_ALL);
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
if (sVerbose) Slog.v(TAG, "onChange(): uri=" + uri + ", userId=" + userId);
synchronized (mLock) {
updateCachedServiceLocked(userId);
}
}
}
}