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

Commit 592a9570 authored by Daniel Norman's avatar Daniel Norman
Browse files

Use A11yManagerService to check if the A11yService warning is required.

This new location provides a single source of truth for whether the
warning must be shown.

A11yManagerService is the central system service with complete knowledge
of the state of enabled services & service shortcuts. Clients (SysUI,
Settings, etc.) should not be calculating this decision themselves.

Note: renames flag to cleanup_accessibility_warning_dialog.
This flag is not yet rolled out so the rename is effectively
just deleting the old flag & adding a new one.

NO_IFTTT=New IFTTT tags

Bug: 303511250
Test: atest AccessibilityShortcutChooserActivityTest
Test: atest AccessibilityManagerServiceTest
Test: m RunSettingsRoboTests ROBOTEST_FILTER=ToggleAccessibilityServicePreferenceFragmentTest
Change-Id: I76b012443cf510b36cc5b3f7f9ed0a7731312a06
parent 85df79c1
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -185,15 +185,30 @@ public final class AccessibilityManager {

    /**
     * Annotations for the shortcut type.
     * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            // LINT.IfChange(shortcut_type_intdef)
            ACCESSIBILITY_BUTTON,
            ACCESSIBILITY_SHORTCUT_KEY
            // LINT.ThenChange(:shortcut_type_array)
    })
    public @interface ShortcutType {}

    /**
     * Used for iterating through {@link ShortcutType}.
     * <p>Note: Keep in sync with {@link ShortcutType}.</p>
     * @hide
     */
    public static final int[] SHORTCUT_TYPES = {
            // LINT.IfChange(shortcut_type_array)
            ACCESSIBILITY_BUTTON,
            ACCESSIBILITY_SHORTCUT_KEY,
            // LINT.ThenChange(:shortcut_type_intdef)
    };

    /**
     * Annotations for content flag of UI.
     * @hide
@@ -913,6 +928,28 @@ public final class AccessibilityManager {
        }
    }

    /**
     * Returns whether the user must be shown the AccessibilityService warning dialog
     * before the AccessibilityService (or any shortcut for the service) can be enabled.
     * @hide
     */
    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
    public boolean isAccessibilityServiceWarningRequired(@NonNull AccessibilityServiceInfo info) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return true;
            }
        }
        try {
            return service.isAccessibilityServiceWarningRequired(info);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while checking isAccessibilityServiceWarningRequired: ", re);
            return true;
        }
    }

    /**
     * Registers an {@link AccessibilityStateChangeListener} for changes in
     * the global accessibility state of the system. Equivalent to calling
+3 −0
Original line number Diff line number Diff line
@@ -128,6 +128,9 @@ interface IAccessibilityManager {
    boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId);
    boolean sendRestrictedDialogIntent(String packageName, int uid, int userId);

    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
    boolean isAccessibilityServiceWarningRequired(in AccessibilityServiceInfo info);

    parcelable WindowTransformationSpec {
        float[] transformationMatrix;
        MagnificationSpec magnificationSpec;
+6 −6
Original line number Diff line number Diff line
@@ -17,17 +17,17 @@ flag {
}

flag {
    name: "cleanup_accessibility_warning_dialog"
    namespace: "accessibility"
    name: "collection_info_item_counts"
    description: "Fields for total items and the number of important for accessibility items in a collection"
    bug: "302376158"
    description: "Cleans up duplicated or broken logic surrounding the accessibility warning dialog."
    bug: "303511250"
}

flag {
    name: "deduplicate_accessibility_warning_dialog"
    namespace: "accessibility"
    description: "Removes duplicate definition of the accessibility warning dialog."
    bug: "303511250"
    name: "collection_info_item_counts"
    description: "Fields for total items and the number of important for accessibility items in a collection"
    bug: "302376158"
}

flag {
+30 −8
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.Flags;
import android.widget.AdapterView;

@@ -114,6 +115,26 @@ public class AccessibilityShortcutChooserActivity extends Activity {
    private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
        final AccessibilityTarget target = mTargets.get(position);

        if (Flags.cleanupAccessibilityWarningDialog()) {
            if (target instanceof AccessibilityServiceTarget serviceTarget) {
                if (sendRestrictedDialogIntentIfNeeded(target)) {
                    return;
                }
                final AccessibilityManager am = getSystemService(AccessibilityManager.class);
                if (am.isAccessibilityServiceWarningRequired(
                        serviceTarget.getAccessibilityServiceInfo())) {
                    showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
                            position, mTargetAdapter);
                    return;
                }
            }
            if (target instanceof AccessibilityActivityTarget activityTarget) {
                if (!activityTarget.isShortcutEnabled()
                        && sendRestrictedDialogIntentIfNeeded(activityTarget)) {
                    return;
                }
            }
        } else {
            if (!target.isShortcutEnabled()) {
                if (target instanceof AccessibilityServiceTarget
                        || target instanceof AccessibilityActivityTarget) {
@@ -128,6 +149,7 @@ public class AccessibilityShortcutChooserActivity extends Activity {
                    return;
                }
            }
        }

        target.onCheckedChanged(!target.isShortcutEnabled());
        mTargetAdapter.notifyDataSetChanged();
@@ -156,7 +178,7 @@ public class AccessibilityShortcutChooserActivity extends Activity {
            return;
        }

        if (Flags.deduplicateAccessibilityWarningDialog()) {
        if (Flags.cleanupAccessibilityWarningDialog()) {
            mPermissionDialog = AccessibilityServiceWarning
                    .createAccessibilityServiceWarningDialog(context,
                            serviceTarget.getAccessibilityServiceInfo(),
+63 −6
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.internal.accessibility;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.doubleClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
@@ -85,10 +84,13 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Tests for {@link AccessibilityShortcutChooserActivity}.
@@ -151,6 +153,8 @@ public class AccessibilityShortcutChooserActivityTest {
        when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
                anyInt())).thenReturn(new ParceledListSlice<>(
                Collections.singletonList(mAccessibilityServiceInfo)));
        when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
                .thenReturn(true);
        when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                anyString(), anyInt(), anyInt())).thenReturn(true);
        when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
@@ -169,7 +173,7 @@ public class AccessibilityShortcutChooserActivityTest {
    }

    @Test
    @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
    @RequiresFlagsDisabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
    public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() {
        launchActivity();
        openShortcutsList();
@@ -183,7 +187,7 @@ public class AccessibilityShortcutChooserActivityTest {
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
    @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
    public void selectTestService_permissionDialog_allow_rowChecked() {
        launchActivity();
        openShortcutsList();
@@ -197,7 +201,7 @@ public class AccessibilityShortcutChooserActivityTest {
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
    @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
    public void selectTestService_permissionDialog_deny_rowNotChecked() {
        launchActivity();
        openShortcutsList();
@@ -211,7 +215,7 @@ public class AccessibilityShortcutChooserActivityTest {
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
    @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
    public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() {
        launchActivity();
        openShortcutsList();
@@ -226,6 +230,59 @@ public class AccessibilityShortcutChooserActivityTest {
                UI_TIMEOUT_MS)).isFalse();
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
    public void selectTestService_permissionDialog_notShownWhenNotRequired() throws Exception {
        when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
                .thenReturn(false);
        launchActivity();
        openShortcutsList();

        // Clicking the test service should not show a permission dialog window,
        assertThat(mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(
                Until.newWindow(), UI_TIMEOUT_MS)).isFalse();
        // and should become checked.
        assertThat(mDevice.findObject(By.checked(true))).isNotNull();
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG)
    public void selectTestService_notPermittedByAdmin_blockedEvenIfNoWarningRequired()
            throws Exception {
        when(mAccessibilityManagerService.isAccessibilityServiceWarningRequired(any()))
                .thenReturn(false);
        when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
                eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false);
        // This test class mocks AccessibilityManagerService, so the restricted dialog window
        // will not actually appear and therefore cannot be used for a wait Until.newWindow().
        // To still allow smart waiting in this test we can instead set up the mocked method
        // to update an atomic boolean and wait for that to be set.
        final Object waitObject = new Object();
        final AtomicBoolean calledSendRestrictedDialogIntent = new AtomicBoolean(false);
        Mockito.doAnswer((Answer<Void>) invocation -> {
            synchronized (waitObject) {
                calledSendRestrictedDialogIntent.set(true);
                waitObject.notify();
            }
            return null;
        }).when(mAccessibilityManagerService).sendRestrictedDialogIntent(
                eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt());
        launchActivity();
        openShortcutsList();

        mDevice.findObject(By.text(TEST_LABEL)).click();
        final long timeout = System.currentTimeMillis() + UI_TIMEOUT_MS;
        synchronized (waitObject) {
            while (!calledSendRestrictedDialogIntent.get() &&
                    (System.currentTimeMillis() < timeout)) {
                waitObject.wait(timeout - System.currentTimeMillis());
            }
        }

        assertThat(calledSendRestrictedDialogIntent.get()).isTrue();
        assertThat(mDevice.findObject(By.checked(true))).isNull();
    }

    @Test
    public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
            throws Exception {
@@ -329,7 +386,7 @@ public class AccessibilityShortcutChooserActivityTest {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (Flags.deduplicateAccessibilityWarningDialog()) {
            if (Flags.cleanupAccessibilityWarningDialog()) {
                // Setting the Theme is necessary here for the dialog to use the proper style
                // resources as designated in its layout XML.
                setTheme(R.style.Theme_DeviceDefault_DayNight);
Loading