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
This commit is contained in:
Riley Jones
2023-03-10 01:41:53 +00:00
parent abdfd6f63b
commit dc6745dff3
4 changed files with 252 additions and 24 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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));
}
}