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

Commit ed5a6f51 authored by Daniel Norman's avatar Daniel Norman Committed by Android (Google) Code Review
Browse files

Merge "Adds a config for trusted accessibility services." into main

parents 83352c04 f59f0d28
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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"
+10 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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" />
+27 −2
Original line number Diff line number Diff line
@@ -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;
+57 −12
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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();
    }
@@ -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());
@@ -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());
@@ -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.