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