Loading core/java/android/view/accessibility/AccessibilityCache.java +20 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.view.accessibility; import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY; import android.annotation.Nullable; import android.os.Build; import android.os.SystemClock; import android.util.ArraySet; Loading Loading @@ -48,6 +49,8 @@ public class AccessibilityCache { private boolean mEnabled = true; private final SparseArray<String> mWindowIdToEventSourceClassName = new SparseArray<>(); /** * {@link AccessibilityEvent} types that are critical for the cache to stay up to date * Loading Loading @@ -273,8 +276,11 @@ public class AccessibilityCache { clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId()); } break; case AccessibilityEvent.TYPE_WINDOWS_CHANGED: case AccessibilityEvent.TYPE_WINDOWS_CHANGED: { mValidWindowCacheTimeStamp = event.getEventTime(); if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) { mWindowIdToEventSourceClassName.remove(event.getWindowId()); } if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) { // Don't need to clear all cache. Unless the changes are related to Loading @@ -282,8 +288,15 @@ public class AccessibilityCache { clearWindowCacheLocked(); break; } clear(); } break; case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { mValidWindowCacheTimeStamp = event.getEventTime(); if (event.getContentChangeTypes() == 0 && event.getClassName() != null) { mWindowIdToEventSourceClassName.put(event.getWindowId(), event.getClassName().toString()); } clear(); } break; } Loading Loading @@ -907,6 +920,12 @@ public class AccessibilityCache { } } /** Returns the source class associated with the window with the given id. */ @Nullable public String getEventSourceClassName(int windowId) { return mWindowIdToEventSourceClassName.get(windowId); } // Layer of indirection included to break dependency chain for testing public static class AccessibilityNodeRefresher { /** Refresh the given AccessibilityNodeInfo object. */ Loading core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +22 −0 Original line number Diff line number Diff line Loading @@ -1053,6 +1053,28 @@ public class AccessibilityCacheTest { assertFalse(mAccessibilityCache.isNodeInCache(childInfo)); } @Test public void getEventSourceClassName_windowStateChangedThenRemoved() { final String sourceActivityClassName = "com.example.SomeActivity"; final AccessibilityEvent windowStateChangedEvent = new AccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); final View mockView = getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1); windowStateChangedEvent.setSource(mockView); windowStateChangedEvent.setClassName(sourceActivityClassName); mAccessibilityCache.onAccessibilityEvent(windowStateChangedEvent); assertEquals(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1), sourceActivityClassName); final AccessibilityEvent windowRemovedEvent = new AccessibilityEvent( AccessibilityEvent.TYPE_WINDOWS_CHANGED); windowRemovedEvent.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED); windowRemovedEvent.setSource(mockView); mAccessibilityCache.onAccessibilityEvent(windowRemovedEvent); assertNull(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1)); } private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) { AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain(); windowInfo.setId(windowId); Loading services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java +4 −7 Original line number Diff line number Diff line Loading @@ -26,7 +26,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -60,6 +59,7 @@ public final class AccessibilityCheckerManager { private final Set<AccessibilityHierarchyCheck> mHierarchyChecks; private final ATFHierarchyBuilder mATFHierarchyBuilder; private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>(); @VisibleForTesting final A11yCheckerTimer mTimer = new A11yCheckerTimer(); Loading @@ -86,10 +86,8 @@ public final class AccessibilityCheckerManager { */ @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public Set<AccessibilityCheckResultReported> maybeRunA11yChecker( List<AccessibilityNodeInfo> nodes, @Nullable AccessibilityEvent accessibilityEvent, ComponentName sourceComponentName, @UserIdInt int userId) { List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName, ComponentName a11yServiceComponentName, @UserIdInt int userId) { if (!shouldRunA11yChecker()) { return Set.of(); } Loading @@ -108,14 +106,13 @@ public final class AccessibilityCheckerManager { List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo); Set<AccessibilityCheckResultReported> filteredResults = AccessibilityCheckerUtils.processResults(nodeInfo, checkResults, accessibilityEvent, mPackageManager, sourceComponentName); sourceEventClassName, mPackageManager, a11yServiceComponentName); allResults.addAll(filteredResults); } mCachedResults.addAll(allResults); } catch (RuntimeException e) { Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e); } return allResults; } Loading services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java +14 −22 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -96,7 +95,7 @@ public class AccessibilityCheckerUtils { static Set<AccessibilityCheckResultReported> processResults( AccessibilityNodeInfo nodeInfo, List<AccessibilityHierarchyCheckResult> checkResults, @Nullable AccessibilityEvent accessibilityEvent, @Nullable String activityClassName, PackageManager packageManager, ComponentName a11yServiceComponentName) { String appPackageName = nodeInfo.getPackageName().toString(); Loading @@ -110,7 +109,8 @@ public class AccessibilityCheckerUtils { .setPackageName(appPackageName) .setAppVersionCode(getAppVersionCode(packageManager, appPackageName)) .setUiElementPath(nodePath) .setActivityName(getActivityName(packageManager, accessibilityEvent)) .setActivityName( getActivityName(packageManager, appPackageName, activityClassName)) .setWindowTitle(getWindowTitle(nodeInfo)) .setSourceComponentName(a11yServiceComponentName.flattenToString()) .setSourceVersionCode( Loading Loading @@ -140,32 +140,24 @@ public class AccessibilityCheckerUtils { } /** * Returns the simple class name of the Activity providing the cache update, if available, * Returns the simple class name of the Activity associated with the window, if available, * or an empty String if not. */ @VisibleForTesting static String getActivityName( PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) { if (accessibilityEvent == null) { PackageManager packageManager, String packageName, @Nullable String activityClassName) { if (activityClassName == null) { return ""; } CharSequence activityName = accessibilityEvent.getClassName(); if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && accessibilityEvent.getPackageName() != null && activityName != null) { try { // Check class is for a valid Activity. packageManager .getActivityInfo( new ComponentName(accessibilityEvent.getPackageName().toString(), activityName.toString()), 0); int qualifierEnd = activityName.toString().lastIndexOf('.'); return activityName.toString().substring(qualifierEnd + 1); packageManager.getActivityInfo(new ComponentName(packageName, activityClassName), 0); int qualifierEnd = activityClassName.lastIndexOf('.'); return activityClassName.substring(qualifierEnd + 1); } catch (PackageManager.NameNotFoundException e) { // No need to spam the logs. This is very frequent when the class doesn't match // an activity. } } return ""; } Loading services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -18,13 +18,13 @@ package com.android.server.accessibility.a11ychecker; import static com.android.server.accessibility.Flags.FLAG_ENABLE_A11Y_CHECKER_LOGGING; import static com.android.server.accessibility.a11ychecker.AccessibilityCheckerConstants.MIN_DURATION_BETWEEN_CHECKS; import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER; import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom; import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps; import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent; import static com.google.common.truth.Truth.assertThat; Loading Loading @@ -114,7 +114,7 @@ public class AccessibilityCheckerManagerTest { Set<A11yCheckerProto.AccessibilityCheckResultReported> results = mAccessibilityCheckerManager.maybeRunA11yChecker( List.of(mockNodeInfo1, mockNodeInfo2), getTestAccessibilityEvent(), List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME, new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0); Loading @@ -139,7 +139,7 @@ public class AccessibilityCheckerManagerTest { Set<A11yCheckerProto.AccessibilityCheckResultReported> results = mAccessibilityCheckerManager.maybeRunA11yChecker( List.of(mockNodeInfo), getTestAccessibilityEvent(), List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME, new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0); Loading @@ -160,7 +160,7 @@ public class AccessibilityCheckerManagerTest { Set<A11yCheckerProto.AccessibilityCheckResultReported> results = mAccessibilityCheckerManager.maybeRunA11yChecker( List.of(mockNodeInfo, mockNodeInfoDuplicate), getTestAccessibilityEvent(), List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME, new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0); Loading Loading
core/java/android/view/accessibility/AccessibilityCache.java +20 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.view.accessibility; import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY; import android.annotation.Nullable; import android.os.Build; import android.os.SystemClock; import android.util.ArraySet; Loading Loading @@ -48,6 +49,8 @@ public class AccessibilityCache { private boolean mEnabled = true; private final SparseArray<String> mWindowIdToEventSourceClassName = new SparseArray<>(); /** * {@link AccessibilityEvent} types that are critical for the cache to stay up to date * Loading Loading @@ -273,8 +276,11 @@ public class AccessibilityCache { clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId()); } break; case AccessibilityEvent.TYPE_WINDOWS_CHANGED: case AccessibilityEvent.TYPE_WINDOWS_CHANGED: { mValidWindowCacheTimeStamp = event.getEventTime(); if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) { mWindowIdToEventSourceClassName.remove(event.getWindowId()); } if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) { // Don't need to clear all cache. Unless the changes are related to Loading @@ -282,8 +288,15 @@ public class AccessibilityCache { clearWindowCacheLocked(); break; } clear(); } break; case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { mValidWindowCacheTimeStamp = event.getEventTime(); if (event.getContentChangeTypes() == 0 && event.getClassName() != null) { mWindowIdToEventSourceClassName.put(event.getWindowId(), event.getClassName().toString()); } clear(); } break; } Loading Loading @@ -907,6 +920,12 @@ public class AccessibilityCache { } } /** Returns the source class associated with the window with the given id. */ @Nullable public String getEventSourceClassName(int windowId) { return mWindowIdToEventSourceClassName.get(windowId); } // Layer of indirection included to break dependency chain for testing public static class AccessibilityNodeRefresher { /** Refresh the given AccessibilityNodeInfo object. */ Loading
core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +22 −0 Original line number Diff line number Diff line Loading @@ -1053,6 +1053,28 @@ public class AccessibilityCacheTest { assertFalse(mAccessibilityCache.isNodeInCache(childInfo)); } @Test public void getEventSourceClassName_windowStateChangedThenRemoved() { final String sourceActivityClassName = "com.example.SomeActivity"; final AccessibilityEvent windowStateChangedEvent = new AccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); final View mockView = getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1); windowStateChangedEvent.setSource(mockView); windowStateChangedEvent.setClassName(sourceActivityClassName); mAccessibilityCache.onAccessibilityEvent(windowStateChangedEvent); assertEquals(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1), sourceActivityClassName); final AccessibilityEvent windowRemovedEvent = new AccessibilityEvent( AccessibilityEvent.TYPE_WINDOWS_CHANGED); windowRemovedEvent.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED); windowRemovedEvent.setSource(mockView); mAccessibilityCache.onAccessibilityEvent(windowRemovedEvent); assertNull(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1)); } private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) { AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain(); windowInfo.setId(windowId); Loading
services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java +4 −7 Original line number Diff line number Diff line Loading @@ -26,7 +26,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -60,6 +59,7 @@ public final class AccessibilityCheckerManager { private final Set<AccessibilityHierarchyCheck> mHierarchyChecks; private final ATFHierarchyBuilder mATFHierarchyBuilder; private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>(); @VisibleForTesting final A11yCheckerTimer mTimer = new A11yCheckerTimer(); Loading @@ -86,10 +86,8 @@ public final class AccessibilityCheckerManager { */ @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public Set<AccessibilityCheckResultReported> maybeRunA11yChecker( List<AccessibilityNodeInfo> nodes, @Nullable AccessibilityEvent accessibilityEvent, ComponentName sourceComponentName, @UserIdInt int userId) { List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName, ComponentName a11yServiceComponentName, @UserIdInt int userId) { if (!shouldRunA11yChecker()) { return Set.of(); } Loading @@ -108,14 +106,13 @@ public final class AccessibilityCheckerManager { List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo); Set<AccessibilityCheckResultReported> filteredResults = AccessibilityCheckerUtils.processResults(nodeInfo, checkResults, accessibilityEvent, mPackageManager, sourceComponentName); sourceEventClassName, mPackageManager, a11yServiceComponentName); allResults.addAll(filteredResults); } mCachedResults.addAll(allResults); } catch (RuntimeException e) { Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e); } return allResults; } Loading
services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java +14 −22 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -96,7 +95,7 @@ public class AccessibilityCheckerUtils { static Set<AccessibilityCheckResultReported> processResults( AccessibilityNodeInfo nodeInfo, List<AccessibilityHierarchyCheckResult> checkResults, @Nullable AccessibilityEvent accessibilityEvent, @Nullable String activityClassName, PackageManager packageManager, ComponentName a11yServiceComponentName) { String appPackageName = nodeInfo.getPackageName().toString(); Loading @@ -110,7 +109,8 @@ public class AccessibilityCheckerUtils { .setPackageName(appPackageName) .setAppVersionCode(getAppVersionCode(packageManager, appPackageName)) .setUiElementPath(nodePath) .setActivityName(getActivityName(packageManager, accessibilityEvent)) .setActivityName( getActivityName(packageManager, appPackageName, activityClassName)) .setWindowTitle(getWindowTitle(nodeInfo)) .setSourceComponentName(a11yServiceComponentName.flattenToString()) .setSourceVersionCode( Loading Loading @@ -140,32 +140,24 @@ public class AccessibilityCheckerUtils { } /** * Returns the simple class name of the Activity providing the cache update, if available, * Returns the simple class name of the Activity associated with the window, if available, * or an empty String if not. */ @VisibleForTesting static String getActivityName( PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) { if (accessibilityEvent == null) { PackageManager packageManager, String packageName, @Nullable String activityClassName) { if (activityClassName == null) { return ""; } CharSequence activityName = accessibilityEvent.getClassName(); if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && accessibilityEvent.getPackageName() != null && activityName != null) { try { // Check class is for a valid Activity. packageManager .getActivityInfo( new ComponentName(accessibilityEvent.getPackageName().toString(), activityName.toString()), 0); int qualifierEnd = activityName.toString().lastIndexOf('.'); return activityName.toString().substring(qualifierEnd + 1); packageManager.getActivityInfo(new ComponentName(packageName, activityClassName), 0); int qualifierEnd = activityClassName.lastIndexOf('.'); return activityClassName.substring(qualifierEnd + 1); } catch (PackageManager.NameNotFoundException e) { // No need to spam the logs. This is very frequent when the class doesn't match // an activity. } } return ""; } Loading
services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -18,13 +18,13 @@ package com.android.server.accessibility.a11ychecker; import static com.android.server.accessibility.Flags.FLAG_ENABLE_A11Y_CHECKER_LOGGING; import static com.android.server.accessibility.a11ychecker.AccessibilityCheckerConstants.MIN_DURATION_BETWEEN_CHECKS; import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME; import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER; import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom; import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps; import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent; import static com.google.common.truth.Truth.assertThat; Loading Loading @@ -114,7 +114,7 @@ public class AccessibilityCheckerManagerTest { Set<A11yCheckerProto.AccessibilityCheckResultReported> results = mAccessibilityCheckerManager.maybeRunA11yChecker( List.of(mockNodeInfo1, mockNodeInfo2), getTestAccessibilityEvent(), List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME, new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0); Loading @@ -139,7 +139,7 @@ public class AccessibilityCheckerManagerTest { Set<A11yCheckerProto.AccessibilityCheckResultReported> results = mAccessibilityCheckerManager.maybeRunA11yChecker( List.of(mockNodeInfo), getTestAccessibilityEvent(), List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME, new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0); Loading @@ -160,7 +160,7 @@ public class AccessibilityCheckerManagerTest { Set<A11yCheckerProto.AccessibilityCheckResultReported> results = mAccessibilityCheckerManager.maybeRunA11yChecker( List.of(mockNodeInfo, mockNodeInfoDuplicate), getTestAccessibilityEvent(), List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME, new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0); Loading