Loading core/java/android/view/accessibility/flags/accessibility_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,13 @@ flag { bug: "277305460" } flag { name: "skip_accessibility_warning_dialog_for_trusted_services" namespace: "accessibility" description: "Skips showing the accessibility warning dialog for trusted services." bug: "303511250" } flag { namespace: "accessibility" name: "update_always_on_a11y_service" Loading core/res/res/values/config.xml +10 −0 Original line number Diff line number Diff line Loading @@ -4497,6 +4497,16 @@ <!-- URI for default Accessibility notification sound when to enable accessibility shortcut. --> <string name="config_defaultAccessibilityNotificationSound" translatable="false"></string> <!-- Array of component names, each flattened to a string, for accessibility services that can be enabled by the user without showing a warning prompt. These services must be preinstalled. --> <string-array translatable="false" name="config_trustedAccessibilityServices"> <!-- <item>com.example.package.first/com.example.class.FirstService</item> <item>com.example.package.second/com.example.class.SecondService</item> --> </string-array> <!-- Warning: This API can be dangerous when not implemented properly. In particular, escrow token must NOT be retrievable from device storage. In other words, either escrow token is not stored on device or its ciphertext is stored on device while Loading core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -3630,6 +3630,7 @@ <java-symbol type="string" name="config_defaultAccessibilityService" /> <java-symbol type="string" name="config_defaultAccessibilityNotificationSound" /> <java-symbol type="string" name="accessibility_shortcut_spoken_feedback" /> <java-symbol type="array" name="config_trustedAccessibilityServices" /> <java-symbol type="string" name="accessibility_select_shortcut_menu_title" /> <java-symbol type="string" name="accessibility_edit_shortcut_menu_button_title" /> Loading services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +27 −2 Original line number Diff line number Diff line Loading @@ -4413,25 +4413,50 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) { mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); final ComponentName componentName = info.getComponentName(); // Warning is not required if the service is already enabled. synchronized (mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); if (userState.getEnabledServicesLocked().contains(info.getComponentName())) { if (userState.getEnabledServicesLocked().contains(componentName)) { return false; } } // Warning is not required if the service is already assigned to a shortcut. for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) { if (getAccessibilityShortcutTargets(shortcutType).contains( info.getComponentName().flattenToString())) { componentName.flattenToString())) { return false; } } // Warning is not required if the service is preinstalled and in the // trustedAccessibilityServices allowlist. if (android.view.accessibility.Flags.skipAccessibilityWarningDialogForTrustedServices() && isAccessibilityServicePreinstalledAndTrusted(info)) { return false; } // Warning is required by default. return true; } private boolean isAccessibilityServicePreinstalledAndTrusted(AccessibilityServiceInfo info) { final ComponentName componentName = info.getComponentName(); final boolean isPreinstalled = info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp(); if (isPreinstalled) { final String[] trustedAccessibilityServices = mContext.getResources().getStringArray( R.array.config_trustedAccessibilityServices); if (Arrays.stream(trustedAccessibilityServices) .map(ComponentName::unflattenFromString) .anyMatch(componentName::equals)) { return true; } } return false; } @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; Loading services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +57 −12 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG; import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES; import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; Loading Loading @@ -82,6 +83,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import com.android.compatibility.common.util.TestUtils; import com.android.internal.R; import com.android.internal.compat.IPlatformCompat; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; Loading Loading @@ -857,8 +859,7 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_requiredByDefault() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.setComponentName(COMPONENT_NAME); final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME); assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue(); } Loading @@ -867,10 +868,9 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); info_a.setComponentName(COMPONENT_NAME); final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo(); info_b.setComponentName(new ComponentName("package_b", "class_b")); final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME); final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( new ComponentName("package_b", "class_b")); final AccessibilityUserState userState = mA11yms.getCurrentUserState(); userState.mEnabledServices.clear(); userState.mEnabledServices.add(info_b.getComponentName()); Loading @@ -883,12 +883,12 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); info_a.setComponentName(new ComponentName("package_a", "class_a")); final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo(); info_b.setComponentName(new ComponentName("package_b", "class_b")); final AccessibilityServiceInfo info_c = new AccessibilityServiceInfo(); info_c.setComponentName(new ComponentName("package_c", "class_c")); final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo( new ComponentName("package_a", "class_a")); final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( new ComponentName("package_b", "class_b")); final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo( new ComponentName("package_c", "class_c")); final AccessibilityUserState userState = mA11yms.getCurrentUserState(); userState.mAccessibilityButtonTargets.clear(); userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString()); Loading @@ -900,6 +900,51 @@ public class AccessibilityManagerServiceTest { assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse(); } @Test @RequiresFlagsEnabled({ FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG, FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES}) public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo( new ComponentName("package_a", "class_a"), true); final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( new ComponentName("package_b", "class_b"), false); final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo( new ComponentName("package_c", "class_c"), true); mTestableContext.getOrCreateTestableResources().addOverride( R.array.config_trustedAccessibilityServices, new String[]{ info_b.getComponentName().flattenToString(), info_c.getComponentName().flattenToString()}); // info_a is not in the allowlist => require the warning assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue(); // info_b is not preinstalled => require the warning assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isTrue(); // info_c is both in the allowlist and preinstalled => do not require the warning assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse(); } private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName) { return mockAccessibilityServiceInfo(componentName, false); } private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName, boolean isSystemApp) { AccessibilityServiceInfo accessibilityServiceInfo = Mockito.spy(new AccessibilityServiceInfo()); accessibilityServiceInfo.setComponentName(componentName); ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class); when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo); mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class); mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class); when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp); return accessibilityServiceInfo; } // 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. Loading Loading
core/java/android/view/accessibility/flags/accessibility_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,13 @@ flag { bug: "277305460" } flag { name: "skip_accessibility_warning_dialog_for_trusted_services" namespace: "accessibility" description: "Skips showing the accessibility warning dialog for trusted services." bug: "303511250" } flag { namespace: "accessibility" name: "update_always_on_a11y_service" Loading
core/res/res/values/config.xml +10 −0 Original line number Diff line number Diff line Loading @@ -4497,6 +4497,16 @@ <!-- URI for default Accessibility notification sound when to enable accessibility shortcut. --> <string name="config_defaultAccessibilityNotificationSound" translatable="false"></string> <!-- Array of component names, each flattened to a string, for accessibility services that can be enabled by the user without showing a warning prompt. These services must be preinstalled. --> <string-array translatable="false" name="config_trustedAccessibilityServices"> <!-- <item>com.example.package.first/com.example.class.FirstService</item> <item>com.example.package.second/com.example.class.SecondService</item> --> </string-array> <!-- Warning: This API can be dangerous when not implemented properly. In particular, escrow token must NOT be retrievable from device storage. In other words, either escrow token is not stored on device or its ciphertext is stored on device while Loading
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -3630,6 +3630,7 @@ <java-symbol type="string" name="config_defaultAccessibilityService" /> <java-symbol type="string" name="config_defaultAccessibilityNotificationSound" /> <java-symbol type="string" name="accessibility_shortcut_spoken_feedback" /> <java-symbol type="array" name="config_trustedAccessibilityServices" /> <java-symbol type="string" name="accessibility_select_shortcut_menu_title" /> <java-symbol type="string" name="accessibility_edit_shortcut_menu_button_title" /> Loading
services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +27 −2 Original line number Diff line number Diff line Loading @@ -4413,25 +4413,50 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) { mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); final ComponentName componentName = info.getComponentName(); // Warning is not required if the service is already enabled. synchronized (mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); if (userState.getEnabledServicesLocked().contains(info.getComponentName())) { if (userState.getEnabledServicesLocked().contains(componentName)) { return false; } } // Warning is not required if the service is already assigned to a shortcut. for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) { if (getAccessibilityShortcutTargets(shortcutType).contains( info.getComponentName().flattenToString())) { componentName.flattenToString())) { return false; } } // Warning is not required if the service is preinstalled and in the // trustedAccessibilityServices allowlist. if (android.view.accessibility.Flags.skipAccessibilityWarningDialogForTrustedServices() && isAccessibilityServicePreinstalledAndTrusted(info)) { return false; } // Warning is required by default. return true; } private boolean isAccessibilityServicePreinstalledAndTrusted(AccessibilityServiceInfo info) { final ComponentName componentName = info.getComponentName(); final boolean isPreinstalled = info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp(); if (isPreinstalled) { final String[] trustedAccessibilityServices = mContext.getResources().getStringArray( R.array.config_trustedAccessibilityServices); if (Arrays.stream(trustedAccessibilityServices) .map(ComponentName::unflattenFromString) .anyMatch(componentName::equals)) { return true; } } return false; } @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; Loading
services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +57 −12 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG; import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES; import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; Loading Loading @@ -82,6 +83,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import com.android.compatibility.common.util.TestUtils; import com.android.internal.R; import com.android.internal.compat.IPlatformCompat; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; Loading Loading @@ -857,8 +859,7 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_requiredByDefault() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); info.setComponentName(COMPONENT_NAME); final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME); assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue(); } Loading @@ -867,10 +868,9 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); info_a.setComponentName(COMPONENT_NAME); final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo(); info_b.setComponentName(new ComponentName("package_b", "class_b")); final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME); final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( new ComponentName("package_b", "class_b")); final AccessibilityUserState userState = mA11yms.getCurrentUserState(); userState.mEnabledServices.clear(); userState.mEnabledServices.add(info_b.getComponentName()); Loading @@ -883,12 +883,12 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); info_a.setComponentName(new ComponentName("package_a", "class_a")); final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo(); info_b.setComponentName(new ComponentName("package_b", "class_b")); final AccessibilityServiceInfo info_c = new AccessibilityServiceInfo(); info_c.setComponentName(new ComponentName("package_c", "class_c")); final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo( new ComponentName("package_a", "class_a")); final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( new ComponentName("package_b", "class_b")); final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo( new ComponentName("package_c", "class_c")); final AccessibilityUserState userState = mA11yms.getCurrentUserState(); userState.mAccessibilityButtonTargets.clear(); userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString()); Loading @@ -900,6 +900,51 @@ public class AccessibilityManagerServiceTest { assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse(); } @Test @RequiresFlagsEnabled({ FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG, FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES}) public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() { mockManageAccessibilityGranted(mTestableContext); final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo( new ComponentName("package_a", "class_a"), true); final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( new ComponentName("package_b", "class_b"), false); final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo( new ComponentName("package_c", "class_c"), true); mTestableContext.getOrCreateTestableResources().addOverride( R.array.config_trustedAccessibilityServices, new String[]{ info_b.getComponentName().flattenToString(), info_c.getComponentName().flattenToString()}); // info_a is not in the allowlist => require the warning assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue(); // info_b is not preinstalled => require the warning assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isTrue(); // info_c is both in the allowlist and preinstalled => do not require the warning assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse(); } private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName) { return mockAccessibilityServiceInfo(componentName, false); } private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName, boolean isSystemApp) { AccessibilityServiceInfo accessibilityServiceInfo = Mockito.spy(new AccessibilityServiceInfo()); accessibilityServiceInfo.setComponentName(componentName); ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class); when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo); mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class); mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class); when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp); return accessibilityServiceInfo; } // 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. Loading