blob: 8b3775657da0a79fe0090a58e29097ad550dfa8b [file] [log] [blame]
/*
* 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 boolean requestAutoFill(int userId, IBinder activityToken) {
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
return getImplOrThrowLocked(userId).requestAutoFill(activityToken);
}
}
@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;
}
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);
}
}
}
}
@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);
}
}
}
}