1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.tapl; 17 18 import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID; 19 20 import android.view.KeyEvent; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 25 import com.android.launcher3.testing.shared.TestProtocol; 26 27 import java.util.regex.Pattern; 28 29 /** 30 * Operations on the Keyboard Quick Switch View 31 */ 32 public final class KeyboardQuickSwitch { 33 34 private static final Pattern EVENT_ALT_TAB_DOWN = Pattern.compile( 35 "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB" 36 + ".*?metaState=META_ALT_ON"); 37 private static final Pattern EVENT_ALT_TAB_UP = Pattern.compile( 38 "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB" 39 + ".*?metaState=META_ALT_ON"); 40 private static final Pattern EVENT_ALT_SHIFT_TAB_UP = Pattern.compile( 41 "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB" 42 + ".*?metaState=META_ALT_ON|META_SHIFT_ON"); 43 private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile( 44 "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP" 45 + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON"); 46 private static final Pattern EVENT_KQS_ALT_LEFT_UP = Pattern.compile( 47 "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP" 48 + ".*?keyCode=KEYCODE_ALT_LEFT"); 49 private static final Pattern EVENT_HOME_ALT_LEFT_UP = Pattern.compile( 50 "Key event: KeyEvent.*?action=ACTION_UP" 51 + ".*?keyCode=KEYCODE_ALT_LEFT"); 52 53 private final LauncherInstrumentation mLauncher; 54 private final LauncherInstrumentation.ContainerType mStartingContainerType; 55 private final boolean mIsHomeState; 56 KeyboardQuickSwitch( LauncherInstrumentation launcher, LauncherInstrumentation.ContainerType startingContainerType, boolean isHomeState)57 KeyboardQuickSwitch( 58 LauncherInstrumentation launcher, 59 LauncherInstrumentation.ContainerType startingContainerType, 60 boolean isHomeState) { 61 mLauncher = launcher; 62 mStartingContainerType = startingContainerType; 63 mIsHomeState = isHomeState; 64 } 65 66 /** 67 * Focuses the next task in the Keyboard quick switch view. 68 * <p> 69 * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel. 70 * <ul> 71 * <li>If no task has been focused yet, and there is only one task, then that task will be 72 * focused</li> 73 * <li>If no task has been focused yet, and there are two or more tasks, then the second 74 * task will be focused</li> 75 * <li>If the currently-focused task is at the end of the list, the first task will be 76 * focused</li> 77 * </ul> 78 */ moveFocusForward()79 public KeyboardQuickSwitch moveFocusForward() { 80 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 81 "want to move keyboard quick switch focus forward"); 82 LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 83 mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); 84 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP); 85 mLauncher.assertTrue("Failed to press alt+tab", 86 mLauncher.getDevice().pressKeyCode( 87 KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON)); 88 89 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( 90 "pressed alt+tab")) { 91 mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); 92 93 return this; 94 } 95 } 96 } 97 98 /** 99 * Focuses the next task in the Keyboard quick switch view. 100 * <p> 101 * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel. 102 * <ul> 103 * <li>If no task has been focused yet, and there is only one task, then that task will be 104 * focused</li> 105 * <li>If no task has been focused yet, and there are two or more tasks, then the second 106 * task will be focused</li> 107 * <li>If the currently-focused task is at the start of the list, the last task will be 108 * focused</li> 109 * </ul> 110 */ moveFocusBackward()111 public KeyboardQuickSwitch moveFocusBackward() { 112 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 113 "want to move keyboard quick switch focus backward"); 114 LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 115 mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); 116 117 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP); 118 mLauncher.assertTrue("Failed to press alt+shift+tab", 119 mLauncher.getDevice().pressKeyCode( 120 KeyEvent.KEYCODE_TAB, 121 KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)); 122 123 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( 124 "pressed alt+shift+tab")) { 125 mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); 126 127 return this; 128 } 129 } 130 } 131 132 /** 133 * Dismisses the Keyboard Quick Switch view without launching the focused task. 134 * <p> 135 * The device will return to the same state it started in before displaying the Keyboard Quick 136 * Switch view. 137 */ dismiss()138 public void dismiss() { 139 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 140 "want to dismiss keyboard quick switch view"); 141 LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 142 mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); 143 144 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP); 145 mLauncher.assertTrue("Failed to press alt+tab", 146 mLauncher.getDevice().pressKeyCode( 147 KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON)); 148 149 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( 150 "pressed alt+esc")) { 151 mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 152 153 // Verify the final state is the same as the initial state 154 mLauncher.verifyContainerType(mStartingContainerType); 155 156 // Wait until the device has fully settled before unpressing the key code 157 if (mIsHomeState) { 158 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP); 159 } 160 mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0); 161 } 162 } 163 } 164 165 /** 166 * Dismisses the Keyboard Quick Switch view by going home. After the Keyboard Quick Switch view 167 * gets hidden, it unpresses ALT key, which is generally used to keep the view visible. 168 */ dismissByGoingHome()169 public Workspace dismissByGoingHome() { 170 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 171 "verifying keyboard quick switch view is shown")) { 172 mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); 173 } 174 175 mLauncher.goHome(); 176 177 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 178 "waiting for keyboard quick switch dismissal"); 179 LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 180 mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 181 } 182 183 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( 184 "get workspace after releasing ALT key")) { 185 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP); 186 mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0); 187 return mLauncher.getWorkspace(); 188 } 189 } 190 191 /** 192 * Launches the currently-focused app task. 193 * <p> 194 * This method should only be used if the focused task is for a recent running app, otherwise 195 * use {@link #launchFocusedOverviewTask()}. 196 * 197 * @param expectedPackageName the package name of the expected launched app 198 */ launchFocusedAppTask(@onNull String expectedPackageName)199 public LaunchedAppState launchFocusedAppTask(@NonNull String expectedPackageName) { 200 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 201 return (LaunchedAppState) launchFocusedTask(expectedPackageName); 202 } 203 } 204 205 /** 206 * Launches the currently-focused overview task. 207 * <p> 208 * This method only should be used if the focused task is for overview, otherwise use 209 * {@link #launchFocusedAppTask(String)}. 210 */ launchFocusedOverviewTask()211 public Overview launchFocusedOverviewTask() { 212 try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { 213 return (Overview) launchFocusedTask(null); 214 } 215 } 216 launchFocusedTask( @ullable String expectedPackageName)217 private LauncherInstrumentation.VisibleContainer launchFocusedTask( 218 @Nullable String expectedPackageName) { 219 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( 220 "want to launch focused task: " 221 + (expectedPackageName == null ? "Overview" : expectedPackageName))) { 222 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KQS_ALT_LEFT_UP); 223 224 if (expectedPackageName == null || !mIsHomeState) { 225 mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0); 226 } else { 227 mLauncher.executeAndWaitForLauncherStop( 228 () -> mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0), 229 "unpressing left alt"); 230 } 231 232 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( 233 "un-pressed left alt")) { 234 mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); 235 236 if (expectedPackageName != null) { 237 mLauncher.assertAppLaunched(expectedPackageName); 238 return mLauncher.getLaunchedAppState(); 239 } else { 240 return mLauncher.getOverview(); 241 } 242 } 243 } 244 } 245 } 246