Loading services/accessibility/accessibility.aconfig +7 −0 Original line number Original line Diff line number Diff line Loading @@ -48,3 +48,10 @@ flag { description: "Stops using the deprecated PackageListObserver." description: "Stops using the deprecated PackageListObserver." bug: "304561459" bug: "304561459" } } flag { name: "scan_packages_without_lock" namespace: "accessibility" description: "Scans packages for accessibility service/activity info without holding the A11yMS lock" bug: "295969873" } services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +151 −41 Original line number Original line Diff line number Diff line Loading @@ -290,14 +290,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList = new ArrayList<>(); private final IntArray mTempIntArray = new IntArray(0); private final IntArray mTempIntArray = new IntArray(0); private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = new RemoteCallbackList<>(); new RemoteCallbackList<>(); private PackageMonitor mPackageMonitor; @VisibleForTesting @VisibleForTesting final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); Loading Loading @@ -531,6 +530,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub disableAccessibilityMenuToMigrateIfNeeded(); disableAccessibilityMenuToMigrateIfNeeded(); } } /** * Returns if the current thread is holding {@link #mLock}. Used for testing * deadlock bug fixes. * * <p><strong>Warning:</strong> this should not be used for production logic * because by the time you receive an answer it may no longer be valid. * </p> */ @VisibleForTesting boolean unsafeIsLockHeld() { return Thread.holdsLock(mLock); } @Override @Override public int getCurrentUserIdLocked() { public int getCurrentUserIdLocked() { return mCurrentUserId; return mCurrentUserId; Loading Loading @@ -690,6 +702,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } } private void onSomePackagesChangedLocked( @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { final AccessibilityUserState userState = getCurrentUserStateLocked(); // Reload the installed services since some services may have different attributes // or resolve info (does not support equals), etc. Remove them then to force reload. userState.mInstalledServices.clear(); if (readConfigurationForUserStateLocked(userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) { onUserStateChangedLocked(userState); } } private void onPackageRemovedLocked(String packageName) { private void onPackageRemovedLocked(String packageName) { final AccessibilityUserState userState = getCurrentUserState(); final AccessibilityUserState userState = getCurrentUserState(); final Predicate<ComponentName> filter = final Predicate<ComponentName> filter = Loading Loading @@ -721,8 +746,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } } @VisibleForTesting PackageMonitor getPackageMonitor() { return mPackageMonitor; } private void registerBroadcastReceivers() { private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { mPackageMonitor = new PackageMonitor() { @Override @Override public void onSomePackagesChanged() { public void onSomePackagesChanged() { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { Loading @@ -730,15 +760,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FLAGS_PACKAGE_BROADCAST_RECEIVER); FLAGS_PACKAGE_BROADCAST_RECEIVER); } } final int userId = getChangingUserId(); List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; if (Flags.scanPackagesWithoutLock()) { parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); } synchronized (mLock) { synchronized (mLock) { // Only the profile parent can install accessibility services. // Only the profile parent can install accessibility services. // Therefore we ignore packages from linked profiles. // Therefore we ignore packages from linked profiles. if (getChangingUserId() != mCurrentUserId) { if (userId != mCurrentUserId) { return; return; } } if (Flags.scanPackagesWithoutLock()) { onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } else { onSomePackagesChangedLocked(); onSomePackagesChangedLocked(); } } } } } @Override @Override public void onPackageUpdateFinished(String packageName, int uid) { public void onPackageUpdateFinished(String packageName, int uid) { Loading @@ -751,8 +793,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FLAGS_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER, "packageName=" + packageName + ";uid=" + uid); "packageName=" + packageName + ";uid=" + uid); } } synchronized (mLock) { final int userId = getChangingUserId(); final int userId = getChangingUserId(); List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; if (Flags.scanPackagesWithoutLock()) { parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); } synchronized (mLock) { if (userId != mCurrentUserId) { if (userId != mCurrentUserId) { return; return; } } Loading @@ -765,8 +813,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Reloads the installed services info to make sure the rebound service could // Reloads the installed services info to make sure the rebound service could // get a new one. // get a new one. userState.mInstalledServices.clear(); userState.mInstalledServices.clear(); final boolean configurationChanged = final boolean configurationChanged; readConfigurationForUserStateLocked(userState); if (Flags.scanPackagesWithoutLock()) { configurationChanged = readConfigurationForUserStateLocked(userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } else { configurationChanged = readConfigurationForUserStateLocked(userState); } if (reboundAService || configurationChanged) { if (reboundAService || configurationChanged) { onUserStateChangedLocked(userState); onUserStateChangedLocked(userState); } } Loading Loading @@ -839,7 +892,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }; }; // package changes // package changes monitor.register(mContext, null, UserHandle.ALL, true); mPackageMonitor.register(mContext, null, UserHandle.ALL, true); if (!Flags.deprecatePackageListObserver()) { if (!Flags.deprecatePackageListObserver()) { final PackageManagerInternal pm = LocalServices.getService( final PackageManagerInternal pm = LocalServices.getService( Loading Loading @@ -1831,8 +1884,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager.onTouchInteractionEnd(); mA11yWindowManager.onTouchInteractionEnd(); } } private void switchUser(int userId) { @VisibleForTesting void switchUser(int userId) { mMagnificationController.updateUserIdIfNeeded(userId); mMagnificationController.updateUserIdIfNeeded(userId); List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; if (Flags.scanPackagesWithoutLock()) { parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); } synchronized (mLock) { synchronized (mLock) { if (mCurrentUserId == userId && mInitialized) { if (mCurrentUserId == userId && mInitialized) { return; return; Loading @@ -1857,7 +1917,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mCurrentUserId = userId; mCurrentUserId = userId; AccessibilityUserState userState = getCurrentUserStateLocked(); AccessibilityUserState userState = getCurrentUserStateLocked(); if (Flags.scanPackagesWithoutLock()) { readConfigurationForUserStateLocked(userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } else { readConfigurationForUserStateLocked(userState); readConfigurationForUserStateLocked(userState); } mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices); mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices); // Even if reading did not yield change, we have to update // Even if reading did not yield change, we have to update // the state since the context in which the current user // the state since the context in which the current user Loading Loading @@ -2105,8 +2170,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } } private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) { /** mTempAccessibilityServiceInfoList.clear(); * Finds packages that provide AccessibilityService interfaces, and parses * their metadata XML to build up {@link AccessibilityServiceInfo} objects. * * <p> * <strong>Note:</strong> XML parsing is a resource-heavy operation that may * stall, so this method should not be called while holding a lock. * </p> */ private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) { List<AccessibilityServiceInfo> result = new ArrayList<>(); int flags = PackageManager.GET_SERVICES int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | PackageManager.GET_META_DATA Loading @@ -2114,12 +2188,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; if (userState.getBindInstantServiceAllowedLocked()) { synchronized (mLock) { if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) { flags |= PackageManager.MATCH_INSTANT; flags |= PackageManager.MATCH_INSTANT; } } } List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId); new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId); for (int i = 0, count = installedServices.size(); i < count; i++) { for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); ResolveInfo resolveInfo = installedServices.get(i); Loading @@ -2132,40 +2208,60 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceInfo accessibilityServiceInfo; AccessibilityServiceInfo accessibilityServiceInfo; try { try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); } catch (XmlPullParserException | IOException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); continue; } if (!accessibilityServiceInfo.isWithinParcelableSize()) { if (!accessibilityServiceInfo.isWithinParcelableSize()) { Slog.e(LOG_TAG, "Skipping service " Slog.e(LOG_TAG, "Skipping service " + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + " because service info size is larger than safe parcelable limits."); + " because service info size is larger than safe parcelable limits."); continue; continue; } } if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { result.add(accessibilityServiceInfo); } return result; } private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState, @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) { for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) { AccessibilityServiceInfo accessibilityServiceInfo = parsedAccessibilityServiceInfos.get(i); if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) { // Restore the crashed attribute. // Restore the crashed attribute. accessibilityServiceInfo.crashed = true; accessibilityServiceInfo.crashed = true; } } mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo); } catch (XmlPullParserException | IOException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); } } } if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) { if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) { userState.mInstalledServices.clear(); userState.mInstalledServices.clear(); userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList); userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos); mTempAccessibilityServiceInfoList.clear(); return true; return true; } } mTempAccessibilityServiceInfoList.clear(); return false; return false; } } private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) { /** final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager * Returns the {@link AccessibilityShortcutInfo}s of the installed .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser( * accessibility shortcut targets for the given user. mContext, mCurrentUserId); * if (!shortcutInfos.equals(userState.mInstalledShortcuts)) { * <p> * <strong>Note:</strong> XML parsing is a resource-heavy operation that may * stall, so this method should not be called while holding a lock. * </p> */ private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) { // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager. return AccessibilityManager.getInstance(mContext) .getInstalledAccessibilityShortcutListAsUser(mContext, userId); } private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState, List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) { userState.mInstalledShortcuts.clear(); userState.mInstalledShortcuts.clear(); userState.mInstalledShortcuts.addAll(shortcutInfos); userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos); return true; return true; } } return false; return false; Loading Loading @@ -2890,9 +2986,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.setFilterKeyEventsEnabledLocked(false); userState.setFilterKeyEventsEnabledLocked(false); } } // ErrorProne doesn't understand that this method is only called while locked, // returning an error for accessing mCurrentUserId. @SuppressWarnings("GuardedBy") private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState); return readConfigurationForUserStateLocked(userState, somethingChanged |= readInstalledAccessibilityShortcutLocked(userState); parseAccessibilityServiceInfos(mCurrentUserId), parseAccessibilityShortcutInfos(mCurrentUserId)); } private boolean readConfigurationForUserStateLocked( AccessibilityUserState userState, List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { boolean somethingChanged = readInstalledAccessibilityServiceLocked( userState, parsedAccessibilityServiceInfos); somethingChanged |= readInstalledAccessibilityShortcutLocked( userState, parsedAccessibilityShortcutInfos); somethingChanged |= readEnabledAccessibilityServicesLocked(userState); somethingChanged |= readEnabledAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); Loading services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +85 −0 Original line number Original line Diff line number Diff line Loading @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock; Loading @@ -53,12 +54,18 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerGlobal; import android.net.Uri; import android.os.Bundle; import android.os.Bundle; import android.os.IBinder; import android.os.IBinder; import android.os.LocaleList; import android.os.LocaleList; import android.os.UserHandle; import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.provider.Settings; import android.testing.TestableContext; import android.testing.TestableContext; import android.view.Display; import android.view.Display; Loading Loading @@ -93,8 +100,13 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** /** * APCT tests for {@link AccessibilityManagerService}. * APCT tests for {@link AccessibilityManagerService}. Loading @@ -104,6 +116,10 @@ public class AccessibilityManagerServiceTest { public final A11yTestableContext mTestableContext = new A11yTestableContext( public final A11yTestableContext mTestableContext = new A11yTestableContext( ApplicationProvider.getApplicationContext()); ApplicationProvider.getApplicationContext()); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int ACTION_ID = 20; private static final int ACTION_ID = 20; private static final String LABEL = "label"; private static final String LABEL = "label"; private static final String INTENT_ACTION = "TESTACTION"; private static final String INTENT_ACTION = "TESTACTION"; Loading Loading @@ -204,6 +220,8 @@ public class AccessibilityManagerServiceTest { mA11yms.getCurrentUserIdLocked()); mA11yms.getCurrentUserIdLocked()); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); mMockResolveInfo.serviceInfo.packageName = "packageName"; mMockResolveInfo.serviceInfo.name = "className"; mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); Loading Loading @@ -581,6 +599,73 @@ public class AccessibilityManagerServiceTest { ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); } } @Test @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) // Test old behavior to validate lock detection for the old (locked access) case. public void testPackageMonitorScanPackages_scansWhileHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(List.of(mMockResolveInfo)); when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); packageIntent.setData(Uri.parse("test://package")); packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); packageIntent.putExtra(Intent.EXTRA_REPLACING, true); mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); assertThat(lockState.get()).containsExactly(true); } @Test @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) public void testPackageMonitorScanPackages_scansWithoutHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(List.of(mMockResolveInfo)); when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); packageIntent.setData(Uri.parse("test://package")); packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); packageIntent.putExtra(Intent.EXTRA_REPLACING, true); mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); assertThat(lockState.get()).containsExactly(false); } @Test @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) public void testSwitchUserScanPackages_scansWithoutHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(List.of(mMockResolveInfo)); when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1); assertThat(lockState.get()).containsExactly(false); } // Single package intents can trigger multiple PackageMonitor callbacks. // Collect the state of the lock in a set, since tests only care if calls // were all locked or all unlocked. private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() { final AtomicReference<Set<Boolean>> lockState = new AtomicReference<>(new HashSet<Boolean>()); doAnswer((Answer<XmlResourceParser>) invocation -> { lockState.updateAndGet(set -> { set.add(mA11yms.unsafeIsLockHeld()); return set; }); return null; }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any()); return lockState; } private void mockManageAccessibilityGranted(TestableContext context) { private void mockManageAccessibilityGranted(TestableContext context) { context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY, context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_GRANTED); PackageManager.PERMISSION_GRANTED); Loading Loading
services/accessibility/accessibility.aconfig +7 −0 Original line number Original line Diff line number Diff line Loading @@ -48,3 +48,10 @@ flag { description: "Stops using the deprecated PackageListObserver." description: "Stops using the deprecated PackageListObserver." bug: "304561459" bug: "304561459" } } flag { name: "scan_packages_without_lock" namespace: "accessibility" description: "Scans packages for accessibility service/activity info without holding the A11yMS lock" bug: "295969873" }
services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +151 −41 Original line number Original line Diff line number Diff line Loading @@ -290,14 +290,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList = new ArrayList<>(); private final IntArray mTempIntArray = new IntArray(0); private final IntArray mTempIntArray = new IntArray(0); private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients = new RemoteCallbackList<>(); new RemoteCallbackList<>(); private PackageMonitor mPackageMonitor; @VisibleForTesting @VisibleForTesting final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); Loading Loading @@ -531,6 +530,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub disableAccessibilityMenuToMigrateIfNeeded(); disableAccessibilityMenuToMigrateIfNeeded(); } } /** * Returns if the current thread is holding {@link #mLock}. Used for testing * deadlock bug fixes. * * <p><strong>Warning:</strong> this should not be used for production logic * because by the time you receive an answer it may no longer be valid. * </p> */ @VisibleForTesting boolean unsafeIsLockHeld() { return Thread.holdsLock(mLock); } @Override @Override public int getCurrentUserIdLocked() { public int getCurrentUserIdLocked() { return mCurrentUserId; return mCurrentUserId; Loading Loading @@ -690,6 +702,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } } private void onSomePackagesChangedLocked( @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { final AccessibilityUserState userState = getCurrentUserStateLocked(); // Reload the installed services since some services may have different attributes // or resolve info (does not support equals), etc. Remove them then to force reload. userState.mInstalledServices.clear(); if (readConfigurationForUserStateLocked(userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) { onUserStateChangedLocked(userState); } } private void onPackageRemovedLocked(String packageName) { private void onPackageRemovedLocked(String packageName) { final AccessibilityUserState userState = getCurrentUserState(); final AccessibilityUserState userState = getCurrentUserState(); final Predicate<ComponentName> filter = final Predicate<ComponentName> filter = Loading Loading @@ -721,8 +746,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } } @VisibleForTesting PackageMonitor getPackageMonitor() { return mPackageMonitor; } private void registerBroadcastReceivers() { private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { mPackageMonitor = new PackageMonitor() { @Override @Override public void onSomePackagesChanged() { public void onSomePackagesChanged() { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { Loading @@ -730,15 +760,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FLAGS_PACKAGE_BROADCAST_RECEIVER); FLAGS_PACKAGE_BROADCAST_RECEIVER); } } final int userId = getChangingUserId(); List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; if (Flags.scanPackagesWithoutLock()) { parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); } synchronized (mLock) { synchronized (mLock) { // Only the profile parent can install accessibility services. // Only the profile parent can install accessibility services. // Therefore we ignore packages from linked profiles. // Therefore we ignore packages from linked profiles. if (getChangingUserId() != mCurrentUserId) { if (userId != mCurrentUserId) { return; return; } } if (Flags.scanPackagesWithoutLock()) { onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } else { onSomePackagesChangedLocked(); onSomePackagesChangedLocked(); } } } } } @Override @Override public void onPackageUpdateFinished(String packageName, int uid) { public void onPackageUpdateFinished(String packageName, int uid) { Loading @@ -751,8 +793,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FLAGS_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER, "packageName=" + packageName + ";uid=" + uid); "packageName=" + packageName + ";uid=" + uid); } } synchronized (mLock) { final int userId = getChangingUserId(); final int userId = getChangingUserId(); List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; if (Flags.scanPackagesWithoutLock()) { parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); } synchronized (mLock) { if (userId != mCurrentUserId) { if (userId != mCurrentUserId) { return; return; } } Loading @@ -765,8 +813,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Reloads the installed services info to make sure the rebound service could // Reloads the installed services info to make sure the rebound service could // get a new one. // get a new one. userState.mInstalledServices.clear(); userState.mInstalledServices.clear(); final boolean configurationChanged = final boolean configurationChanged; readConfigurationForUserStateLocked(userState); if (Flags.scanPackagesWithoutLock()) { configurationChanged = readConfigurationForUserStateLocked(userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } else { configurationChanged = readConfigurationForUserStateLocked(userState); } if (reboundAService || configurationChanged) { if (reboundAService || configurationChanged) { onUserStateChangedLocked(userState); onUserStateChangedLocked(userState); } } Loading Loading @@ -839,7 +892,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }; }; // package changes // package changes monitor.register(mContext, null, UserHandle.ALL, true); mPackageMonitor.register(mContext, null, UserHandle.ALL, true); if (!Flags.deprecatePackageListObserver()) { if (!Flags.deprecatePackageListObserver()) { final PackageManagerInternal pm = LocalServices.getService( final PackageManagerInternal pm = LocalServices.getService( Loading Loading @@ -1831,8 +1884,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager.onTouchInteractionEnd(); mA11yWindowManager.onTouchInteractionEnd(); } } private void switchUser(int userId) { @VisibleForTesting void switchUser(int userId) { mMagnificationController.updateUserIdIfNeeded(userId); mMagnificationController.updateUserIdIfNeeded(userId); List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; if (Flags.scanPackagesWithoutLock()) { parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); } synchronized (mLock) { synchronized (mLock) { if (mCurrentUserId == userId && mInitialized) { if (mCurrentUserId == userId && mInitialized) { return; return; Loading @@ -1857,7 +1917,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mCurrentUserId = userId; mCurrentUserId = userId; AccessibilityUserState userState = getCurrentUserStateLocked(); AccessibilityUserState userState = getCurrentUserStateLocked(); if (Flags.scanPackagesWithoutLock()) { readConfigurationForUserStateLocked(userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); } else { readConfigurationForUserStateLocked(userState); readConfigurationForUserStateLocked(userState); } mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices); mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices); // Even if reading did not yield change, we have to update // Even if reading did not yield change, we have to update // the state since the context in which the current user // the state since the context in which the current user Loading Loading @@ -2105,8 +2170,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } } private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) { /** mTempAccessibilityServiceInfoList.clear(); * Finds packages that provide AccessibilityService interfaces, and parses * their metadata XML to build up {@link AccessibilityServiceInfo} objects. * * <p> * <strong>Note:</strong> XML parsing is a resource-heavy operation that may * stall, so this method should not be called while holding a lock. * </p> */ private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) { List<AccessibilityServiceInfo> result = new ArrayList<>(); int flags = PackageManager.GET_SERVICES int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | PackageManager.GET_META_DATA Loading @@ -2114,12 +2188,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; if (userState.getBindInstantServiceAllowedLocked()) { synchronized (mLock) { if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) { flags |= PackageManager.MATCH_INSTANT; flags |= PackageManager.MATCH_INSTANT; } } } List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId); new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId); for (int i = 0, count = installedServices.size(); i < count; i++) { for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); ResolveInfo resolveInfo = installedServices.get(i); Loading @@ -2132,40 +2208,60 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceInfo accessibilityServiceInfo; AccessibilityServiceInfo accessibilityServiceInfo; try { try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); } catch (XmlPullParserException | IOException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); continue; } if (!accessibilityServiceInfo.isWithinParcelableSize()) { if (!accessibilityServiceInfo.isWithinParcelableSize()) { Slog.e(LOG_TAG, "Skipping service " Slog.e(LOG_TAG, "Skipping service " + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + accessibilityServiceInfo.getResolveInfo().getComponentInfo() + " because service info size is larger than safe parcelable limits."); + " because service info size is larger than safe parcelable limits."); continue; continue; } } if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { result.add(accessibilityServiceInfo); } return result; } private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState, @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) { for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) { AccessibilityServiceInfo accessibilityServiceInfo = parsedAccessibilityServiceInfos.get(i); if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) { // Restore the crashed attribute. // Restore the crashed attribute. accessibilityServiceInfo.crashed = true; accessibilityServiceInfo.crashed = true; } } mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo); } catch (XmlPullParserException | IOException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); } } } if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) { if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) { userState.mInstalledServices.clear(); userState.mInstalledServices.clear(); userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList); userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos); mTempAccessibilityServiceInfoList.clear(); return true; return true; } } mTempAccessibilityServiceInfoList.clear(); return false; return false; } } private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) { /** final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager * Returns the {@link AccessibilityShortcutInfo}s of the installed .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser( * accessibility shortcut targets for the given user. mContext, mCurrentUserId); * if (!shortcutInfos.equals(userState.mInstalledShortcuts)) { * <p> * <strong>Note:</strong> XML parsing is a resource-heavy operation that may * stall, so this method should not be called while holding a lock. * </p> */ private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) { // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager. return AccessibilityManager.getInstance(mContext) .getInstalledAccessibilityShortcutListAsUser(mContext, userId); } private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState, List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) { userState.mInstalledShortcuts.clear(); userState.mInstalledShortcuts.clear(); userState.mInstalledShortcuts.addAll(shortcutInfos); userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos); return true; return true; } } return false; return false; Loading Loading @@ -2890,9 +2986,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.setFilterKeyEventsEnabledLocked(false); userState.setFilterKeyEventsEnabledLocked(false); } } // ErrorProne doesn't understand that this method is only called while locked, // returning an error for accessing mCurrentUserId. @SuppressWarnings("GuardedBy") private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState); return readConfigurationForUserStateLocked(userState, somethingChanged |= readInstalledAccessibilityShortcutLocked(userState); parseAccessibilityServiceInfos(mCurrentUserId), parseAccessibilityShortcutInfos(mCurrentUserId)); } private boolean readConfigurationForUserStateLocked( AccessibilityUserState userState, List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { boolean somethingChanged = readInstalledAccessibilityServiceLocked( userState, parsedAccessibilityServiceInfos); somethingChanged |= readInstalledAccessibilityShortcutLocked( userState, parsedAccessibilityShortcutInfos); somethingChanged |= readEnabledAccessibilityServicesLocked(userState); somethingChanged |= readEnabledAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); Loading
services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +85 −0 Original line number Original line Diff line number Diff line Loading @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock; Loading @@ -53,12 +54,18 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; import android.graphics.drawable.Icon; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerGlobal; import android.net.Uri; import android.os.Bundle; import android.os.Bundle; import android.os.IBinder; import android.os.IBinder; import android.os.LocaleList; import android.os.LocaleList; import android.os.UserHandle; import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.provider.Settings; import android.testing.TestableContext; import android.testing.TestableContext; import android.view.Display; import android.view.Display; Loading Loading @@ -93,8 +100,13 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** /** * APCT tests for {@link AccessibilityManagerService}. * APCT tests for {@link AccessibilityManagerService}. Loading @@ -104,6 +116,10 @@ public class AccessibilityManagerServiceTest { public final A11yTestableContext mTestableContext = new A11yTestableContext( public final A11yTestableContext mTestableContext = new A11yTestableContext( ApplicationProvider.getApplicationContext()); ApplicationProvider.getApplicationContext()); @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int ACTION_ID = 20; private static final int ACTION_ID = 20; private static final String LABEL = "label"; private static final String LABEL = "label"; private static final String INTENT_ACTION = "TESTACTION"; private static final String INTENT_ACTION = "TESTACTION"; Loading Loading @@ -204,6 +220,8 @@ public class AccessibilityManagerServiceTest { mA11yms.getCurrentUserIdLocked()); mA11yms.getCurrentUserIdLocked()); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); mMockResolveInfo.serviceInfo.packageName = "packageName"; mMockResolveInfo.serviceInfo.name = "className"; mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); Loading Loading @@ -581,6 +599,73 @@ public class AccessibilityManagerServiceTest { ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()); } } @Test @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) // Test old behavior to validate lock detection for the old (locked access) case. public void testPackageMonitorScanPackages_scansWhileHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(List.of(mMockResolveInfo)); when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); packageIntent.setData(Uri.parse("test://package")); packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); packageIntent.putExtra(Intent.EXTRA_REPLACING, true); mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); assertThat(lockState.get()).containsExactly(true); } @Test @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) public void testPackageMonitorScanPackages_scansWithoutHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(List.of(mMockResolveInfo)); when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); packageIntent.setData(Uri.parse("test://package")); packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); packageIntent.putExtra(Intent.EXTRA_REPLACING, true); mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); assertThat(lockState.get()).containsExactly(false); } @Test @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) public void testSwitchUserScanPackages_scansWithoutHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) .thenReturn(List.of(mMockResolveInfo)); when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1); assertThat(lockState.get()).containsExactly(false); } // Single package intents can trigger multiple PackageMonitor callbacks. // Collect the state of the lock in a set, since tests only care if calls // were all locked or all unlocked. private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() { final AtomicReference<Set<Boolean>> lockState = new AtomicReference<>(new HashSet<Boolean>()); doAnswer((Answer<XmlResourceParser>) invocation -> { lockState.updateAndGet(set -> { set.add(mA11yms.unsafeIsLockHeld()); return set; }); return null; }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any()); return lockState; } private void mockManageAccessibilityGranted(TestableContext context) { private void mockManageAccessibilityGranted(TestableContext context) { context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY, context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_GRANTED); PackageManager.PERMISSION_GRANTED); Loading