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

Commit dc6745df authored by Riley Jones's avatar Riley Jones
Browse files

Further A11yMenuService tests.

Buttons with known behavior (brightness, volume & intent execution) verify the result of being clicked.
Global Action buttons simply verify that clicking results in the service attempting to perform the respective action.

Bug: 265173123
Test: atest AccessibilityMenuServiceTests
Change-Id: Ibea107ae6355c501aa509b0308f8e6f17f0510f9
parent abdfd6f6
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -58,5 +58,8 @@
        <intent>
            <action android:name="android.intent.action.VOICE_COMMAND" />
        </intent>
        <!--intent>
            <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
        </intent-->
    </queries>
</manifest>
 No newline at end of file
+25 −6
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -56,6 +57,8 @@ public class AccessibilityMenuService extends AccessibilityService
    public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
    public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
    public static final String INTENT_HIDE_MENU = ".hide_menu";
    public static final String INTENT_GLOBAL_ACTION = ".global_action";
    public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";

    private static final String TAG = "A11yMenuService";
    private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -230,6 +233,22 @@ public class AccessibilityMenuService extends AccessibilityService
                mOnConfigChangedRunnable, BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE);
    }

    /**
     * Performs global action and broadcasts an intent indicating the action was performed.
     * This is unnecessary for any current functionality, but is used for testing.
     * Refer to {@code performGlobalAction()}.
     *
     * @param globalAction Global action to be performed.
     * @return {@code true} if successful, {@code false} otherwise.
     */
    private boolean performGlobalActionInternal(int globalAction) {
        Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
        intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
        sendBroadcast(intent);
        Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
        return performGlobalAction(globalAction);
    }

    /**
     * Handles click events of shortcuts.
     *
@@ -249,17 +268,17 @@ public class AccessibilityMenuService extends AccessibilityService
                    new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS),
                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        } else if (viewTag == ShortcutId.ID_POWER_VALUE.ordinal()) {
            performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
            performGlobalActionInternal(GLOBAL_ACTION_POWER_DIALOG);
        } else if (viewTag == ShortcutId.ID_RECENT_VALUE.ordinal()) {
            performGlobalAction(GLOBAL_ACTION_RECENTS);
            performGlobalActionInternal(GLOBAL_ACTION_RECENTS);
        } else if (viewTag == ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()) {
            performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
            performGlobalActionInternal(GLOBAL_ACTION_LOCK_SCREEN);
        } else if (viewTag == ShortcutId.ID_QUICKSETTING_VALUE.ordinal()) {
            performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS);
            performGlobalActionInternal(GLOBAL_ACTION_QUICK_SETTINGS);
        } else if (viewTag == ShortcutId.ID_NOTIFICATION_VALUE.ordinal()) {
            performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
            performGlobalActionInternal(GLOBAL_ACTION_NOTIFICATIONS);
        } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
            performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT);
            performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT);
        } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
            adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
            return;
+11 −0
Original line number Diff line number Diff line
@@ -29,4 +29,15 @@
        android:targetPackage="com.android.systemui.accessibility.accessibilitymenu.tests"
        android:label="AccessibilityMenu Test Cases">
    </instrumentation>

    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VOICE_COMMAND" />
        </intent>
    </queries>
</manifest>
 No newline at end of file
+213 −18
Original line number Diff line number Diff line
@@ -16,6 +16,15 @@

package com.android.systemui.accessibility.accessibilitymenu.tests;

import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_POWER_DIALOG;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;

import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
@@ -23,11 +32,15 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;

@@ -37,22 +50,29 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.TestUtils;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@RunWith(AndroidJUnit4.class)
public class AccessibilityMenuServiceTest {
    private static final String TAG = "A11yMenuServiceTest";
    private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;

    private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
    private static final int TIMEOUT_UI_CHANGE_S = 5;
    private static final int TIMEOUT_UI_CHANGE_S = 10;
    private static final int NO_GLOBAL_ACTION = -1;
    private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";

    private static Instrumentation sInstrumentation;
    private static UiAutomation sUiAutomation;
    private static AtomicInteger sLastGlobalAction;

    private static AccessibilityManager sAccessibilityManager;

@@ -62,7 +82,7 @@ public class AccessibilityMenuServiceTest {
        sInstrumentation = InstrumentationRegistry.getInstrumentation();
        sUiAutomation = sInstrumentation.getUiAutomation(
                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
        final Context context = sInstrumentation.getContext();
        final Context context = sInstrumentation.getTargetContext();
        sAccessibilityManager = context.getSystemService(AccessibilityManager.class);

        // Disable all a11yServices if any are active.
@@ -85,6 +105,17 @@ public class AccessibilityMenuServiceTest {
                () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
                                info -> info.getId().contains(serviceName)).count() == 1);

        sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
        context.registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Log.i(TAG, "Received global action intent.");
                sLastGlobalAction.set(
                        intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
            }},
                new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
                null, null, Context.RECEIVER_EXPORTED);
    }

    @AfterClass
@@ -93,23 +124,39 @@ public class AccessibilityMenuServiceTest {
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
    }

    private boolean isMenuVisible() {
        return sUiAutomation.getRootInActiveWindow() != null
                && sUiAutomation.getRootInActiveWindow().getPackageName().toString().equals(
                PACKAGE_NAME);
    @Before
    public void setup() throws Throwable {
        openMenu();
    }

    private void openMenu() throws Throwable {
        if (isMenuVisible()) {
            return;
    @After
    public void tearDown() throws Throwable {
        closeMenu();
        sLastGlobalAction.set(NO_GLOBAL_ACTION);
    }

    private static boolean isMenuVisible() {
        AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
        return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
    }

    private static void openMenu() throws Throwable {
        Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
        sInstrumentation.getContext().sendBroadcast(intent);

        TestUtils.waitUntil("Timed out before menu could appear.",
                TIMEOUT_UI_CHANGE_S, () -> isMenuVisible());
                TIMEOUT_UI_CHANGE_S,
                () -> {
                    if (isMenuVisible()) {
                        return true;
                    } else {
                        sInstrumentation.getContext().sendBroadcast(intent);
                        return false;
                    }
                });
    }

    private void closeMenu() throws Throwable {
    private static void closeMenu() throws Throwable {
        if (!isMenuVisible()) {
            return;
        }
@@ -119,11 +166,21 @@ public class AccessibilityMenuServiceTest {
                TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
    }

    /**
     * Provides list of all present shortcut buttons.
     * @return List of shortcut buttons.
     */
    private List<AccessibilityNodeInfo> getGridButtonList() {
        return sUiAutomation.getRootInActiveWindow()
                        .findAccessibilityNodeInfosByViewId(PACKAGE_NAME + ":id/shortcutIconBtn");
    }

    /**
     * Returns the first button whose uniqueID matches the provided text.
     * @param buttons List of buttons.
     * @param text Text to match button's uniqueID to.
     * @return Button whose uniqueID matches text, {@code null} otherwise.
     */
    private AccessibilityNodeInfo findGridButtonInfo(
            List<AccessibilityNodeInfo> buttons, String text) {
        for (AccessibilityNodeInfo button: buttons) {
@@ -136,8 +193,6 @@ public class AccessibilityMenuServiceTest {

    @Test
    public void testAdjustBrightness() throws Throwable {
        openMenu();

        Context context = sInstrumentation.getTargetContext();
        DisplayManager displayManager = context.getSystemService(
                DisplayManager.class);
@@ -149,7 +204,6 @@ public class AccessibilityMenuServiceTest {
        AccessibilityNodeInfo brightnessDownButton = findGridButtonInfo(buttons,
                String.valueOf(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()));

        int clickId = AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.getId();
        BrightnessInfo brightnessInfo = displayManager.getDisplay(
                context.getDisplayId()).getBrightnessInfo();

@@ -159,7 +213,7 @@ public class AccessibilityMenuServiceTest {
                    TIMEOUT_UI_CHANGE_S,
                    () -> displayManager.getBrightness(context.getDisplayId())
                            == brightnessInfo.brightnessMinimum);
            brightnessUpButton.performAction(clickId);
            brightnessUpButton.performAction(CLICK_ID);
            TestUtils.waitUntil("Did not detect an increase in brightness.",
                    TIMEOUT_UI_CHANGE_S,
                    () -> displayManager.getBrightness(context.getDisplayId())
@@ -170,14 +224,155 @@ public class AccessibilityMenuServiceTest {
                    TIMEOUT_UI_CHANGE_S,
                    () -> displayManager.getBrightness(context.getDisplayId())
                            == brightnessInfo.brightnessMaximum);
            brightnessDownButton.performAction(clickId);
            brightnessDownButton.performAction(CLICK_ID);
            TestUtils.waitUntil("Did not detect a decrease in brightness.",
                    TIMEOUT_UI_CHANGE_S,
                    () -> displayManager.getBrightness(context.getDisplayId())
                            < brightnessInfo.brightnessMaximum);
        } finally {
            displayManager.setBrightness(context.getDisplayId(), resetBrightness);
            closeMenu();
        }
    }

    @Test
    public void testAdjustVolume() throws Throwable {
        Context context = sInstrumentation.getTargetContext();
        AudioManager audioManager = context.getSystemService(AudioManager.class);
        int resetVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        List<AccessibilityNodeInfo> buttons = getGridButtonList();
        AccessibilityNodeInfo volumeUpButton = findGridButtonInfo(buttons,
                String.valueOf(ShortcutId.ID_VOLUME_UP_VALUE.ordinal()));
        AccessibilityNodeInfo volumeDownButton = findGridButtonInfo(buttons,
                String.valueOf(ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()));

        try {
            int min = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, min,
                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
            TestUtils.waitUntil("Could not change audio stream to minimum volume.",
                    TIMEOUT_UI_CHANGE_S,
                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) == min);
            volumeUpButton.performAction(CLICK_ID);
            TestUtils.waitUntil("Did not detect an increase in volume.",
                    TIMEOUT_UI_CHANGE_S,
                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) > min);

            int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max,
                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
            TestUtils.waitUntil("Could not change audio stream to maximum volume.",
                    TIMEOUT_UI_CHANGE_S,
                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) == max);
            volumeDownButton.performAction(CLICK_ID);
            TestUtils.waitUntil("Did not detect a decrease in volume.",
                    TIMEOUT_UI_CHANGE_S,
                    () -> audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) < max);
        } finally {
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
                    resetVolume, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
        }
    }

    @Test
    public void testAssistantButton_opensVoiceAssistant() throws Throwable {
        AccessibilityNodeInfo assistantButton = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_ASSISTANT_VALUE.ordinal()));
        Intent expectedIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
        String expectedPackage = expectedIntent.resolveActivity(
                sInstrumentation.getContext().getPackageManager()).getPackageName();

        sUiAutomation.executeAndWaitForEvent(
                () -> assistantButton.performAction(CLICK_ID),
                (event) -> expectedPackage.contains(event.getPackageName()),
                TIMEOUT_UI_CHANGE_S * 1000
        );
    }

    @Test
    public void testAccessibilitySettingsButton_opensAccessibilitySettings() throws Throwable {
        AccessibilityNodeInfo settingsButton = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_A11YSETTING_VALUE.ordinal()));
        Intent expectedIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
        String expectedPackage = expectedIntent.resolveActivity(
                sInstrumentation.getContext().getPackageManager()).getPackageName();

        sUiAutomation.executeAndWaitForEvent(
                () -> settingsButton.performAction(CLICK_ID),
                (event) -> expectedPackage.contains(event.getPackageName()),
                TIMEOUT_UI_CHANGE_S * 1000
        );
    }

    @Test
    public void testPowerButton_performsGlobalAction() throws Throwable {
        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_POWER_VALUE.ordinal()));

        button.performAction(CLICK_ID);
        TestUtils.waitUntil("Did not detect Power action being performed.",
                TIMEOUT_UI_CHANGE_S,
                () -> sLastGlobalAction.compareAndSet(
                        GLOBAL_ACTION_POWER_DIALOG, NO_GLOBAL_ACTION));
    }

    @Test
    public void testRecentButton_performsGlobalAction() throws Throwable {
        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_RECENT_VALUE.ordinal()));

        button.performAction(CLICK_ID);
        TestUtils.waitUntil("Did not detect Recents action being performed.",
                TIMEOUT_UI_CHANGE_S,
                () -> sLastGlobalAction.compareAndSet(
                        GLOBAL_ACTION_RECENTS, NO_GLOBAL_ACTION));
    }

    @Test
    public void testLockButton_performsGlobalAction() throws Throwable {
        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_LOCKSCREEN_VALUE.ordinal()));

        button.performAction(CLICK_ID);
        TestUtils.waitUntil("Did not detect Lock action being performed.",
                TIMEOUT_UI_CHANGE_S,
                () -> sLastGlobalAction.compareAndSet(
                        GLOBAL_ACTION_LOCK_SCREEN, NO_GLOBAL_ACTION));
    }

    @Test
    public void testQuickSettingsButton_performsGlobalAction() throws Throwable {
        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_QUICKSETTING_VALUE.ordinal()));

        button.performAction(CLICK_ID);
        TestUtils.waitUntil("Did not detect Quick Settings action being performed.",
                TIMEOUT_UI_CHANGE_S,
                () -> sLastGlobalAction.compareAndSet(
                        GLOBAL_ACTION_QUICK_SETTINGS, NO_GLOBAL_ACTION));
    }

    @Test
    public void testNotificationsButton_performsGlobalAction() throws Throwable {
        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_NOTIFICATION_VALUE.ordinal()));

        button.performAction(CLICK_ID);
        TestUtils.waitUntil("Did not detect Notifications action being performed.",
                TIMEOUT_UI_CHANGE_S,
                () -> sLastGlobalAction.compareAndSet(
                        GLOBAL_ACTION_NOTIFICATIONS, NO_GLOBAL_ACTION));
    }

    @Test
    public void testScreenshotButton_performsGlobalAction() throws Throwable {
        AccessibilityNodeInfo button = findGridButtonInfo(getGridButtonList(),
                String.valueOf(ShortcutId.ID_SCREENSHOT_VALUE.ordinal()));

        button.performAction(CLICK_ID);
        TestUtils.waitUntil("Did not detect Screenshot action being performed.",
                TIMEOUT_UI_CHANGE_S,
                () -> sLastGlobalAction.compareAndSet(
                        GLOBAL_ACTION_TAKE_SCREENSHOT, NO_GLOBAL_ACTION));
    }
}