Merge "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." into udc-dev
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index ca84265..440c6e5 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -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
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 8ca64d2..96ea5b4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -35,6 +35,7 @@
 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 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;
@@ -231,6 +234,22 @@
     }
 
     /**
+     * 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.
      *
      * @param view the shortcut button being clicked.
@@ -249,17 +268,17 @@
                     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;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
index 7be6ca7..2be9245 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
@@ -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
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 529a70c..0e89dcd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -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 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 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 @@
         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 @@
                 () -> 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 @@
                 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 @@
                 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 @@
 
     @Test
     public void testAdjustBrightness() throws Throwable {
-        openMenu();
-
         Context context = sInstrumentation.getTargetContext();
         DisplayManager displayManager = context.getSystemService(
                 DisplayManager.class);
@@ -149,7 +204,6 @@
         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 @@
                     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 @@
                     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));
+    }
 }