Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16553313 authored by Yara Hassan's avatar Yara Hassan
Browse files

Store source activity name per window in the AccessiblityCache

This will be sent to the AccessibilityCheckerManager to log along with the a11y checker results.

Bug: 326385939
Test: unit tests
Flag: com.android.server.accessibility.enable_a11y_checker_logging
Change-Id: I6ba2271c60b21b4602f3401c1aacd23ed464e3b7
parent c508a91b
Loading
Loading
Loading
Loading
+20 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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
     *
@@ -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
@@ -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;
            }
@@ -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. */
+22 −0
Original line number Diff line number Diff line
@@ -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);
+4 −7
Original line number Diff line number Diff line
@@ -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;
@@ -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();

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

+14 −22
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -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(
@@ -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 "";
    }

+4 −4
Original line number Diff line number Diff line
@@ -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;

@@ -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);

@@ -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);

@@ -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