From ea95a6d790206071f7f212ed50dfe1d752b402ce Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 15 Oct 2018 10:45:01 -0700 Subject: [PATCH] Refactored AutofillManager service classes into common code. Autofill has the following workflow: - AutofillManager talks to an IAutofillManagerService service located on system_server - AutofillManagerService implements the IAutofillManagerService and delegates each call to the AutofillManagerServiceImpl associated with the given Android user - AutofillManagerServiceImpl uses a RemoteFillService class that is responsible for binding to the service that is provided by a 3rd-party - Plus a lot of other plumbing, like getting the name of the package that provides the service using Settings, listening to Settings changes, etc... A lot of this "plumbing" is common to other system services (like TextClassificationManager), so it makes sense to move this logic to common code that can be used. This CL refactors the "main" service classes (AutofillManagerService and AutofillManagerServiceImpl), while RemoteFillService will be refactored later. Bug: 117779333 Test: atest CtsAutoFillServiceTestCases Change-Id: I19bae47b72096e66bd51c3cd6b68f9d3cf8ea708 --- .../autofill/AutofillManagerService.java | 569 ++++++------------ .../autofill/AutofillManagerServiceImpl.java | 201 ++----- .../com/android/server/autofill/Helper.java | 24 +- .../com/android/server/autofill/Session.java | 16 +- .../android/server/autofill/ui/FillUi.java | 12 +- .../server/AbstractMasterSystemService.java | 507 ++++++++++++++++ .../server/AbstractPerUserSystemService.java | 274 +++++++++ 7 files changed, 1023 insertions(+), 580 deletions(-) create mode 100644 services/core/java/com/android/server/AbstractMasterSystemService.java create mode 100644 services/core/java/com/android/server/AbstractPerUserSystemService.java diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index d3842b74990c1..4205ac7d9f865 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -21,14 +21,11 @@ import static android.content.Context.AUTOFILL_MANAGER_SERVICE; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; -import static com.android.server.autofill.Helper.sPartitionMaxCount; -import static com.android.server.autofill.Helper.sVisibleDatasetsMaxCount; import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityThread; import android.content.BroadcastReceiver; @@ -38,13 +35,10 @@ 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.Bundle; -import android.os.Handler; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteCallback; @@ -53,7 +47,6 @@ 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.service.autofill.UserData; @@ -63,7 +56,6 @@ import android.util.ArrayMap; 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; @@ -73,14 +65,12 @@ import android.view.autofill.IAutoFillManagerClient; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -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.AbstractMasterSystemService; 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; @@ -98,10 +88,13 @@ import java.util.Objects; * {@link AutofillManagerServiceImpl} per user; the real work is done by * {@link AutofillManagerServiceImpl} itself. */ -public final class AutofillManagerService extends SystemService { +public final class AutofillManagerService + extends AbstractMasterSystemService { private static final String TAG = "AutofillManagerService"; + private static final Object sLock = AutofillManagerService.class; + static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; private static final char COMPAT_PACKAGE_DELIMITER = ':'; @@ -109,27 +102,28 @@ public final class AutofillManagerService extends SystemService { private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN = '['; private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_END = ']'; - private final Context mContext; + + /** + * Maximum number of partitions that can be allowed in a session. + * + *

Can be modified using {@code cmd autofill set max_partitions} or through + * {@link android.provider.Settings.Global#AUTOFILL_MAX_PARTITIONS_SIZE}. + */ + @GuardedBy("sLock") + private static int sPartitionMaxCount = AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE; + + /** + * Maximum number of visible datasets in the dataset picker UI, or {@code 0} to use default + * value from resources. + * + *

Can be modified using {@code cmd autofill set max_visible_datasets} or through + * {@link android.provider.Settings.Global#AUTOFILL_MAX_VISIBLE_DATASETS}. + */ + @GuardedBy("sLock") + private static int sVisibleDatasetsMaxCount = 0; + 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 LocalLog mWtfHistory = new LocalLog(50); @@ -148,22 +142,19 @@ public final class AutofillManagerService extends SystemService { // 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(); - } + visitServicesLocked((s) -> s.destroyFinishedSessionsLocked()); } - mUi.hideAll(null); } } }; + // TODO(b/117779333): move to superclass / create super-class for ShellCommand @GuardedBy("mLock") private boolean mAllowInstantService; public AutofillManagerService(Context context) { - super(context); - mContext = context; + super(context, UserManager.DISALLOW_AUTOFILL); mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext()); setLogLevelFromSettings(); @@ -172,199 +163,91 @@ public final class AutofillManagerService extends SystemService { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - mContext.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler()); + context.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) { - Slog.i(TAG, "Disabling Autofill for user " + userId); - mDisabledUsers.put(userId, disabled); - } + @Override // from MasterSystemService + protected String getServiceSettingsProperty() { + return Settings.Secure.AUTOFILL_SERVICE; + } + + @Override // from MasterSystemService + protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver, + @NonNull ContentObserver observer) { + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_LOGGING_LEVEL), false, observer, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE), false, observer, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS), false, observer, + UserHandle.USER_ALL); + } + + @Override // from MasterSystemService + protected void onSettingsChanged(int userId, @NonNull String property) { + switch (property) { + case Settings.Global.AUTOFILL_LOGGING_LEVEL: + setLogLevelFromSettings(); + break; + case Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE: + setMaxPartitionsFromSettings(); + break; + case Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS: + setMaxVisibleDatasetsFromSettings(); + break; + default: + Slog.w(TAG, "Unexpected property (" + property + "); updating cache instead"); + // fall through + case Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: + synchronized (mLock) { + updateCachedServiceLocked(userId); + } } - 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); - 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()); - } else { - handlePackageUpdateLocked(packageName); - } - } - } - - @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()); - } else { - handlePackageUpdateLocked(pkg); - } - } - } - 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(); - } - - @GuardedBy("mLock") - private void handlePackageUpdateLocked(String packageName) { - final int size = mServicesCache.size(); - for (int i = 0; i < size; i++) { - mServicesCache.valueAt(i).handlePackageUpdateLocked(packageName); - } - } - }; - - // package changes - monitor.register(mContext, null, UserHandle.ALL, true); + @Override // from MasterSystemService + protected AutofillManagerServiceImpl newServiceLocked(int resolvedUserId, boolean disabled) { + return new AutofillManagerServiceImpl(this, mLock, mRequestsHistory, + mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi, mAutofillCompatState, + disabled); } - @Override + @Override // MasterSystemService + protected AutofillManagerServiceImpl removeCachedServiceLocked(int userId) { + final AutofillManagerServiceImpl service = super.removeCachedServiceLocked(userId); + if (service != null) { + service.destroyLocked(); + mAutofillCompatState.removeCompatibilityModeRequests(userId); + } + return service; + } + + @Override // from MasterSystemService + protected void onServiceEnabledLocked(@NonNull AutofillManagerServiceImpl service, int userId) { + addCompatibilityModeRequestsLocked(service, userId); + } + + @Override // from SystemService public void onStart() { publishBinderService(AUTOFILL_MANAGER_SERVICE, new AutoFillManagerServiceStub()); publishLocalService(AutofillManagerInternal.class, mLocalService); } - @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 + @Override // from SystemService 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. - */ - @GuardedBy("mLock") - @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, mWtfHistory, resolvedUserId, mUi, - mAutofillCompatState, mDisabledUsers.get(resolvedUserId)); - mServicesCache.put(userId, service); - addCompatibilityModeRequestsLocked(service, userId); - } - return service; - } - - /** - * Peeks the service instance for a user. - * - * @return service instance or {@code null} if not already present - */ - @GuardedBy("mLock") - @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); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { if (userId != UserHandle.USER_ALL) { @@ -373,10 +256,7 @@ public final class AutofillManagerService extends SystemService { service.destroySessionsLocked(); } } else { - final int size = mServicesCache.size(); - for (int i = 0; i < size; i++) { - mServicesCache.valueAt(i).destroySessionsLocked(); - } + visitServicesLocked((s) -> s.destroySessionsLocked()); } } @@ -390,7 +270,7 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. void listSessions(int userId, IResultReceiver receiver) { Slog.i(TAG, "listSessions() for userId " + userId); - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); final Bundle resultData = new Bundle(); final ArrayList sessions = new ArrayList<>(); @@ -402,10 +282,7 @@ public final class AutofillManagerService extends SystemService { service.listSessionsLocked(sessions); } } else { - final int size = mServicesCache.size(); - for (int i = 0; i < size; i++) { - mServicesCache.valueAt(i).listSessionsLocked(sessions); - } + visitServicesLocked((s) -> s.listSessionsLocked(sessions)); } } @@ -420,25 +297,22 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. void reset() { Slog.i(TAG, "reset()"); - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().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(); + visitServicesLocked((s) -> s.destroyLocked()); + clearCacheLocked(); } } // Called by Shell command. void setLogLevel(int level) { Slog.i(TAG, "setLogLevel(): " + level); - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); final long token = Binder.clearCallingIdentity(); try { - Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.putInt(getContext().getContentResolver(), Settings.Global.AUTOFILL_LOGGING_LEVEL, level); } finally { Binder.restoreCallingIdentity(token); @@ -447,7 +321,7 @@ public final class AutofillManagerService extends SystemService { private void setLogLevelFromSettings() { final int level = Settings.Global.getInt( - mContext.getContentResolver(), + getContext().getContentResolver(), Settings.Global.AUTOFILL_LOGGING_LEVEL, AutofillManager.DEFAULT_LOGGING_LEVEL); boolean debug = false; boolean verbose = false; @@ -465,14 +339,13 @@ public final class AutofillManagerService extends SystemService { + ", verbose=" + verbose); } synchronized (mLock) { - setDebugLocked(debug); - setVerboseLocked(verbose); + setLoggingLevelsLocked(debug, verbose); } } // Called by Shell command. int getLogLevel() { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { if (sVerbose) return AutofillManager.FLAG_ADD_CLIENT_VERBOSE; @@ -483,7 +356,7 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. int getMaxPartitions() { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { return sPartitionMaxCount; @@ -492,12 +365,12 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. void setMaxPartitions(int max) { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); Slog.i(TAG, "setMaxPartitions(): " + max); final long token = Binder.clearCallingIdentity(); try { - Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.putInt(getContext().getContentResolver(), Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, max); } finally { Binder.restoreCallingIdentity(token); @@ -505,33 +378,33 @@ public final class AutofillManagerService extends SystemService { } private void setMaxPartitionsFromSettings() { - final int max = Settings.Global.getInt(mContext.getContentResolver(), + final int max = Settings.Global.getInt(getContext().getContentResolver(), Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE); if (sDebug) Slog.d(TAG, "setMaxPartitionsFromSettings(): " + max); - synchronized (mLock) { + synchronized (sLock) { sPartitionMaxCount = max; } } // Called by Shell command. int getMaxVisibleDatasets() { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); - synchronized (mLock) { + synchronized (sLock) { return sVisibleDatasetsMaxCount; } } // Called by Shell command. void setMaxVisibleDatasets(int max) { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); Slog.i(TAG, "setMaxVisibleDatasets(): " + max); final long token = Binder.clearCallingIdentity(); try { - Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.putInt(getContext().getContentResolver(), Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, max); } finally { Binder.restoreCallingIdentity(token); @@ -539,11 +412,11 @@ public final class AutofillManagerService extends SystemService { } private void setMaxVisibleDatasetsFromSettings() { - final int max = Settings.Global.getInt(mContext.getContentResolver(), + final int max = Settings.Global.getInt(getContext().getContentResolver(), Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, 0); if (sDebug) Slog.d(TAG, "setMaxVisibleDatasetsFromSettings(): " + max); - synchronized (mLock) { + synchronized (sLock) { sVisibleDatasetsMaxCount = max; } } @@ -551,10 +424,10 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. void getScore(@Nullable String algorithmName, @NonNull String value1, @NonNull String value2, @NonNull RemoteCallback callback) { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); final FieldClassificationStrategy strategy = - new FieldClassificationStrategy(mContext, UserHandle.USER_CURRENT); + new FieldClassificationStrategy(getContext(), UserHandle.USER_CURRENT); strategy.getScores(callback, algorithmName, null, Arrays.asList(AutofillValue.forText(value1)), new String[] { value2 }); @@ -562,19 +435,19 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. Boolean getFullScreenMode() { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); return sFullScreenMode; } // Called by Shell command. void setFullScreenMode(@Nullable Boolean mode) { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); sFullScreenMode = mode; } // Called by Shell command. boolean getAllowInstantService() { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); synchronized (mLock) { return mAllowInstantService; } @@ -582,59 +455,21 @@ public final class AutofillManagerService extends SystemService { // Called by Shell command. void setAllowInstantService(boolean mode) { - mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + getContext().enforceCallingPermission(MANAGE_AUTO_FILL, TAG); Slog.i(TAG, "setAllowInstantService(): " + mode); synchronized (mLock) { mAllowInstantService = mode; } } - private void setDebugLocked(boolean debug) { + private void setLoggingLevelsLocked(boolean debug, boolean verbose) { com.android.server.autofill.Helper.sDebug = debug; android.view.autofill.Helper.sDebug = debug; - } + this.debug = 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. - */ - @GuardedBy("mLock") - private void removeCachedServiceLocked(int userId) { - final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); - if (service != null) { - mServicesCache.delete(userId); - service.destroyLocked(); - mAutofillCompatState.removeCompatibilityModeRequests(userId); - } - } - - /** - * Updates a cached service for a given user. - */ - @GuardedBy("mLock") - private void updateCachedServiceLocked(int userId) { - updateCachedServiceLocked(userId, mDisabledUsers.get(userId)); - } - - /** - * Updates a cached service for a given user. - */ - @GuardedBy("mLock") - private void updateCachedServiceLocked(int userId, boolean disabled) { - AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - service.destroySessionsLocked(); - service.updateLocked(disabled); - if (!service.isEnabledLocked()) { - removeCachedServiceLocked(userId); - } else { - addCompatibilityModeRequestsLocked(service, userId); - } - } + this.verbose = verbose; } private void addCompatibilityModeRequestsLocked(@NonNull AutofillManagerServiceImpl service @@ -664,7 +499,7 @@ public final class AutofillManagerService extends SystemService { private String getWhitelistedCompatModePackagesFromSettings() { return Settings.Global.getString( - mContext.getContentResolver(), + getContext().getContentResolver(), Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES); } @@ -758,6 +593,24 @@ public final class AutofillManagerService extends SystemService { return compatPackages; } + /** + * Gets the maximum number of partitions / fill requests. + */ + public static int getPartitionMaxCount() { + synchronized (sLock) { + return sPartitionMaxCount; + } + } + + /** + * Gets the maxium number of datasets visible in the UI. + */ + public static int getVisibleDatasetsMaxCount() { + synchronized (sLock) { + return sVisibleDatasetsMaxCount; + } + } + private final class LocalService extends AutofillManagerInternal { @Override public void onBackKeyPressed() { @@ -889,20 +742,24 @@ public final class AutofillManagerService extends SystemService { } private void dump(String prefix, PrintWriter pw) { - if (mUserSpecs == null) { - pw.println("N/A"); - return; - } - pw.println(); - final String prefix2 = prefix + " "; - for (int i = 0; i < mUserSpecs.size(); i++) { - final int user = mUserSpecs.keyAt(i); - pw.print(prefix); pw.print("User: "); pw.println(user); - final ArrayMap perUser = mUserSpecs.valueAt(i); - for (int j = 0; j < perUser.size(); j++) { - final String packageName = perUser.keyAt(j); - final PackageCompatState state = perUser.valueAt(j); - pw.print(prefix2); pw.print(packageName); pw.print(": "); pw.println(state); + synchronized (mLock) { + if (mUserSpecs == null) { + pw.println("N/A"); + return; + } + pw.println(); + final String prefix2 = prefix + " "; + for (int i = 0; i < mUserSpecs.size(); i++) { + final int user = mUserSpecs.keyAt(i); + pw.print(prefix); + pw.print("User: "); + pw.println(user); + final ArrayMap perUser = mUserSpecs.valueAt(i); + for (int j = 0; j < perUser.size(); j++) { + final String packageName = perUser.keyAt(j); + final PackageCompatState state = perUser.valueAt(j); + pw.print(prefix2); pw.print(packageName); pw.print(": "); pw.println(state); + } } } } @@ -971,7 +828,7 @@ public final class AutofillManagerService extends SystemService { Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId"); try { - mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, userId); + getContext().getPackageManager().getPackageInfoAsUser(packageName, 0, userId); } catch (PackageManager.NameNotFoundException e) { throw new IllegalArgumentException(packageName + " is not a valid package", e); } @@ -1139,7 +996,7 @@ public final class AutofillManagerService extends SystemService { boolean restored = false; synchronized (mLock) { - final AutofillManagerServiceImpl service = mServicesCache.get(userId); + final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { restored = service.restoreSession(sessionId, getCallingUid(), activityToken, appCallback); @@ -1216,7 +1073,7 @@ public final class AutofillManagerService extends SystemService { public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) { boolean supported = false; synchronized (mLock) { - supported = !mDisabledUsers.get(userId); + supported = !isDisabledLocked(userId); } send(receiver, supported); } @@ -1253,7 +1110,7 @@ public final class AutofillManagerService extends SystemService { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; boolean showHistory = true; boolean uiOnly = false; @@ -1280,102 +1137,48 @@ public final class AutofillManagerService extends SystemService { return; } - boolean oldDebug = sDebug; final String prefix = " "; - final String prefix2 = " "; + boolean realDebug = sDebug; + boolean realVerbose = sVerbose; try { + sDebug = sVerbose = true; 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("sDebug: "); pw.print(realDebug); + pw.print(" sVerbose: "); pw.println(realVerbose); + // Dump per-user services + dumpLocked("", pw); pw.print("Max partitions per session: "); pw.println(sPartitionMaxCount); pw.print("Max visible datasets: "); pw.println(sVisibleDatasetsMaxCount); if (sFullScreenMode != null) { pw.print("Overridden full-screen mode: "); pw.println(sFullScreenMode); } pw.println("User data constraints: "); UserData.dumpConstraints(prefix, pw); - 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(prefix, pw); - } - } mUi.dump(pw); pw.print("Autofill Compat State: "); - mAutofillCompatState.dump(prefix2, pw); - pw.print(prefix2); pw.print("from settings: "); + mAutofillCompatState.dump(prefix, pw); + pw.print("from settings: "); pw.println(getWhitelistedCompatModePackagesFromSettings()); pw.print("Allow instant service: "); pw.println(mAllowInstantService); - } - if (showHistory) { - pw.println(); pw.println("Requests history:"); pw.println(); - mRequestsHistory.reverseDump(fd, pw, args); - pw.println(); pw.println("UI latency history:"); pw.println(); - mUiLatencyHistory.reverseDump(fd, pw, args); - pw.println(); pw.println("WTF history:"); pw.println(); - mWtfHistory.reverseDump(fd, pw, args); + if (showHistory) { + pw.println(); pw.println("Requests history:"); pw.println(); + mRequestsHistory.reverseDump(fd, pw, args); + pw.println(); pw.println("UI latency history:"); pw.println(); + mUiLatencyHistory.reverseDump(fd, pw, args); + pw.println(); pw.println("WTF history:"); pw.println(); + mWtfHistory.reverseDump(fd, pw, args); + } } } finally { - setDebugLocked(oldDebug); + sDebug = realDebug; + sVerbose = realVerbose; } } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - (new AutofillManagerServiceShellCommand(AutofillManagerService.this)).exec( + 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); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, this, - UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.AUTOFILL_LOGGING_LEVEL), false, this, - UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE), false, this, - UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS), 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); - switch (uri.getLastPathSegment()) { - case Settings.Global.AUTOFILL_LOGGING_LEVEL: - setLogLevelFromSettings(); - break; - case Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE: - setMaxPartitionsFromSettings(); - break; - case Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS: - setMaxVisibleDatasetsFromSettings(); - break; - default: - synchronized (mLock) { - updateCachedServiceLocked(userId); - } - } - } - } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 14d68cb853d6e..48103559afb67 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -25,19 +25,14 @@ import static com.android.server.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityTaskManager; import android.app.ActivityManagerInternal; -import android.app.AppGlobals; +import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.metrics.LogMaker; import android.os.AsyncTask; import android.os.Binder; @@ -49,7 +44,6 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; -import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; @@ -60,7 +54,6 @@ import android.service.autofill.FillEventHistory.Event; import android.service.autofill.FillResponse; import android.service.autofill.IAutoFillService; import android.service.autofill.UserData; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; @@ -77,6 +70,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.server.AbstractPerUserSystemService; import com.android.server.LocalServices; import com.android.server.autofill.AutofillManagerService.AutofillCompatState; import com.android.server.autofill.ui.AutoFillUI; @@ -91,7 +85,8 @@ import java.util.Random; * app's {@link IAutoFillService} implementation. * */ -final class AutofillManagerServiceImpl { +final class AutofillManagerServiceImpl + extends AbstractPerUserSystemService { private static final String TAG = "AutofillManagerServiceImpl"; private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; @@ -99,9 +94,6 @@ final class AutofillManagerServiceImpl { /** Minimum interval to prune abandoned sessions */ private static final int MAX_ABANDONED_SESSION_MILLIS = 30000; - private final int mUserId; - private final Context mContext; - private final Object mLock; private final AutoFillUI mUi; private final MetricsLogger mMetricsLogger = new MetricsLogger(); @@ -131,24 +123,12 @@ final class AutofillManagerServiceImpl { @GuardedBy("mLock") private ArrayMap mDisabledActivities; - /** - * Whether service was disabled for user due to {@link UserManager} restrictions. - */ - @GuardedBy("mLock") - private boolean mDisabled; - /** * Data used for field classification. */ @GuardedBy("mLock") private UserData mUserData; - /** - * Caches whether the setup completed for the current user. - */ - @GuardedBy("mLock") - private boolean mSetupComplete; - private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); /** @@ -170,116 +150,27 @@ final class AutofillManagerServiceImpl { /** When was {@link PruneTask} last executed? */ private long mLastPrune = 0; - AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, + AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog requestsHistory, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, boolean disabled) { - mContext = context; - mLock = lock; + super(master, lock, userId); + mRequestsHistory = requestsHistory; mUiLatencyHistory = uiLatencyHistory; mWtfHistory = wtfHistory; - mUserId = userId; mUi = ui; - mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId); + mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId); mAutofillCompatState = autofillCompatState; updateLocked(disabled); } @GuardedBy("mLock") - private int getServiceUidLocked() { - if (mInfo == null) { - Slog.w(TAG, "getServiceUidLocked(): no mInfo"); - return -1; - } - return mInfo.getServiceInfo().applicationInfo.uid; - } - - - @Nullable - String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) { - return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId); - } - - @Nullable - String getServicePackageName() { - final ComponentName serviceComponent = getServiceComponentName(); - if (serviceComponent != null) { - return serviceComponent.getPackageName(); - } - return null; - } - - @Nullable - ComponentName getServiceComponentName() { - synchronized (mLock) { - if (mInfo == null) { - return null; - } - return mInfo.getServiceInfo().getComponentName(); - } - } - - int getTargedSdkLocked() { - if (mInfo == null) { - return 0; - } - return mInfo.getServiceInfo().applicationInfo.targetSdkVersion; - } - - private boolean isSetupCompletedLocked() { - final String setupComplete = Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId); - return "1".equals(setupComplete); - } - - private String getComponentNameFromSettings() { - return Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId); - } - - @GuardedBy("mLock") - void updateLocked(boolean disabled) { - final boolean wasEnabled = isEnabledLocked(); - if (sVerbose) { - Slog.v(TAG, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled - + ", mSetupComplete= " + mSetupComplete - + ", disabled=" + disabled + ", mDisabled=" + mDisabled); - } - mSetupComplete = isSetupCompletedLocked(); - mDisabled = disabled; - ComponentName serviceComponent = null; - ServiceInfo serviceInfo = null; - final String componentName = getComponentNameFromSettings(); - if (!TextUtils.isEmpty(componentName)) { - try { - serviceComponent = ComponentName.unflattenFromString(componentName); - serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, - 0, mUserId); - if (serviceInfo == null) { - Slog.e(TAG, "Bad AutofillService name: " + componentName); - } - } catch (RuntimeException | RemoteException e) { - Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e); - serviceInfo = null; - } - } - try { - if (serviceInfo != null) { - mInfo = new AutofillServiceInfo(mContext, serviceComponent, mUserId); - if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo); - } else { - mInfo = null; - if (sDebug) { - Slog.d(TAG, "Reset component for user " + mUserId + " (" + componentName + ")"); - } - } - } catch (Exception e) { - Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e); - mInfo = null; - } - final boolean isEnabled = isEnabledLocked(); - if (wasEnabled != isEnabled) { - if (!isEnabled) { + @Override // from PerUserSystemService + protected boolean updateLocked(boolean disabled) { + destroySessionsLocked(); + final boolean enabledChanged = super.updateLocked(disabled); + if (enabledChanged) { + if (!isEnabledLocked()) { final int sessionCount = mSessions.size(); for (int i = sessionCount - 1; i >= 0; i--) { final Session session = mSessions.valueAt(i); @@ -288,6 +179,19 @@ final class AutofillManagerServiceImpl { } sendStateToClients(false); } + return enabledChanged; + } + + @Override // from PerUserSystemService + protected ServiceInfo newServiceInfo(@NonNull ComponentName serviceComponent) + throws NameNotFoundException { + mInfo = new AutofillServiceInfo(getContext(), serviceComponent, mUserId); + return mInfo.getServiceInfo(); + } + + @Nullable + String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) { + return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId); } @GuardedBy("mLock") @@ -469,7 +373,7 @@ final class AutofillManagerServiceImpl { if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) { mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF, componentName.getPackageName()); - Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, null, mUserId); destroySessionsLocked(); } else { @@ -501,7 +405,7 @@ final class AutofillManagerServiceImpl { assertCallerLocked(componentName, compatMode); - final Session newSession = new Session(this, mUi, mContext, mHandler, mUserId, mLock, + final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock, sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory, mWtfHistory, mInfo.getServiceInfo().getComponentName(), componentName, compatMode, bindInstantServiceAllowed, flags); @@ -515,7 +419,7 @@ final class AutofillManagerServiceImpl { */ private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) { final String packageName = componentName.getPackageName(); - final PackageManager pm = mContext.getPackageManager(); + final PackageManager pm = getContext().getPackageManager(); final int callingUid = Binder.getCallingUid(); final int packageUid; try { @@ -651,7 +555,8 @@ final class AutofillManagerServiceImpl { } @GuardedBy("mLock") - void handlePackageUpdateLocked(String packageName) { + @Override // from PerUserSystemService + protected void handlePackageUpdateLocked(@NonNull String packageName) { final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo(); if (serviceInfo != null && serviceInfo.packageName.equals(packageName)) { resetExtServiceLocked(); @@ -690,29 +595,6 @@ final class AutofillManagerServiceImpl { } } - /** - * Gets the user-visibile name of the service this service binds to, or {@code null} if the - * service is disabled. - */ - @Nullable - @GuardedBy("mLock") - public CharSequence getServiceLabelLocked() { - return mInfo == null ? null : mInfo.getServiceInfo().loadSafeLabel( - mContext.getPackageManager(), 0 /* do not ellipsize */, - PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE | PackageItemInfo.SAFE_LABEL_FLAG_TRIM); - } - - /** - * Gets the icon of the service this service binds to, or {@code null} if the service is - * disabled. - */ - @NonNull - @Nullable - @GuardedBy("mLock") - Drawable getServiceIconLocked() { - return mInfo == null ? null : mInfo.getServiceInfo().loadIcon(mContext.getPackageManager()); - } - /** * Initializes the last fill selection after an autofill service returned a new * {@link FillResponse}. @@ -941,11 +823,13 @@ final class AutofillManagerServiceImpl { return true; } + @Override @GuardedBy("mLock") - void dumpLocked(String prefix, PrintWriter pw) { + protected void dumpLocked(String prefix, PrintWriter pw) { + super.dumpLocked(prefix, pw); + final String prefix2 = prefix + " "; - pw.print(prefix); pw.print("User: "); pw.println(mUserId); pw.print(prefix); pw.print("UID: "); pw.println(getServiceUidLocked()); pw.print(prefix); pw.print("Autofill Service Info: "); if (mInfo == null) { @@ -958,9 +842,8 @@ final class AutofillManagerServiceImpl { } pw.print(prefix); pw.print("Component from settings: "); pw.println(getComponentNameFromSettings()); - pw.print(prefix); pw.print("Default component: "); - pw.println(mContext.getString(R.string.config_defaultAutofillService)); - pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); + pw.print(prefix); pw.print("Default component: "); pw.println(getContext() + .getString(R.string.config_defaultAutofillService)); pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); pw.print(prefix); pw.print("Compat pkgs: "); @@ -970,7 +853,6 @@ final class AutofillManagerServiceImpl { } else { pw.println(compatPkgs); } - pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); pw.print(prefix); pw.print("Disabled apps: "); @@ -1158,11 +1040,6 @@ final class AutofillManagerServiceImpl { return true; } - @GuardedBy("mLock") - boolean isEnabledLocked() { - return mSetupComplete && mInfo != null && !mDisabled; - } - /** * Called by {@link Session} when service asked to disable autofill for an app. */ @@ -1270,7 +1147,7 @@ final class AutofillManagerServiceImpl { // Called by internally, no need to check UID. boolean isFieldClassificationEnabledLocked() { return Settings.Secure.getIntForUser( - mContext.getContentResolver(), + getContext().getContentResolver(), Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 1, mUserId) == 1; } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 420c2be008cb4..3c0da7d2d388e 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -28,20 +28,21 @@ import android.util.ArraySet; import android.util.Slog; import android.view.WindowManager; import android.view.autofill.AutofillId; -import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.LinkedList; public final class Helper { private static final String TAG = "AutofillHelper"; + // TODO(b/117779333): get rid of sDebug / sVerbose and always use the service variables instead + /** * Defines a logging flag that can be dynamically changed at runtime using * {@code cmd autofill set log_level debug} or through @@ -56,23 +57,6 @@ public final class Helper { */ public static boolean sVerbose = false; - /** - * Maximum number of partitions that can be allowed in a session. - * - *

Can be modified using {@code cmd autofill set max_partitions} or through - * {@link android.provider.Settings.Global#AUTOFILL_MAX_PARTITIONS_SIZE}. - */ - static int sPartitionMaxCount = AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE; - - /** - * Maximum number of visible datasets in the dataset picker UI, or {@code 0} to use default - * value from resources. - * - *

Can be modified using {@code cmd autofill set max_visible_datasets} or through - * {@link android.provider.Settings.Global#AUTOFILL_MAX_VISIBLE_DATASETS}. - */ - public static int sVisibleDatasetsMaxCount = 0; - /** * When non-null, overrides whether the UI should be shown on full-screen mode. * @@ -162,7 +146,7 @@ public final class Helper { private static ViewNode findViewNode(@NonNull AssistStructure structure, @NonNull ViewNodeFilter filter) { - final LinkedList nodesToProcess = new LinkedList<>(); + final ArrayDeque nodesToProcess = new ArrayDeque<>(); final int numWindowNodes = structure.getWindowNodeCount(); for (int i = 0; i < numWindowNodes; i++) { nodesToProcess.add(structure.getWindowNodeAt(i).getRootViewNode()); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f85749af54fdc..86432944ef4d0 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -27,7 +27,6 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.autofill.Helper.getNumericValue; 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 static com.android.server.autofill.Helper.toArray; import static com.android.server.autofill.ViewState.STATE_RESTARTED_SESSION; @@ -65,7 +64,6 @@ import android.service.autofill.AutofillService; import android.service.autofill.Dataset; import android.service.autofill.FieldClassification; import android.service.autofill.FieldClassification.Match; -import android.text.TextUtils; import android.service.autofill.FillContext; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; @@ -75,10 +73,10 @@ import android.service.autofill.SaveInfo; import android.service.autofill.SaveRequest; import android.service.autofill.UserData; import android.service.autofill.ValueFinder; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -2095,9 +2093,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final int numResponses = mResponses.size(); - if (numResponses >= sPartitionMaxCount) { + if (numResponses >= AutofillManagerService.getPartitionMaxCount()) { Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id - + " reached maximum of " + sPartitionMaxCount); + + " reached maximum of " + AutofillManagerService.getPartitionMaxCount()); return false; } @@ -2289,7 +2287,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags); // Remove the UI if the ViewState has changed. - if (mCurrentViewId != viewState.id) { + if (!Objects.equals(mCurrentViewId, viewState.id)) { mUi.hideFillUi(this); mCurrentViewId = viewState.id; } @@ -2298,7 +2296,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.update(value, virtualBounds, flags); break; case ACTION_VIEW_EXITED: - if (mCurrentViewId == viewState.id) { + if (Objects.equals(mCurrentViewId, viewState.id)) { if (sVerbose) Slog.d(TAG, "Exiting view " + id); mUi.hideFillUi(this); mCurrentViewId = null; @@ -3077,7 +3075,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void wtf(@Nullable Exception e, String fmt, Object...args) { final String message = String.format(fmt, args); - mWtfHistory.log(message); + synchronized (mLock) { + mWtfHistory.log(message); + } if (e != null) { Slog.wtf(TAG, message, e); diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index f79f6ff73d29e..d1fe970c07831 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -19,25 +19,22 @@ import static com.android.server.autofill.Helper.paramsToString; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; import static com.android.server.autofill.Helper.sVerbose; -import static com.android.server.autofill.Helper.sVisibleDatasetsMaxCount; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.PendingIntent; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.view.ContextThemeWrapper; -import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.service.autofill.Dataset; import android.service.autofill.Dataset.DatasetFieldFilter; import android.service.autofill.FillResponse; import android.text.TextUtils; import android.util.Slog; import android.util.TypedValue; +import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -60,6 +57,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.server.UiThread; +import com.android.server.autofill.AutofillManagerService; import com.android.server.autofill.Helper; import java.io.PrintWriter; @@ -193,8 +191,8 @@ final class FillUi { } }); - if (sVisibleDatasetsMaxCount > 0) { - mVisibleDatasetsMaxCount = sVisibleDatasetsMaxCount; + if (AutofillManagerService.getVisibleDatasetsMaxCount() > 0) { + mVisibleDatasetsMaxCount = AutofillManagerService.getVisibleDatasetsMaxCount(); if (sVerbose) { Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount); } diff --git a/services/core/java/com/android/server/AbstractMasterSystemService.java b/services/core/java/com/android/server/AbstractMasterSystemService.java new file mode 100644 index 0000000000000..c955daf2fae4a --- /dev/null +++ b/services/core/java/com/android/server/AbstractMasterSystemService.java @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.UserManagerInternal; +import android.provider.Settings; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; + +import java.io.PrintWriter; +import java.util.List; + +/** + * Base class for {@link SystemService SystemServices} that support multi user. + * + *

Subclasses of this service are just a facade for the service binder calls - the "real" work + * is done by the {@link AbstractPerUserSystemService} subclasses, which are automatically managed + * through an user -> service cache. + * + *

It also takes care of other plumbing tasks such as: + * + *

+ * + *

See {@code com.android.server.autofill.AutofillManagerService} for a concrete + * (no pun intended) example of how to use it. + * + * @param "real" service class. + * + * @hide + */ +// TODO(b/117779333): improve javadoc above instead of using Autofill as an example +public abstract class AbstractMasterSystemService> + extends SystemService { + + /** + * Log tag + */ + protected final String mTag = getClass().getSimpleName(); + + /** + * Lock used to synchronize access to internal state; should be acquired before calling a + * method whose name ends with {@code locked}. + */ + protected final Object mLock = new Object(); + + /** + * Whether the service should log debug statements. + */ + public boolean verbose = false; + + /** + * Whether the service should log verbose statements. + */ + public boolean debug = false; + + /** + * Users disabled due to {@link UserManager} restrictions, or {@code null} if the service cannot + * be disabled through {@link UserManager}. + */ + @GuardedBy("mLock") + @Nullable + private final SparseBooleanArray mDisabledUsers; + + /** + * Cache of services per user id. + */ + @GuardedBy("mLock") + private final SparseArray mServicesCache = new SparseArray<>(); + + /** + * Default constructor. + * + * @param context system context. + * @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that + * disables the service. + */ + protected AbstractMasterSystemService(@NonNull Context context, + @Nullable String disallowProperty) { + super(context); + + if (disallowProperty == null) { + mDisabledUsers = null; + } else { + mDisabledUsers = new SparseBooleanArray(); + // 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, disallowProperty); + if (disabled) { + Slog.i(mTag, "Disabling for user " + userId); + mDisabledUsers.put(userId, disabled); + } + } + umi.addUserRestrictionsListener((userId, newRestrictions, prevRestrictions) -> { + final boolean disabledNow = + newRestrictions.getBoolean(disallowProperty, false); + synchronized (mLock) { + final boolean disabledBefore = mDisabledUsers.get(userId); + if (disabledBefore == disabledNow) { + // Nothing changed, do nothing. + if (debug) { + Slog.d(mTag, "Restriction did not change for user " + userId); + return; + } + } + Slog.i(mTag, "Updating for user " + userId + ": disabled=" + disabledNow); + mDisabledUsers.put(userId, disabledNow); + updateCachedServiceLocked(userId, disabledNow); + } + }); + } + startTrackingPackageChanges(); + } + + @Override // from SystemService + public void onBootPhase(int phase) { + if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + new SettingsObserver(BackgroundThread.getHandler()); + } + } + + @Override // from SystemService + public void onUnlockUser(int userId) { + synchronized (mLock) { + updateCachedServiceLocked(userId); + } + } + + @Override // from SystemService + public void onCleanupUser(int userId) { + synchronized (mLock) { + removeCachedServiceLocked(userId); + } + } + + /** + * Creates a new service that will be added to the cache. + * + * @param resolvedUserId the resolved user id for the service. + * @param disabled whether the service is currently disabled (due to {@link UserManager} + * restrictions). + * + * @return a new instance. + */ + protected abstract S newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled); + + /** + * Register the service for extra Settings changes (i.e., other than + * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} or + * {@link #getServiceSettingsProperty()}, which are automatically handled). + * + *

Example: + * + *


+     * resolver.registerContentObserver(Settings.Global.getUriFor(
+     *     Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer,
+     *     UserHandle.USER_ALL);
+     * 
+ * + *

NOTE:

it doesn't need to register for + * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} or + * {@link #getServiceSettingsProperty()}. + * + */ + @SuppressWarnings("unused") + protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver, + @NonNull ContentObserver observer) { + } + + /** + * Callback for Settings changes that were registered though + * {@link #registerForExtraSettingsChanges(ContentResolver, ContentObserver)}. + * + * @param userId user associated with the change + * @param property Settings property changed. + */ + protected void onSettingsChanged(@UserIdInt int userId, @NonNull String property) { + } + + /** + * Gets the service instance for an user, creating an instance if not present in the cache. + */ + @GuardedBy("mLock") + @NonNull + protected S getServiceForUserLocked(@UserIdInt int userId) { + final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, false, null, null); + S service = mServicesCache.get(resolvedUserId); + if (service == null) { + final boolean disabled = isDisabledLocked(userId); + service = newServiceLocked(resolvedUserId, disabled); + if (!disabled) { + onServiceEnabledLocked(service, resolvedUserId); + } + mServicesCache.put(userId, service); + } + return service; + } + + /** + * Gets the existing service instance for a user, returning {@code null} if not already + * present in the cache. + */ + @GuardedBy("mLock") + @Nullable + protected S peekServiceForUserLocked(int userId) { + final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, false, null, null); + return mServicesCache.get(resolvedUserId); + } + + /** + * Updates a cached service for a given user. + */ + @GuardedBy("mLock") + protected void updateCachedServiceLocked(int userId) { + updateCachedServiceLocked(userId, isDisabledLocked(userId)); + } + + /** + * Checks whether the service is disabled (through {@link UserManager} restrictions) for the + * given user. + */ + protected boolean isDisabledLocked(int userId) { + return mDisabledUsers == null ? false : mDisabledUsers.get(userId); + } + + /** + * Updates a cached service for a given user. + * + * @param userId user handle. + * @param disabled whether the user is disabled. + * @return service for the user. + */ + @GuardedBy("mLock") + protected S updateCachedServiceLocked(int userId, boolean disabled) { + final S service = getServiceForUserLocked(userId); + if (service != null) { + service.updateLocked(disabled); + if (!service.isEnabledLocked()) { + removeCachedServiceLocked(userId); + } else { + onServiceEnabledLocked(service, userId); + } + } + return service; + } + + /** + * Gets the Settings property that defines the name of the component name used to bind this + * service to an external service, or {@code null} when the service is not defined by such + * property (for example, if it's a system service defined by framework resources). + */ + @Nullable + protected String getServiceSettingsProperty() { + return null; + } + + /** + * Callback called after a new service was added to the cache, or an existing service that was + * previously disabled gets enabled. + * + *

By default doesn't do anything, but can be overridden by subclasses. + */ + @SuppressWarnings("unused") + protected void onServiceEnabledLocked(S service, @UserIdInt int userId) { + } + + /** + * Removes a cached service for a given user. + * + * @return the removed service; + */ + @GuardedBy("mLock") + @NonNull + protected S removeCachedServiceLocked(@UserIdInt int userId) { + final S service = peekServiceForUserLocked(userId); + if (service != null) { + mServicesCache.delete(userId); + } + return service; + } + + /** + * Visits all services in the cache. + */ + @GuardedBy("mLock") + protected void visitServicesLocked(@NonNull Visitor visitor) { + final int size = mServicesCache.size(); + for (int i = 0; i < size; i++) { + visitor.visit(mServicesCache.valueAt(i)); + } + } + + /** + * Clear the cache by removing all services. + */ + @GuardedBy("mLock") + protected void clearCacheLocked() { + mServicesCache.clear(); + } + + // TODO(b/117779333): support proto + protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { + boolean realDebug = debug; + boolean realVerbose = verbose; + + try { + // Temporarily turn on full logging; + debug = verbose = true; + final int size = mServicesCache.size(); + pw.print(prefix); pw.print("Debug: "); pw.print(realDebug); + pw.print(" Verbose: "); pw.println(realVerbose); + pw.print(prefix); pw.print("Disabled users: "); pw.println(mDisabledUsers); + pw.print(prefix); pw.print("Settings property: "); pw.println( + getServiceSettingsProperty()); + pw.print(prefix); pw.print("Cached services: "); + if (size == 0) { + pw.println("none"); + } else { + pw.println(size); + final String prefix2 = " "; + for (int i = 0; i < size; i++) { + pw.print(prefix); pw.print("Service at "); pw.print(i); pw.println(": "); + final S service = mServicesCache.valueAt(i); + service.dumpLocked(prefix2, pw); + pw.println(); + } + } + } finally { + debug = realDebug; + verbose = realVerbose; + } + } + + 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 = getActiveServicePackageName(); + if (packageName.equals(activePackageName)) { + removeCachedServiceLocked(getChangingUserId()); + } else { + handlePackageUpdateLocked(packageName); + } + } + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + synchronized (mLock) { + final int userId = getChangingUserId(); + final S service = peekServiceForUserLocked(userId); + if (service != null) { + final ComponentName componentName = service.getServiceComponentName(); + if (componentName != null) { + if (packageName.equals(componentName.getPackageName())) { + handleActiveServiceRemoved(userId); + } + } + } + } + } + + @Override + public boolean onHandleForceStop(Intent intent, String[] packages, + int uid, boolean doit) { + synchronized (mLock) { + final String activePackageName = getActiveServicePackageName(); + for (String pkg : packages) { + if (pkg.equals(activePackageName)) { + if (!doit) { + return true; + } + removeCachedServiceLocked(getChangingUserId()); + } else { + handlePackageUpdateLocked(pkg); + } + } + } + return false; + } + + private void handleActiveServiceRemoved(@UserIdInt int userId) { + removeCachedServiceLocked(userId); + final String serviceSettingsProperty = getServiceSettingsProperty(); + if (serviceSettingsProperty != null) { + Settings.Secure.putStringForUser(getContext().getContentResolver(), + serviceSettingsProperty, null, userId); + } + } + + private String getActiveServicePackageName() { + final int userId = getChangingUserId(); + final S service = peekServiceForUserLocked(userId); + if (service == null) { + return null; + } + final ComponentName serviceComponent = service.getServiceComponentName(); + if (serviceComponent == null) { + return null; + } + return serviceComponent.getPackageName(); + } + + @GuardedBy("mLock") + private void handlePackageUpdateLocked(String packageName) { + visitServicesLocked((s) -> s.handlePackageUpdateLocked(packageName)); + } + }; + + // package changes + monitor.register(getContext(), null, UserHandle.ALL, true); + } + + /** + * Visitor pattern. + * + * @param visited class. + */ + public interface Visitor { + /** + * Visits a service. + * + * @param service the service to be visited. + */ + void visit(@NonNull S service); + } + + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + ContentResolver resolver = getContext().getContentResolver(); + final String serviceProperty = getServiceSettingsProperty(); + if (serviceProperty != null) { + resolver.registerContentObserver(Settings.Secure.getUriFor( + serviceProperty), false, this, UserHandle.USER_ALL); + } + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.USER_SETUP_COMPLETE), false, this, UserHandle.USER_ALL); + registerForExtraSettingsChanges(resolver, this); + } + + @Override + public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) { + if (verbose) Slog.v(mTag, "onChange(): uri=" + uri + ", userId=" + userId); + final String property = uri.getLastPathSegment(); + if (property.equals(getServiceSettingsProperty()) + || property.equals(Settings.Secure.USER_SETUP_COMPLETE)) { + synchronized (mLock) { + updateCachedServiceLocked(userId); + } + } else { + onSettingsChanged(userId, property); + } + } + } +} diff --git a/services/core/java/com/android/server/AbstractPerUserSystemService.java b/services/core/java/com/android/server/AbstractPerUserSystemService.java new file mode 100644 index 0000000000000..201abe6892832 --- /dev/null +++ b/services/core/java/com/android/server/AbstractPerUserSystemService.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ServiceInfo; +import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.io.PrintWriter; + +/** + * Companion for {@link AbstractMasterSystemService}, it's the base class for the "real" service + * implementation. + * + * @param itself + * + * @hide + */ +public abstract class AbstractPerUserSystemService> { + + protected final @UserIdInt int mUserId; + protected final Object mLock; + protected final String mTag = getClass().getSimpleName(); + + protected final AbstractMasterSystemService mMaster; + + /** + * Whether service was disabled for user due to {@link UserManager} restrictions. + */ + @GuardedBy("mLock") + private boolean mDisabled; + + /** + * Caches whether the setup completed for the current user. + */ + @GuardedBy("mLock") + private boolean mSetupComplete; + + @GuardedBy("mLock") + private ServiceInfo mServiceInfo; + + protected AbstractPerUserSystemService(@NonNull AbstractMasterSystemService master, + @NonNull Object lock, @UserIdInt int userId) { + mMaster = master; + mLock = lock; + mUserId = userId; + } + + /** + * Creates a new {@link ServiceInfo} for the given service name. + * + * @throws NameNotFoundException if the service does not exist. + * @throws SecurityException if the service does not have the proper permissions to be bound to. + */ + protected abstract ServiceInfo newServiceInfo(@NonNull ComponentName serviceComponent) + throws NameNotFoundException; + + /** + * Callback called when an app has been updated. + * + * @param packageName package of the app being updated. + */ + protected void handlePackageUpdateLocked(@NonNull String packageName) { + } + + /** + * Gets whether the service is enabled and ready. + */ + @GuardedBy("mLock") + protected boolean isEnabledLocked() { + return mSetupComplete && mServiceInfo != null && !mDisabled; + } + + /** + * Updates the state of this service. + * + *

Typically called when the service {@link Settings} property or {@link UserManager} + * restriction changed, which includes the initial creation of the service. + * + *

Subclasses can extend this method to provide extra initialization. + * + * @param disabled whether the service is disabled (due to {@link UserManager} restrictions). + * + * @return whether the disabled state changed. + */ + @GuardedBy("mLock") + @CallSuper + protected boolean updateLocked(boolean disabled) { + + final boolean wasEnabled = isEnabledLocked(); + if (mMaster.verbose) { + Slog.v(mTag, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled + + ", mSetupComplete=" + mSetupComplete + + ", disabled=" + disabled + ", mDisabled=" + mDisabled); + } + + mSetupComplete = isSetupCompletedLocked(); + mDisabled = disabled; + ComponentName serviceComponent = null; + ServiceInfo serviceInfo = null; + final String componentName = getComponentNameFromSettings(); + if (!TextUtils.isEmpty(componentName)) { + try { + serviceComponent = ComponentName.unflattenFromString(componentName); + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + 0, mUserId); + if (serviceInfo == null) { + Slog.e(mTag, "Bad service name: " + componentName); + } + } catch (RuntimeException | RemoteException e) { + Slog.e(mTag, "Error getting service info for '" + componentName + "': " + e); + serviceInfo = null; + } + } + try { + if (serviceInfo != null) { + mServiceInfo = newServiceInfo(serviceComponent); + if (mMaster.debug) { + Slog.d(mTag, "Set component for user " + mUserId + " as " + mServiceInfo); + } + } else { + mServiceInfo = null; + if (mMaster.debug) { + Slog.d(mTag, "Reset component for user " + mUserId + ":" + componentName); + } + } + } catch (Exception e) { + Slog.e(mTag, "Bad ServiceInfo for '" + componentName + "': " + e); + mServiceInfo = null; + } + return wasEnabled != isEnabledLocked(); + } + + /** + * Gets this UID of the remote service this service binds to, or {@code -1} if the service is + * disabled. + */ + @GuardedBy("mLock") + protected final int getServiceUidLocked() { + if (mServiceInfo == null) { + Slog.w(mTag, "getServiceUidLocked(): no mServiceInfo"); + return Process.INVALID_UID; + } + return mServiceInfo.applicationInfo.uid; + } + + /** + * Gets this name of the remote service this service binds to as defined by {@link Settings}. + */ + @Nullable + protected final String getComponentNameFromSettings() { + final String property = mMaster.getServiceSettingsProperty(); + return property == null ? null : Settings.Secure + .getStringForUser(getContext().getContentResolver(), property, mUserId); + } + + /** + * Gets the {@link ComponentName} of the remote service this service binds to, or {@code null} + * if the service is disabled. + */ + @Nullable + public final ComponentName getServiceComponentName() { + synchronized (mLock) { + return mServiceInfo == null ? null : mServiceInfo.getComponentName(); + } + } + /** + * Gets the name of the of the app this service binds to, or {@code null} if the service is + * disabled. + */ + @Nullable + public final String getServicePackageName() { + final ComponentName serviceComponent = getServiceComponentName(); + return serviceComponent == null ? null : serviceComponent.getPackageName(); + } + + /** + * Gets the user-visibile name of the service this service binds to, or {@code null} if the + * service is disabled. + */ + @Nullable + @GuardedBy("mLock") + public final CharSequence getServiceLabelLocked() { + return mServiceInfo == null ? null : mServiceInfo.loadSafeLabel( + getContext().getPackageManager(), 0 /* do not ellipsize */, + PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE | PackageItemInfo.SAFE_LABEL_FLAG_TRIM); + } + + /** + * Gets the icon the service this service binds to, or {@code null} if the service is disabled. + */ + @Nullable + @GuardedBy("mLock") + public final Drawable getServiceIconLocked() { + return mServiceInfo == null ? null + : mServiceInfo.loadIcon(getContext().getPackageManager()); + } + + /** + * Whether the service should log debug statements. + */ + public final boolean isDebug() { + return mMaster.debug; + } + + /** + * Whether the service should log verbose statements. + */ + public final boolean isVerbose() { + return mMaster.verbose; + } + + /** + * Gets the target SDK level of the service this service binds to, + * or {@code 0} if the service is disabled. + */ + public final int getTargedSdkLocked() { + return mServiceInfo == null ? 0 : mServiceInfo.applicationInfo.targetSdkVersion; + } + + /** + * Gets whether the device already finished setup. + */ + protected final boolean isSetupCompletedLocked() { + final String setupComplete = Settings.Secure.getStringForUser( + getContext().getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId); + return "1".equals(setupComplete); + } + + /** + * Gets the context associated with this service. + */ + protected final Context getContext() { + return mMaster.getContext(); + } + + // TODO(b/117779333): support proto + @GuardedBy("mLock") + protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { + pw.print(prefix); pw.print("User: "); pw.println(mUserId); + pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); + pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); + pw.print(prefix); pw.print("Service name: "); pw.println(getComponentNameFromSettings()); + } +}