diff options
author | 2024-07-01 19:57:10 +0100 | |
---|---|---|
committer | 2024-07-11 00:02:29 +0100 | |
commit | 71f38092fd49b5b7574d6e92dabc61046fae15a6 (patch) | |
tree | 13d4abc39ebeae709dc7cb532108b0b093dd6451 | |
parent | a9b54179d4a945c4f881f66ae469e859b996a941 (diff) |
Add utils to process a11y check results
Also moved a11ychecker from core/... to services/accessibility
NO_IFTTT=New IFTTT
Bug: 350530488
Bug: 341926585
Test: unit tests
Flag: com.android.server.accessibility.enable_a11y_checker_logging
Change-Id: Ibc0cb4eb2d207032ef21a46196d4b2cc04cf7e95
14 files changed, 600 insertions, 33 deletions
diff --git a/core/java/android/view/accessibility/a11ychecker/Android.bp b/core/java/android/view/accessibility/a11ychecker/Android.bp deleted file mode 100644 index e5a577c16421..000000000000 --- a/core/java/android/view/accessibility/a11ychecker/Android.bp +++ /dev/null @@ -1,7 +0,0 @@ -java_library_static { - name: "A11yChecker", - srcs: [ - "*.java", - ], - visibility: ["//visibility:public"], -} diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index d2771691c50c..41696dfa782e 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -63,7 +63,6 @@ android_test { "-c fa", ], static_libs: [ - "A11yChecker", "collector-device-lib-platform", "frameworks-base-testutils", "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS b/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS deleted file mode 100644 index 872a1804555b..000000000000 --- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -# Android Accessibility Framework owners -include /core/java/android/view/accessibility/a11ychecker/OWNERS -include /services/accessibility/OWNERS - -yaraabdullatif@google.com diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp index 7a99b605c4fb..311addb90298 100644 --- a/services/accessibility/Android.bp +++ b/services/accessibility/Android.bp @@ -29,10 +29,12 @@ java_library_static { "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc", ], libs: [ + "aatf", "services.core", "androidx.annotation_annotation", ], static_libs: [ + "a11ychecker-protos-java-proto-lite", "com_android_server_accessibility_flags_lib", "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib", @@ -68,3 +70,14 @@ java_aconfig_library { name: "com_android_server_accessibility_flags_lib", aconfig_declarations: "com_android_server_accessibility_flags", } + +java_library_static { + name: "a11ychecker-protos-java-proto-lite", + proto: { + type: "lite", + canonical_path_from_root: false, + }, + srcs: [ + "java/**/a11ychecker/proto/*.proto", + ], +} diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java new file mode 100644 index 000000000000..55af9a0cfd24 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java @@ -0,0 +1,218 @@ +/* + * Copyright 2024 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.accessibility.a11ychecker; + + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +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; +import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckClass; +import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultReported; +import com.android.server.accessibility.a11ychecker.A11yCheckerProto.AccessibilityCheckResultType; + +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult; +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck; +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult; +import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateSpeakableTextCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.EditableContentDescCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.ImageContrastCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.LinkPurposeUnclearCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.RedundantDescriptionCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.TextSizeCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.TraversalOrderCheck; + +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Util class to process a11y checker results for logging. + * + * @hide + */ +public class AccessibilityCheckerUtils { + + private static final String LOG_TAG = "AccessibilityCheckerUtils"; + @VisibleForTesting + // LINT.IfChange + static final Map<Class<? extends AccessibilityHierarchyCheck>, AccessibilityCheckClass> + CHECK_CLASS_TO_ENUM_MAP = + Map.ofEntries( + classMapEntry(ClassNameCheck.class, AccessibilityCheckClass.CLASS_NAME_CHECK), + classMapEntry(ClickableSpanCheck.class, + AccessibilityCheckClass.CLICKABLE_SPAN_CHECK), + classMapEntry(DuplicateClickableBoundsCheck.class, + AccessibilityCheckClass.DUPLICATE_CLICKABLE_BOUNDS_CHECK), + classMapEntry(DuplicateSpeakableTextCheck.class, + AccessibilityCheckClass.DUPLICATE_SPEAKABLE_TEXT_CHECK), + classMapEntry(EditableContentDescCheck.class, + AccessibilityCheckClass.EDITABLE_CONTENT_DESC_CHECK), + classMapEntry(ImageContrastCheck.class, + AccessibilityCheckClass.IMAGE_CONTRAST_CHECK), + classMapEntry(LinkPurposeUnclearCheck.class, + AccessibilityCheckClass.LINK_PURPOSE_UNCLEAR_CHECK), + classMapEntry(RedundantDescriptionCheck.class, + AccessibilityCheckClass.REDUNDANT_DESCRIPTION_CHECK), + classMapEntry(SpeakableTextPresentCheck.class, + AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK), + classMapEntry(TextContrastCheck.class, + AccessibilityCheckClass.TEXT_CONTRAST_CHECK), + classMapEntry(TextSizeCheck.class, AccessibilityCheckClass.TEXT_SIZE_CHECK), + classMapEntry(TouchTargetSizeCheck.class, + AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK), + classMapEntry(TraversalOrderCheck.class, + AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK)); + // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto) + + static Set<AccessibilityCheckResultReported> processResults( + Context context, + AccessibilityNodeInfo nodeInfo, + List<AccessibilityHierarchyCheckResult> checkResults, + @Nullable AccessibilityEvent accessibilityEvent, + ComponentName a11yServiceComponentName) { + return processResults(nodeInfo, checkResults, accessibilityEvent, + context.getPackageManager(), a11yServiceComponentName); + } + + @VisibleForTesting + static Set<AccessibilityCheckResultReported> processResults( + AccessibilityNodeInfo nodeInfo, + List<AccessibilityHierarchyCheckResult> checkResults, + @Nullable AccessibilityEvent accessibilityEvent, + PackageManager packageManager, + ComponentName a11yServiceComponentName) { + String appPackageName = nodeInfo.getPackageName().toString(); + AccessibilityCheckResultReported.Builder builder; + try { + builder = AccessibilityCheckResultReported.newBuilder() + .setPackageName(appPackageName) + .setAppVersionCode(getAppVersionCode(packageManager, appPackageName)) + .setUiElementPath(AccessibilityNodePathBuilder.createNodePath(nodeInfo)) + .setActivityName(getActivityName(packageManager, accessibilityEvent)) + .setWindowTitle(getWindowTitle(nodeInfo)) + .setSourceComponentName(a11yServiceComponentName.flattenToString()) + .setSourceVersionCode( + getAppVersionCode(packageManager, + a11yServiceComponentName.getPackageName())); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(LOG_TAG, "Unknown package name", e); + return Set.of(); + } + + return checkResults.stream() + .filter(checkResult -> checkResult.getType() + == AccessibilityCheckResult.AccessibilityCheckResultType.ERROR + || checkResult.getType() + == AccessibilityCheckResult.AccessibilityCheckResultType.WARNING) + .map(checkResult -> builder.setResultCheckClass( + getCheckClass(checkResult)).setResultType( + getCheckResultType(checkResult)).setResultId( + checkResult.getResultId()).build()) + .collect(Collectors.toUnmodifiableSet()); + } + + private static long getAppVersionCode(PackageManager packageManager, String packageName) throws + PackageManager.NameNotFoundException { + PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0); + return packageInfo.getLongVersionCode(); + } + + /** + * Returns the simple class name of the Activity providing the cache update, if available, + * or an empty String if not. + */ + @VisibleForTesting + static String getActivityName( + PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) { + if (accessibilityEvent == 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); + } catch (PackageManager.NameNotFoundException e) { + // No need to spam the logs. This is very frequent when the class doesn't match + // an activity. + } + } + return ""; + } + + /** + * Returns the title of the window containing the a11y node. + */ + private static String getWindowTitle(AccessibilityNodeInfo nodeInfo) { + if (nodeInfo.getWindow() == null) { + return ""; + } + CharSequence windowTitle = nodeInfo.getWindow().getTitle(); + return windowTitle == null ? "" : windowTitle.toString(); + } + + /** + * Maps the {@link AccessibilityHierarchyCheck} class that produced the given result, with the + * corresponding {@link AccessibilityCheckClass} enum. This enumeration is to avoid relying on + * String class names in the logging, which can be proguarded. It also reduces the logging size. + */ + private static AccessibilityCheckClass getCheckClass( + AccessibilityHierarchyCheckResult checkResult) { + if (CHECK_CLASS_TO_ENUM_MAP.containsKey(checkResult.getSourceCheckClass())) { + return CHECK_CLASS_TO_ENUM_MAP.get(checkResult.getSourceCheckClass()); + } + return AccessibilityCheckClass.UNKNOWN_CHECK; + } + + private static AccessibilityCheckResultType getCheckResultType( + AccessibilityHierarchyCheckResult checkResult) { + return switch (checkResult.getType()) { + case ERROR -> AccessibilityCheckResultType.ERROR; + case WARNING -> AccessibilityCheckResultType.WARNING; + default -> AccessibilityCheckResultType.UNKNOWN_RESULT_TYPE; + }; + } + + private static Map.Entry<Class<? extends AccessibilityHierarchyCheck>, + AccessibilityCheckClass> classMapEntry( + Class<? extends AccessibilityHierarchyCheck> checkClass, + AccessibilityCheckClass checkClassEnum) { + return new AbstractMap.SimpleImmutableEntry<>(checkClass, checkClassEnum); + } +} diff --git a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java index 2996ddef6f64..bbfb217d925e 100644 --- a/core/java/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilder.java +++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view.accessibility.a11ychecker; +package com.android.server.accessibility.a11ychecker; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/view/accessibility/a11ychecker/OWNERS b/services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS index d1e7986cc209..d1e7986cc209 100644 --- a/core/java/android/view/accessibility/a11ychecker/OWNERS +++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto new file mode 100644 index 000000000000..8beed4afa9cb --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 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. + */ +syntax = "proto2"; +package android.accessibility; + +option java_package = "com.android.server.accessibility.a11ychecker"; +option java_outer_classname = "A11yCheckerProto"; + +// TODO(b/326385939): remove and replace usage with the atom extension proto, when submitted. +/** Logs the result of an AccessibilityCheck. */ +message AccessibilityCheckResultReported { + // Package name of the app containing the checked View. + optional string package_name = 1; + // Version code of the app containing the checked View. + optional int64 app_version_code = 2; + // The path of the View starting from the root element in the window. Each element is + // represented by the View's resource id, when available, or the View's class name. + optional string ui_element_path = 3; + // Class name of the activity containing the checked View. + optional string activity_name = 4; + // Title of the window containing the checked View. + optional string window_title = 5; + // The flattened component name of the app running the AccessibilityService which provided the a11y node. + optional string source_component_name = 6; + // Version code of the app running the AccessibilityService that provided the a11y node. + optional int64 source_version_code = 7; + // Class Name of the AccessibilityCheck that produced the result. + optional AccessibilityCheckClass result_check_class = 8; + // Result type of the AccessibilityCheckResult. + optional AccessibilityCheckResultType result_type = 9; + // Result ID of the AccessibilityCheckResult. + optional int32 result_id = 10; +} + +/** The AccessibilityCheck class. */ +// LINT.IfChange +enum AccessibilityCheckClass { + UNKNOWN_CHECK = 0; + CLASS_NAME_CHECK = 1; + CLICKABLE_SPAN_CHECK = 2; + DUPLICATE_CLICKABLE_BOUNDS_CHECK = 3; + DUPLICATE_SPEAKABLE_TEXT_CHECK = 4; + EDITABLE_CONTENT_DESC_CHECK = 5; + IMAGE_CONTRAST_CHECK = 6; + LINK_PURPOSE_UNCLEAR_CHECK = 7; + REDUNDANT_DESCRIPTION_CHECK = 8; + SPEAKABLE_TEXT_PRESENT_CHECK = 9; + TEXT_CONTRAST_CHECK = 10; + TEXT_SIZE_CHECK = 11; + TOUCH_TARGET_SIZE_CHECK = 12; + TRAVERSAL_ORDER_CHECK = 13; +} +// LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java) + +/** The type of AccessibilityCheckResult */ +enum AccessibilityCheckResultType { + UNKNOWN_RESULT_TYPE = 0; + ERROR = 1; + WARNING = 2; +} diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 753db125ffe3..b9e99dd2e1e4 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -36,6 +36,8 @@ android_test { "-Werror", ], static_libs: [ + "a11ychecker-protos-java-proto-lite", + "aatf", "cts-input-lib", "frameworks-base-testutils", "services.accessibility", diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java new file mode 100644 index 000000000000..90d427596ab2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2024 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.accessibility.a11ychecker; + +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_A11Y_SERVICE_SOURCE_VERSION_CODE; +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME; +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME; +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_VERSION_CODE; +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE; +import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset; +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult; +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck; +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult; +import com.google.android.apps.common.testing.accessibility.framework.checks.ClassNameCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.ClickableSpanCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck; +import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck; +import com.google.common.collect.ImmutableSet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@RunWith(AndroidJUnit4.class) +public class AccessibilityCheckerUtilsTest { + + PackageManager mMockPackageManager; + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + mMockPackageManager = getMockPackageManagerWithInstalledApps(); + } + + @Test + public void processResults_happyPath_setsAllFields() { + AccessibilityNodeInfo mockNodeInfo = + new MockAccessibilityNodeInfoBuilder() + .setViewIdResourceName("TargetNode") + .build(); + AccessibilityHierarchyCheckResult result1 = + new AccessibilityHierarchyCheckResult( + SpeakableTextPresentCheck.class, + AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1, + null); + AccessibilityHierarchyCheckResult result2 = + new AccessibilityHierarchyCheckResult( + TouchTargetSizeCheck.class, + AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null); + AccessibilityHierarchyCheckResult result3 = + new AccessibilityHierarchyCheckResult( + ClassNameCheck.class, + AccessibilityCheckResult.AccessibilityCheckResultType.INFO, null, 5, null); + AccessibilityHierarchyCheckResult result4 = + new AccessibilityHierarchyCheckResult( + ClickableSpanCheck.class, + AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5, + null); + + Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms = + AccessibilityCheckerUtils.processResults( + mockNodeInfo, + List.of(result1, result2, result3, result4), + null, + mMockPackageManager, + new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, + TEST_A11Y_SERVICE_CLASS_NAME)); + + assertThat(atoms).containsExactly( + createAtom(A11yCheckerProto.AccessibilityCheckClass.SPEAKABLE_TEXT_PRESENT_CHECK, + A11yCheckerProto.AccessibilityCheckResultType.WARNING, 1), + createAtom(A11yCheckerProto.AccessibilityCheckClass.TOUCH_TARGET_SIZE_CHECK, + A11yCheckerProto.AccessibilityCheckResultType.ERROR, 2) + ); + } + + @Test + public void processResults_packageNameNotFound_returnsEmptySet() + throws PackageManager.NameNotFoundException { + when(mMockPackageManager.getPackageInfo("com.uninstalled.app", 0)) + .thenThrow(PackageManager.NameNotFoundException.class); + AccessibilityNodeInfo mockNodeInfo = + new MockAccessibilityNodeInfoBuilder() + .setPackageName("com.uninstalled.app") + .setViewIdResourceName("TargetNode") + .build(); + AccessibilityHierarchyCheckResult result1 = + new AccessibilityHierarchyCheckResult( + TouchTargetSizeCheck.class, + AccessibilityCheckResult.AccessibilityCheckResultType.WARNING, null, 1, + null); + AccessibilityHierarchyCheckResult result2 = + new AccessibilityHierarchyCheckResult( + TouchTargetSizeCheck.class, + AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null); + + Set<A11yCheckerProto.AccessibilityCheckResultReported> atoms = + AccessibilityCheckerUtils.processResults( + mockNodeInfo, + List.of(result1, result2), + null, + mMockPackageManager, + new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, + TEST_A11Y_SERVICE_CLASS_NAME)); + + assertThat(atoms).isEmpty(); + } + + @Test + public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() { + AccessibilityEvent accessibilityEvent = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + accessibilityEvent.setPackageName(TEST_APP_PACKAGE_NAME); + accessibilityEvent.setClassName(TEST_ACTIVITY_NAME); + + assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager, + accessibilityEvent)).isEqualTo("MainActivity"); + } + + // Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the + // latest prod preset. + @Test + public void checkClassToEnumMap_hasAllLatestPreset() { + ImmutableSet<AccessibilityHierarchyCheck> checkPreset = + AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset( + AccessibilityCheckPreset.LATEST); + Set<Class<? extends AccessibilityHierarchyCheck>> latestCheckClasses = + checkPreset.stream().map(AccessibilityHierarchyCheck::getClass).collect( + Collectors.toUnmodifiableSet()); + + assertThat(AccessibilityCheckerUtils.CHECK_CLASS_TO_ENUM_MAP.keySet()) + .containsExactlyElementsIn(latestCheckClasses); + } + + + private static A11yCheckerProto.AccessibilityCheckResultReported createAtom( + A11yCheckerProto.AccessibilityCheckClass checkClass, + A11yCheckerProto.AccessibilityCheckResultType resultType, + int resultId) { + return A11yCheckerProto.AccessibilityCheckResultReported.newBuilder() + .setPackageName(TEST_APP_PACKAGE_NAME) + .setAppVersionCode(TEST_APP_VERSION_CODE) + .setUiElementPath(TEST_APP_PACKAGE_NAME + ":TargetNode") + .setWindowTitle(TEST_WINDOW_TITLE) + .setActivityName("") + .setSourceComponentName(new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, + TEST_A11Y_SERVICE_CLASS_NAME).flattenToString()) + .setSourceVersionCode(TEST_A11Y_SERVICE_SOURCE_VERSION_CODE) + .setResultCheckClass(checkClass) + .setResultType(resultType) + .setResultId(resultId) + .build(); + } + +} diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java index 438277b272cb..a53f42ece21f 100644 --- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityNodePathBuilderTest.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.view.accessibility.a11ychecker; +package com.android.server.accessibility.a11ychecker; -import static android.view.accessibility.a11ychecker.MockAccessibilityNodeInfoBuilder.PACKAGE_NAME; +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; @@ -36,7 +36,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class AccessibilityNodePathBuilderTest { - public static final String RESOURCE_ID_PREFIX = PACKAGE_NAME + ":id/"; + public static final String RESOURCE_ID_PREFIX = TEST_APP_PACKAGE_NAME + ":id/"; @Test public void createNodePath_pathWithResourceNames() { @@ -55,11 +55,11 @@ public class AccessibilityNodePathBuilderTest { .build(); assertThat(AccessibilityNodePathBuilder.createNodePath(child)) - .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]/child_node[1]"); assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) - .isEqualTo(PACKAGE_NAME + ":root_node/parent_node[1]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node/parent_node[1]"); assertThat(AccessibilityNodePathBuilder.createNodePath(root)) - .isEqualTo(PACKAGE_NAME + ":root_node"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":root_node"); } @Test @@ -81,11 +81,11 @@ public class AccessibilityNodePathBuilderTest { .build(); assertThat(AccessibilityNodePathBuilder.createNodePath(child)) - .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]/TextView[1]"); assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) - .isEqualTo(PACKAGE_NAME + ":FrameLayout/RecyclerView[1]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/RecyclerView[1]"); assertThat(AccessibilityNodePathBuilder.createNodePath(root)) - .isEqualTo(PACKAGE_NAME + ":FrameLayout"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout"); } @Test @@ -105,11 +105,11 @@ public class AccessibilityNodePathBuilderTest { .build(); assertThat(AccessibilityNodePathBuilder.createNodePath(child1)) - .isEqualTo(PACKAGE_NAME + ":FrameLayout/child1[1]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/child1[1]"); assertThat(AccessibilityNodePathBuilder.createNodePath(child2)) - .isEqualTo(PACKAGE_NAME + ":FrameLayout/TextView[2]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout/TextView[2]"); assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) - .isEqualTo(PACKAGE_NAME + ":FrameLayout"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":FrameLayout"); } @Test @@ -133,13 +133,13 @@ public class AccessibilityNodePathBuilderTest { .build(); assertThat(AccessibilityNodePathBuilder.createNodePath(child1)) - .isEqualTo(PACKAGE_NAME + ":parentId/childId[1]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childId[1]"); assertThat(AccessibilityNodePathBuilder.createNodePath(child2)) - .isEqualTo(PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/child/Id/With/Slash[2]"); assertThat(AccessibilityNodePathBuilder.createNodePath(child3)) - .isEqualTo(PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId/childIdWithoutPrefix[3]"); assertThat(AccessibilityNodePathBuilder.createNodePath(parent)) - .isEqualTo(PACKAGE_NAME + ":parentId"); + .isEqualTo(TEST_APP_PACKAGE_NAME + ":parentId"); } } diff --git a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java index e363f0c81720..7cd3535e72ba 100644 --- a/core/tests/coretests/src/android/view/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/MockAccessibilityNodeInfoBuilder.java @@ -14,21 +14,33 @@ * limitations under the License. */ -package android.view.accessibility.a11ychecker; +package com.android.server.accessibility.a11ychecker; + +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME; +import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_WINDOW_TITLE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import java.util.List; final class MockAccessibilityNodeInfoBuilder { - static final String PACKAGE_NAME = "com.example.app"; private final AccessibilityNodeInfo mMockNodeInfo = mock(AccessibilityNodeInfo.class); MockAccessibilityNodeInfoBuilder() { - when(mMockNodeInfo.getPackageName()).thenReturn(PACKAGE_NAME); + setPackageName(TEST_APP_PACKAGE_NAME); + + AccessibilityWindowInfo windowInfo = new AccessibilityWindowInfo(); + windowInfo.setTitle(TEST_WINDOW_TITLE); + when(mMockNodeInfo.getWindow()).thenReturn(windowInfo); + } + + MockAccessibilityNodeInfoBuilder setPackageName(String packageName) { + when(mMockNodeInfo.getPackageName()).thenReturn(packageName); + return this; } MockAccessibilityNodeInfoBuilder setClassName(String className) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS new file mode 100644 index 000000000000..7bdc0297907c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/OWNERS @@ -0,0 +1 @@ +include /services/accessibility/java/com/android/server/accessibility/a11ychecker/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java new file mode 100644 index 000000000000..a04bbee05730 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 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.accessibility.a11ychecker; + +import static org.mockito.Mockito.when; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import org.mockito.Mockito; + +public class TestUtils { + static final String TEST_APP_PACKAGE_NAME = "com.example.app"; + static final int TEST_APP_VERSION_CODE = 12321; + static final String TEST_ACTIVITY_NAME = "com.example.app.MainActivity"; + static final String TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME = "com.assistive.app"; + static final String TEST_A11Y_SERVICE_CLASS_NAME = "MyA11yService"; + static final int TEST_A11Y_SERVICE_SOURCE_VERSION_CODE = 333555; + static final String TEST_WINDOW_TITLE = "Example window"; + + static PackageManager getMockPackageManagerWithInstalledApps() + throws PackageManager.NameNotFoundException { + PackageManager mockPackageManager = Mockito.mock(PackageManager.class); + ActivityInfo testActivityInfo = getTestActivityInfo(); + ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME, + TEST_ACTIVITY_NAME); + + when(mockPackageManager.getActivityInfo(testActivityComponentName, 0)) + .thenReturn(testActivityInfo); + when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0)) + .thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE, + testActivityInfo)); + when(mockPackageManager.getPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, 0)) + .thenReturn(createPackageInfo(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME, + TEST_A11Y_SERVICE_SOURCE_VERSION_CODE, null)); + return mockPackageManager; + } + + static ActivityInfo getTestActivityInfo() { + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.packageName = TEST_APP_PACKAGE_NAME; + activityInfo.name = TEST_ACTIVITY_NAME; + return activityInfo; + } + + static PackageInfo createPackageInfo(String packageName, int versionCode, + @Nullable ActivityInfo activityInfo) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + packageInfo.setLongVersionCode(versionCode); + if (activityInfo != null) { + packageInfo.activities = new ActivityInfo[]{activityInfo}; + } + return packageInfo; + + } +} |