diff options
Diffstat (limited to 'tests/tapl/com/android/launcher3')
26 files changed, 1245 insertions, 258 deletions
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java index fb08ea44eb..0e785659a6 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java @@ -16,6 +16,9 @@ package com.android.launcher3.tapl; +import static android.view.KeyEvent.KEYCODE_ESCAPE; +import static android.view.KeyEvent.KEYCODE_META_RIGHT; + import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_INTERVAL; import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS; @@ -37,17 +40,27 @@ import com.android.launcher3.testing.shared.TestProtocol; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview. */ -public abstract class AllApps extends LauncherInstrumentation.VisibleContainer { +public abstract class AllApps extends LauncherInstrumentation.VisibleContainer + implements KeyboardQuickSwitchSource { // Defer updates flag used to defer all apps updates by a test's request. private static final int DEFER_UPDATES_TEST = 1 << 1; private static final int MAX_SCROLL_ATTEMPTS = 40; + private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background"; + private static final String FAST_SCROLLER_RES_ID = "fast_scroller"; + + private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile( + "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0"); + private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile( + "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0"); + private final int mHeight; private final int mIconHeight; @@ -63,6 +76,16 @@ public abstract class AllApps extends LauncherInstrumentation.VisibleContainer { .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); } + @Override + public LauncherInstrumentation getLauncher() { + return mLauncher; + } + + @Override + public LauncherInstrumentation.ContainerType getStartingContainerType() { + return getContainerType(); + } + private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler, BySelector appIconSelector, int displayBottom) { final UiObject2 icon; @@ -338,6 +361,55 @@ public abstract class AllApps extends LauncherInstrumentation.VisibleContainer { } /** + * Taps outside bottom sheet to dismiss it. Available on tablets only. + * @param tapRight Tap on the right of bottom sheet if true, or left otherwise. + */ + public void dismissByTappingOutsideForTablet(boolean tapRight) { + mLauncher.assertTrue("Device must be a tablet", mLauncher.isTablet()); + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to tap outside AllApps bottom sheet on the " + + (tapRight ? "right" : "left"))) { + + final UiObject2 container = (tapRight) + ? mLauncher.waitForLauncherObject(FAST_SCROLLER_RES_ID) : + mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID); + + mLauncher.touchOutsideContainer(container, tapRight, false); + try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer( + "tapped outside AllApps bottom sheet")) { + verifyVisibleContainerOnDismiss(); + } + } + } + + /** Presses the meta keyboard shortcut to dismiss AllApps. */ + public void dismissByKeyboardShortcut() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT); + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "pressed meta key")) { + verifyVisibleContainerOnDismiss(); + } + } + } + + /** Presses the esc key to dismiss AllApps. */ + public void dismissByEscKey() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN); + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP); + mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE); + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "pressed esc key")) { + verifyVisibleContainerOnDismiss(); + } + } + } + + protected abstract void verifyVisibleContainerOnDismiss(); + + /** * Return the QSB UI object on the AllApps screen. * @return the QSB UI object. */ diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 677f204748..8713b689f3 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -39,7 +39,8 @@ import java.util.regex.Pattern; * Indicates the base state with a UI other than Overview running as foreground. It can also * indicate Launcher as long as Launcher is not in Overview state. */ -public abstract class Background extends LauncherInstrumentation.VisibleContainer { +public abstract class Background extends LauncherInstrumentation.VisibleContainer + implements KeyboardQuickSwitchSource { private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500; private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle"); @@ -47,6 +48,16 @@ public abstract class Background extends LauncherInstrumentation.VisibleContaine super(launcher); } + @Override + public LauncherInstrumentation getLauncher() { + return mLauncher; + } + + @Override + public LauncherInstrumentation.ContainerType getStartingContainerType() { + return getContainerType(); + } + /** * Swipes up or presses the square button to switch to Overview. * Returns the base overview, which can be either in Launcher or the fallback recents. diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index aa5c770ab9..b6b4a47a1a 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -16,15 +16,19 @@ package com.android.launcher3.tapl; +import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID; + import android.graphics.Rect; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.Direction; import androidx.test.uiautomator.UiObject2; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -182,7 +186,14 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { Taskbar taskbar = new Taskbar(mLauncher); taskbar.touchBottomCorner(tapRight); - verifyActiveContainer(); + if (mLauncher.isTransientTaskbar()) { + // Tapping outside Transient Taskbar returns to Workspace, wait for that state. + new Workspace(mLauncher); + } else { + // Should stay in Overview. + verifyActiveContainer(); + verifyActionsViewVisibility(); + } } } @@ -201,7 +212,8 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { OverviewTask task = getCurrentTask(); mLauncher.assertNotNull("current task is null", task); mLauncher.scrollLeftByDistance(verifyActiveContainer(), - task.getVisibleWidth() + mLauncher.getOverviewPageSpacing()); + mLauncher.getRealDisplaySize().x - task.getUiObject().getVisibleBounds().left + + mLauncher.getOverviewPageSpacing()); try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("scrolled task off screen")) { @@ -231,14 +243,13 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { final List<UiObject2> taskViews = getTasks(); mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); - // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one - // in the center, and parts of its right and left siblings. Find the main task view by - // its width. - final UiObject2 widestTask = Collections.max(taskViews, - (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(), - mLauncher.getVisibleBounds(t2).width())); - - return new OverviewTask(mLauncher, widestTask, this); + // The widest, and most top-right task should be the current task + UiObject2 currentTask = Collections.max(taskViews, + Comparator.comparingInt((UiObject2 t) -> t.getParent().getVisibleBounds().width()) + .thenComparingInt((UiObject2 t) -> t.getParent().getVisibleCenter().x) + .thenComparing(Comparator.comparing( + (UiObject2 t) -> t.getParent().getVisibleCenter().y).reversed())); + return new OverviewTask(mLauncher, currentTask, this); } /** Returns an overview task matching TestActivity {@param activityNumber}. */ @@ -320,6 +331,22 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { mLauncher.getOverviewObjectSelector("clear_all")); } + /** + * Returns the taskbar if it's a tablet, or {@code null} otherwise. + */ + @Nullable + public Taskbar getTaskbar() { + if (!mLauncher.isTablet()) { + return null; + } + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to get the taskbar")) { + mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID); + + return new Taskbar(mLauncher); + } + } + protected boolean isActionsViewVisible() { if (!hasTasks() || isClearAllVisible()) { return false; @@ -366,8 +393,10 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { } int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet(); for (UiObject2 task : taskViews) { - if (task.getVisibleBounds().height() == focusedTaskHeight) { - return new OverviewTask(mLauncher, task, this); + OverviewTask overviewTask = new OverviewTask(mLauncher, task, this); + + if (overviewTask.getVisibleHeight() == focusedTaskHeight) { + return overviewTask; } } return null; diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java index 252435badb..85e28e86cb 100644 --- a/tests/tapl/com/android/launcher3/tapl/Home.java +++ b/tests/tapl/com/android/launcher3/tapl/Home.java @@ -62,4 +62,9 @@ public abstract class Home extends Background { protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() { return true; } + + @Override + public boolean isHomeState() { + return true; + } }
\ No newline at end of file diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java index 295190146f..9ca2dc8463 100644 --- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java @@ -23,7 +23,6 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.testing.shared.TestProtocol; public class HomeAllApps extends AllApps { - private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background"; HomeAllApps(LauncherInstrumentation launcher) { super(launcher); @@ -98,25 +97,6 @@ public class HomeAllApps extends AllApps { .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); } - /** - * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only. - * @param tapRight Tap on the right of bottom sheet if true, or left otherwise. - */ - public Workspace dismissByTappingOutsideForTablet(boolean tapRight) { - try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); - LauncherInstrumentation.Closable c = mLauncher.addContextLayer( - "want to tap outside AllApps bottom sheet on the " - + (tapRight ? "right" : "left"))) { - final UiObject2 allAppsBottomSheet = - mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID); - mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight); - try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer( - "tapped outside AllApps bottom sheet")) { - return mLauncher.getWorkspace(); - } - } - } - @NonNull @Override public Qsb getQsb() { @@ -128,4 +108,27 @@ public class HomeAllApps extends AllApps { return mLauncher.getTestInfo(TestProtocol.REQUEST_APPS_LIST_SCROLL_Y) .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); } + + @Override + protected void verifyVisibleContainerOnDismiss() { + mLauncher.getWorkspace(); + } + + @Override + public boolean isHomeState() { + return true; + } + + /** Send the "back" gesture to go to workspace. */ + public Workspace pressBackToWorkspace() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to press back from all apps to workspace")) { + mLauncher.runToState( + () -> mLauncher.pressBackImpl(), + NORMAL_STATE_ORDINAL, + "pressing back"); + return new Workspace(mLauncher); + } + } } diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java new file mode 100644 index 0000000000..a1d8059631 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.tapl; + +import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID; + +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.testing.shared.TestProtocol; + +import java.util.regex.Pattern; + +/** + * Operations on the Keyboard Quick Switch View + */ +public final class KeyboardQuickSwitch { + + private static final Pattern EVENT_ALT_TAB_DOWN = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB" + + ".*?metaState=META_ALT_ON"); + private static final Pattern EVENT_ALT_TAB_UP = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB" + + ".*?metaState=META_ALT_ON"); + private static final Pattern EVENT_ALT_SHIFT_TAB_DOWN = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB" + + ".*?metaState=META_ALT_ON|META_SHIFT_ON"); + private static final Pattern EVENT_ALT_SHIFT_TAB_UP = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB" + + ".*?metaState=META_ALT_ON|META_SHIFT_ON"); + private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN" + + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON"); + private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP" + + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON"); + private static final Pattern EVENT_KQS_ALT_LEFT_UP = Pattern.compile( + "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP" + + ".*?keyCode=KEYCODE_ALT_LEFT"); + private static final Pattern EVENT_HOME_ALT_LEFT_UP = Pattern.compile( + "Key event: KeyEvent.*?action=ACTION_UP" + + ".*?keyCode=KEYCODE_ALT_LEFT"); + + private final LauncherInstrumentation mLauncher; + private final LauncherInstrumentation.ContainerType mStartingContainerType; + private final boolean mExpectHomeKeyEventsOnDismiss; + + KeyboardQuickSwitch( + LauncherInstrumentation launcher, + LauncherInstrumentation.ContainerType startingContainerType, + boolean expectHomeKeyEventsOnDismiss) { + mLauncher = launcher; + mStartingContainerType = startingContainerType; + mExpectHomeKeyEventsOnDismiss = expectHomeKeyEventsOnDismiss; + } + + /** + * Focuses the next task in the Keyboard quick switch view. + * <p> + * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel. + * <ul> + * <li>If no task has been focused yet, and there is only one task, then that task will be + * focused</li> + * <li>If no task has been focused yet, and there are two or more tasks, then the second + * task will be focused</li> + * <li>If the currently-focused task is at the end of the list, the first task will be + * focused</li> + * </ul> + */ + public KeyboardQuickSwitch moveFocusForward() { + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "want to move keyboard quick switch focus forward"); + LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); + + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_DOWN); + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP); + mLauncher.assertTrue("Failed to press alt+tab", + mLauncher.getDevice().pressKeyCode( + KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON)); + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "pressed alt+tab")) { + mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); + + return this; + } + } + } + + /** + * Focuses the next task in the Keyboard quick switch view. + * <p> + * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel. + * <ul> + * <li>If no task has been focused yet, and there is only one task, then that task will be + * focused</li> + * <li>If no task has been focused yet, and there are two or more tasks, then the second + * task will be focused</li> + * <li>If the currently-focused task is at the start of the list, the last task will be + * focused</li> + * </ul> + */ + public KeyboardQuickSwitch moveFocusBackward() { + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "want to move keyboard quick switch focus backward"); + LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); + + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_DOWN); + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP); + mLauncher.assertTrue("Failed to press alt+shift+tab", + mLauncher.getDevice().pressKeyCode( + KeyEvent.KEYCODE_TAB, + KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)); + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "pressed alt+shift+tab")) { + mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); + + return this; + } + } + } + + /** + * Dismisses the Keyboard Quick Switch view without launching the focused task. + * <p> + * The device will return to the same state it started in before displaying the Keyboard Quick + * Switch view. + */ + public void dismiss() { + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "want to dismiss keyboard quick switch view"); + LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); + + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN); + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP); + mLauncher.assertTrue("Failed to press alt+tab", + mLauncher.getDevice().pressKeyCode( + KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON)); + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "pressed alt+esc")) { + mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); + + // Verify the final state is the same as the initial state + mLauncher.verifyContainerType(mStartingContainerType); + + // Wait until the device has fully settled before unpressing the key code + if (mExpectHomeKeyEventsOnDismiss) { + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP); + } + mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0); + } + } + } + + /** + * Launches the currently-focused app task. + * <p> + * This method should only be used if the focused task is for a recent running app, otherwise + * use {@link #launchFocusedOverviewTask()}. + * + * @param expectedPackageName the package name of the expected launched app + */ + public LaunchedAppState launchFocusedAppTask(@NonNull String expectedPackageName) { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + return (LaunchedAppState) launchFocusedTask(expectedPackageName); + } + } + + /** + * Launches the currently-focused overview task. + * <p> + * This method only should be used if the focused task is for overview, otherwise use + * {@link #launchFocusedAppTask(String)}. + */ + public Overview launchFocusedOverviewTask() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + return (Overview) launchFocusedTask(null); + } + } + + private LauncherInstrumentation.VisibleContainer launchFocusedTask( + @Nullable String expectedPackageName) { + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "want to launch focused task: " + + (expectedPackageName == null ? "Overview" : expectedPackageName))) { + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KQS_ALT_LEFT_UP); + mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0); + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "un-pressed left alt")) { + mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); + + if (expectedPackageName != null) { + mLauncher.assertAppLaunched(expectedPackageName); + return mLauncher.getLaunchedAppState(); + } else { + return mLauncher.getOverview(); + } + } + } + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java new file mode 100644 index 0000000000..677ed0434a --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.tapl; + +import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID; + +import android.view.KeyEvent; + +/** + * {@link com.android.launcher3.tapl.LauncherInstrumentation.VisibleContainer} that can be used to + * show the keyboard quick switch view. + */ +interface KeyboardQuickSwitchSource { + + /** + * Shows the Keyboard Quick Switch view. + */ + default KeyboardQuickSwitch showQuickSwitchView() { + LauncherInstrumentation launcher = getLauncher(); + + try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer( + "want to show keyboard quick switch object"); + LauncherInstrumentation.Closable e = launcher.eventsCheck()) { + launcher.pressAndHoldKeyCode(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_LEFT_ON); + + try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer( + "press and held alt+tab")) { + launcher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID); + launcher.unpressKeyCode(KeyEvent.KEYCODE_TAB, 0); + + return new KeyboardQuickSwitch( + launcher, getStartingContainerType(), isHomeState()); + } + } + } + + /** This method requires public access, however should not be called in tests. */ + LauncherInstrumentation getLauncher(); + + /** This method requires public access, however should not be called in tests. */ + LauncherInstrumentation.ContainerType getStartingContainerType(); + + /** This method requires public access, however should not be called in tests. */ + boolean isHomeState(); +} diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index a953fa9009..fe927b3fd0 100644 --- a/tests/tapl/com/android/launcher3/tapl/Launchable.java +++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java @@ -16,15 +16,14 @@ package com.android.launcher3.tapl; +import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; + import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL; import android.graphics.Point; import android.view.MotionEvent; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; -import androidx.test.uiautomator.Until; import com.android.launcher3.testing.shared.TestProtocol; @@ -48,12 +47,36 @@ public abstract class Launchable { return mObject; } + protected boolean launcherStopsAfterLaunch() { + return true; + } + /** * Clicks the object to launch its app. + * We are assuming non-translucent app launches because only such launches generate + * LAUNCHER_ACTIVITY_STOPPED_MESSAGE. */ public LaunchedAppState launch(String expectedPackageName) { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { - return launch(By.pkg(expectedPackageName)); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "want to launch an app from " + launchableType())) { + LauncherInstrumentation.log("Launchable.launch before click " + + mObject.getVisibleCenter() + " in " + + mLauncher.getVisibleBounds(mObject)); + + if (launcherStopsAfterLaunch()) { + mLauncher.executeAndWaitForLauncherStop( + () -> mLauncher.clickLauncherObject(mObject), + "clicking the launchable"); + } else { + mLauncher.clickLauncherObject(mObject); + } + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) { + expectActivityStartEvents(); + return mLauncher.assertAppLaunched(expectedPackageName); + } + } } } @@ -61,21 +84,6 @@ public abstract class Launchable { protected abstract String launchableType(); - private LaunchedAppState launch(BySelector selector) { - try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( - "want to launch an app from " + launchableType())) { - LauncherInstrumentation.log("Launchable.launch before click " - + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject)); - - mLauncher.clickLauncherObject(mObject); - - try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) { - expectActivityStartEvents(); - return assertAppLaunched(selector); - } - } - } - /** * Clicks a launcher object to initiate splitscreen, where the selected app will be one of two * apps running on the screen. Should be called when Launcher is in a "split staging" state @@ -83,12 +91,18 @@ public abstract class Launchable { * fired when the click is executed. */ public LaunchedAppState launchIntoSplitScreen() { - try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( - "want to launch split tasks from " + launchableType())) { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "want to launch split tasks from " + launchableType())) { LauncherInstrumentation.log("Launchable.launch before click " - + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject)); - - mLauncher.clickLauncherObject(mObject); + + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds( + mObject)); + mLauncher.executeAndWaitForLauncherEvent( + () -> mLauncher.clickLauncherObject(mObject), + accessibilityEvent -> + accessibilityEvent.getEventType() == TYPE_WINDOW_STATE_CHANGED, + () -> "Unable to click object to launch split", + "Click launcher object to launch split"); try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) { mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT); @@ -97,14 +111,6 @@ public abstract class Launchable { } } - protected LaunchedAppState assertAppLaunched(BySelector selector) { - mLauncher.assertTrue( - "App didn't start: (" + selector + ")", - mLauncher.getDevice().wait(Until.hasObject(selector), - LauncherInstrumentation.WAIT_TIME_MS)); - return new LaunchedAppState(mLauncher); - } - Point startDrag(long downTime, Runnable expectLongClickEvents, boolean runToSpringLoadedState) { final Point iconCenter = getObject().getVisibleCenter(); final Point dragStartCenter = new Point(iconCenter.x, diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java index 30417c082f..184ece74f5 100644 --- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java +++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java @@ -20,12 +20,11 @@ import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_IN import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID; import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS; import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT; -import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING; import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT; -import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING; import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_SHELL_DRAG_READY; -import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT; import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_SCALE; +import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD; +import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD; import android.graphics.Point; import android.graphics.Rect; @@ -34,7 +33,6 @@ import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; -import androidx.test.uiautomator.By; import androidx.test.uiautomator.Condition; import androidx.test.uiautomator.UiDevice; @@ -55,13 +53,13 @@ public final class LaunchedAppState extends Background { private static final int STASHED_TASKBAR_BOTTOM_EDGE_DP = 1; private final Condition<UiDevice, Boolean> mStashedTaskbarHintScaleCondition = - device -> mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat( - TestProtocol.TEST_INFO_RESPONSE_FIELD) - UNSTASHED_TASKBAR_HANDLE_HINT_SCALE + device -> Math.abs(mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat( + TestProtocol.TEST_INFO_RESPONSE_FIELD) - UNSTASHED_TASKBAR_HANDLE_HINT_SCALE) < 0.00001f; private final Condition<UiDevice, Boolean> mStashedTaskbarDefaultScaleCondition = - device -> mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat( - TestProtocol.TEST_INFO_RESPONSE_FIELD) - 1f < 0.00001f; + device -> Math.abs(mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat( + TestProtocol.TEST_INFO_RESPONSE_FIELD) - 1f) < 0.00001f; LaunchedAppState(LauncherInstrumentation launcher) { super(launcher); @@ -72,6 +70,11 @@ public final class LaunchedAppState extends Background { return LauncherInstrumentation.ContainerType.LAUNCHED_APP; } + @Override + public boolean isHomeState() { + return false; + } + /** * Returns the taskbar. * @@ -80,8 +83,6 @@ public final class LaunchedAppState extends Background { public Taskbar getTaskbar() { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to get the taskbar")) { - mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID); - return new Taskbar(mLauncher); } } @@ -109,37 +110,32 @@ public final class LaunchedAppState extends Background { /** * Returns the Taskbar in a visible state. * - * The taskbar must already be hidden when calling this method. + * The taskbar must already be hidden and in transient mode when calling this method. */ - public Taskbar showTaskbar() { - mLauncher.getTestInfo(REQUEST_ENABLE_MANUAL_TASKBAR_STASHING); + public Taskbar swipeUpToUnstashTaskbar() { + mLauncher.assertTrue("Taskbar is not transient, swipe up not supported", + mLauncher.isTransientTaskbar()); + mLauncher.getTestInfo(REQUEST_ENABLE_BLOCK_TIMEOUT); try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( - "want to show the taskbar")) { + "want to swipe up to unstash the taskbar")) { mLauncher.waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); - final long downTime = SystemClock.uptimeMillis(); - final int unstashTargetY = mLauncher.getRealDisplaySize().y - - (mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_HEIGHT) - .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) / 2); - final Point unstashTarget = new Point( - mLauncher.getRealDisplaySize().x / 2, unstashTargetY); + int taskbarFromNavThreshold = mLauncher.getTestInfo(REQUEST_TASKBAR_FROM_NAV_THRESHOLD) + .getInt(TEST_INFO_RESPONSE_FIELD); + int startX = mLauncher.getRealDisplaySize().x / 2; + int startY = mLauncher.getRealDisplaySize().y - 1; + int endX = startX; + int endY = startY - taskbarFromNavThreshold; - mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, unstashTarget, + mLauncher.linearGesture(startX, startY, endX, endY, 10, /* slowDown= */ true, LauncherInstrumentation.GestureScope.EXPECT_PILFER); - LauncherInstrumentation.log("showTaskbar: sent down"); + LauncherInstrumentation.log("swipeUpToUnstashTaskbar: sent linear swipe up gesture"); - try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) { - mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID); - mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, unstashTarget, - LauncherInstrumentation.GestureScope.EXPECT_PILFER); - - return new Taskbar(mLauncher); - } + return new Taskbar(mLauncher); } finally { - mLauncher.getTestInfo(REQUEST_DISABLE_MANUAL_TASKBAR_STASHING); mLauncher.getTestInfo(REQUEST_DISABLE_BLOCK_TIMEOUT); } } @@ -200,8 +196,8 @@ public final class LaunchedAppState extends Background { try (LauncherInstrumentation.Closable c4 = launcher.addContextLayer( "dropped item")) { - launchable.assertAppLaunched(By.pkg(expectedNewPackageName)); - launchable.assertAppLaunched(By.pkg(expectedExistingPackageName)); + launcher.assertAppLaunched(expectedNewPackageName); + launcher.assertAppLaunched(expectedExistingPackageName); } } } @@ -280,7 +276,8 @@ public final class LaunchedAppState extends Background { Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2, mLauncher.getRealDisplaySize().y - 1); mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER, - new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null); + new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null, + InputDevice.SOURCE_MOUSE); mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition, LauncherInstrumentation.WAIT_TIME_MS); @@ -292,7 +289,7 @@ public final class LaunchedAppState extends Background { mLauncher.getRealDisplaySize().y - 500); mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT, new Point(outsideStashedTaskbarHintArea.x, outsideStashedTaskbarHintArea.y), - null); + null, InputDevice.SOURCE_MOUSE); mLauncher.getDevice().wait(mStashedTaskbarDefaultScaleCondition, LauncherInstrumentation.WAIT_TIME_MS); @@ -343,4 +340,17 @@ public final class LaunchedAppState extends Background { } } } + + /** Send the "back" gesture to go to workspace. */ + public Workspace pressBackToWorkspace() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to press back from launched app to workspace")) { + mLauncher.executeAndWaitForWallpaperAnimation( + () -> mLauncher.pressBackImpl(), + "pressing back" + ); + return new Workspace(mLauncher); + } + } } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index c58ae1608a..91ef472ee4 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -20,21 +20,26 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.PackageManager.DONT_KILL_APP; import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT; import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID; import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; +import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS; import android.app.ActivityManager; import android.app.Instrumentation; import android.app.UiAutomation; +import android.app.UiModeManager; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Point; @@ -48,6 +53,9 @@ import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; import android.view.InputDevice; +import android.view.InputEvent; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.WindowManager; @@ -169,10 +177,13 @@ public final class LauncherInstrumentation { private static final String OPEN_FOLDER_RES_ID = "folder_content"; static final String TASKBAR_RES_ID = "taskbar_view"; private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder"; + static final String KEYBOARD_QUICK_SWITCH_RES_ID = "keyboard_quick_switch_view"; public static final int WAIT_TIME_MS = 30000; static final long DEFAULT_POLL_INTERVAL = 1000; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; private static final String ANDROID_PACKAGE = "android"; + private static final String ASSISTANT_PACKAGE = "com.google.android.googlequicksearchbox"; + private static final String ASSISTANT_GO_HOME_RES_ID = "home_icon"; private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null); @@ -204,15 +215,33 @@ public final class LauncherInstrumentation { * Constructs the root of TAPL hierarchy. You get all other objects from it. */ public LauncherInstrumentation() { - this(InstrumentationRegistry.getInstrumentation()); + this(InstrumentationRegistry.getInstrumentation(), false); } /** * Constructs the root of TAPL hierarchy. You get all other objects from it. - * Deprecated: use the constructor without parameters instead. + */ + public LauncherInstrumentation(boolean isLauncherTest) { + this(InstrumentationRegistry.getInstrumentation(), isLauncherTest); + } + + /** + * Constructs the root of TAPL hierarchy. You get all other objects from it. + * + * @deprecated use the constructor without Instrumentation parameter instead. */ @Deprecated public LauncherInstrumentation(Instrumentation instrumentation) { + this(instrumentation, false); + } + + /** + * Constructs the root of TAPL hierarchy. You get all other objects from it. + * + * @deprecated use the constructor without Instrumentation parameter instead. + */ + @Deprecated + public LauncherInstrumentation(Instrumentation instrumentation, boolean isLauncherTest) { mInstrumentation = instrumentation; mDevice = UiDevice.getInstance(instrumentation); @@ -251,28 +280,31 @@ public final class LauncherInstrumentation { if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) { if (TestHelpers.isInLauncherProcess()) { pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP); - // b/195031154 - SystemClock.sleep(5000); } else { try { final int userId = getContext().getUserId(); - final String launcherPidCommand = "pidof " + pi.packageName; - final String initialPid = mDevice.executeShellCommand(launcherPidCommand) - .replaceAll("\\s", ""); mDevice.executeShellCommand( "pm enable --user " + userId + " " + cn.flattenToString()); - // Wait for Launcher restart after enabling test provider. - for (int i = 0; i < 100; ++i) { - final String currentPid = mDevice.executeShellCommand(launcherPidCommand) - .replaceAll("\\s", ""); - if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break; - if (i == 99) fail("Launcher didn't restart after enabling test provider"); - SystemClock.sleep(100); - } } catch (IOException e) { fail(e.toString()); } } + + final int iterations = isLauncherTest ? 300 : 100; + + // Wait for Launcher content provider to become enabled. + for (int i = 0; i < iterations; ++i) { + final ContentProviderClient testProvider = getContext().getContentResolver() + .acquireContentProviderClient(mTestProviderUri); + if (testProvider != null) { + testProvider.close(); + break; + } + if (i == iterations - 1) { + fail("Launcher content provider is still not enabled"); + } + SystemClock.sleep(100); + } } } @@ -338,6 +370,16 @@ public final class LauncherInstrumentation { .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); } + Insets getImeInsets() { + return getTestInfo(TestProtocol.REQUEST_IME_INSETS) + .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + + public int getNumAllAppsColumns() { + return getTestInfo(REQUEST_NUM_ALL_APPS_COLUMNS).getInt( + TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + public boolean isTablet() { return getTestInfo(TestProtocol.REQUEST_IS_TABLET) .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); @@ -543,6 +585,11 @@ public final class LauncherInstrumentation { if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview"; if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace"; if (hasLauncherObject(APPS_RES_ID)) return "AllApps"; + if (hasLauncherObject(TASKBAR_RES_ID)) return "Taskbar"; + if (hasLauncherObject("wallpaper_carousel")) return "Launcher Settings Popup"; + if (mDevice.hasObject(By.pkg(getLauncherPackageName()).depth(0))) { + return "<Launcher in invalid state>"; + } return "LaunchedApp (" + getVisiblePackages() + ")"; } @@ -689,6 +736,7 @@ public final class LauncherInstrumentation { /** * Set the trackpad gesture type of the interaction. + * * @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or * four-finger gesture. */ @@ -713,6 +761,10 @@ public final class LauncherInstrumentation { mExpectedRotationCheckEnabled = expectedRotationCheckEnabled; } + public boolean getExpectedRotationCheckEnabled() { + return mExpectedRotationCheckEnabled; + } + public String getNavigationModeMismatchError(boolean waitForCorrectState) { final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0; final NavigationModel navigationModel = getNavigationModel(); @@ -743,7 +795,7 @@ public final class LauncherInstrumentation { return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE; } - private UiObject2 verifyContainerType(ContainerType containerType) { + UiObject2 verifyContainerType(ContainerType containerType) { waitForLauncherInitialized(); if (mExpectedRotationCheckEnabled && mExpectedRotation != null) { @@ -772,12 +824,8 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(WIDGETS_RES_ID); waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); - - if (is3PLauncher() && isTablet()) { - waitForSystemLauncherObject(TASKBAR_RES_ID); - } else { - waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); - } + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); + waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); return waitForLauncherObject(WORKSPACE_RES_ID); } @@ -786,12 +834,8 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(APPS_RES_ID); waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); - - if (is3PLauncher() && isTablet()) { - waitForSystemLauncherObject(TASKBAR_RES_ID); - } else { - waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); - } + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); + waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); return waitForLauncherObject(WIDGETS_RES_ID); } @@ -801,6 +845,7 @@ public final class LauncherInstrumentation { waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); return waitForLauncherObject(APPS_RES_ID); } @@ -809,8 +854,9 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(WIDGETS_RES_ID); waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); - if (is3PLauncher() && isTablet()) { + if (is3PLauncher() && isTablet() && !isTransientTaskbar()) { waitForSystemLauncherObject(TASKBAR_RES_ID); } else { waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); @@ -823,12 +869,13 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(APPS_RES_ID); waitUntilLauncherObjectGone(WORKSPACE_RES_ID); waitUntilLauncherObjectGone(WIDGETS_RES_ID); - if (isTablet()) { + if (isTablet() && !is3PLauncher()) { waitForSystemLauncherObject(TASKBAR_RES_ID); } else { waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); } waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); return waitForSystemLauncherObject(OVERVIEW_RES_ID); } @@ -843,6 +890,7 @@ public final class LauncherInstrumentation { } waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID); + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); return waitForSystemLauncherObject(OVERVIEW_RES_ID); } case LAUNCHED_APP: { @@ -851,13 +899,18 @@ public final class LauncherInstrumentation { waitUntilLauncherObjectGone(WIDGETS_RES_ID); waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID); waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID); + waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID); if (mIgnoreTaskbarVisibility) { return null; } if (isTablet()) { - waitForSystemLauncherObject(TASKBAR_RES_ID); + // Only check that Persistent Taskbar is visible, since Transient Taskbar + // may or may not be visible by design. + if (!isTransientTaskbar()) { + waitForSystemLauncherObject(TASKBAR_RES_ID); + } } else { waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); } @@ -887,6 +940,12 @@ public final class LauncherInstrumentation { fail("Launcher didn't initialize"); } + public boolean isLauncherActivityStarted() { + return getTestInfo( + TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED). + getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + Parcelable executeAndWaitForLauncherEvent(Runnable command, UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message, String actionName) { @@ -915,6 +974,14 @@ public final class LauncherInstrumentation { } } + void executeAndWaitForLauncherStop(Runnable command, String actionName) { + executeAndWaitForLauncherEvent( + () -> command.run(), + event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE + .equals(event.getClassName().toString()), + () -> "Launcher activity didn't stop", actionName); + } + /** * Get the resource ID of visible floating view. */ @@ -931,7 +998,7 @@ public final class LauncherInstrumentation { /** * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content. */ - private void swipeUpToCloseFloatingView(boolean gestureStartFromLauncher) { + private void swipeUpToCloseFloatingView() { final Point displaySize = getRealDisplaySize(); final Optional<String> floatingRes = getFloatingResId(); @@ -940,16 +1007,11 @@ public final class LauncherInstrumentation { return; } - GestureScope gestureScope = gestureStartFromLauncher - // Without the navigation bar layer, the gesture scope on tablets remains inside the - // launcher process. - ? (isTablet() ? GestureScope.DONT_EXPECT_PILFER : GestureScope.EXPECT_PILFER) - : GestureScope.EXPECT_PILFER; linearGesture( displaySize.x / 2, displaySize.y - 1, displaySize.x / 2, 0, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, - false, gestureScope); + false, GestureScope.EXPECT_PILFER); try (LauncherInstrumentation.Closable c1 = addContextLayer( String.format("Swiped up from floating view %s to home", floatingRes.get()))) { @@ -969,6 +1031,25 @@ public final class LauncherInstrumentation { } /** + * Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then + * performing {@code goHome()} action. + * Currently only supports gesture navigation mode. + * + * @return the Workspace object. + */ + public Workspace goHomeFromImmersiveFullscreenApp() { + assertTrue("expected gesture navigation mode", + getNavigationModel() == NavigationModel.ZERO_BUTTON); + final Point displaySize = getRealDisplaySize(); + linearGesture( + displaySize.x / 2, displaySize.y - 1, + displaySize.x / 2, 0, + ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, + false, GestureScope.EXPECT_PILFER); + return goHome(); + } + + /** * Goes to home by swiping up in zero-button mode or pressing Home button. * Calling it after another TAPL call is safe because all TAPL methods wait for the animations * to finish. @@ -998,11 +1079,8 @@ public final class LauncherInstrumentation { final Point displaySize = getRealDisplaySize(); - boolean gestureStartFromLauncher = - isTablet() ? !isLauncher3() : isLauncherVisible(); - // CLose floating views before going back to home. - swipeUpToCloseFloatingView(gestureStartFromLauncher); + swipeUpToCloseFloatingView(); if (hasLauncherObject(WORKSPACE_RES_ID)) { log(action = "already at home"); @@ -1024,7 +1102,7 @@ public final class LauncherInstrumentation { action = "clicking home button"; runToState( - waitForNavigationUiObject("home")::click, + getHomeButton()::click, NORMAL_STATE_ORDINAL, !hasLauncherObject(WORKSPACE_RES_ID) && (hasLauncherObject(APPS_RES_ID) @@ -1043,30 +1121,34 @@ public final class LauncherInstrumentation { */ public void pressBack() { try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) { - waitForLauncherInitialized(); - final boolean launcherVisible = - isTablet() ? isLauncherContainerVisible() : isLauncherVisible(); - boolean isThreeFingerTrackpadGesture = - mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; - if (getNavigationModel() == NavigationModel.ZERO_BUTTON - || isThreeFingerTrackpadGesture) { - final Point displaySize = getRealDisplaySize(); - // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the - // issue is solved. - int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0; - int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2; - linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4, - 10, false, GestureScope.DONT_EXPECT_PILFER); + pressBackImpl(); + } + } + + void pressBackImpl() { + waitForLauncherInitialized(); + final boolean launcherVisible = + isTablet() ? isLauncherContainerVisible() : isLauncherVisible(); + boolean isThreeFingerTrackpadGesture = + mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; + if (getNavigationModel() == NavigationModel.ZERO_BUTTON + || isThreeFingerTrackpadGesture) { + final Point displaySize = getRealDisplaySize(); + // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the + // issue is solved. + int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0; + int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2; + linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4, + 10, false, GestureScope.DONT_EXPECT_PILFER); + } else { + waitForNavigationUiObject("back").click(); + } + if (launcherVisible) { + if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) { + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED); } else { - waitForNavigationUiObject("back").click(); - } - if (launcherVisible) { - if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) { - expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ON_BACK_INVOKED); - } else { - expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN); - expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP); - } + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN); + expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP); } } } @@ -1158,6 +1240,14 @@ public final class LauncherInstrumentation { } } + LaunchedAppState assertAppLaunched(@NonNull String expectedPackageName) { + BySelector packageSelector = By.pkg(expectedPackageName); + assertTrue("App didn't start: (" + packageSelector + ")", + mDevice.wait(Until.hasObject(packageSelector), + LauncherInstrumentation.WAIT_TIME_MS)); + return new LaunchedAppState(this); + } + void waitUntilLauncherObjectGone(String resId) { waitUntilGoneBySelector(getLauncherObjectSelector(resId)); } @@ -1205,6 +1295,28 @@ public final class LauncherInstrumentation { } @NonNull + private UiObject2 getHomeButton() { + UiModeManager uiManager = + (UiModeManager) getContext().getSystemService(Context.UI_MODE_SERVICE); + if (uiManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { + return waitForAssistantHomeButton(); + } else { + return waitForNavigationUiObject("home"); + } + } + + /* Assistant Home button is present when system is in car mode. */ + @NonNull + UiObject2 waitForAssistantHomeButton() { + final UiObject2 object = mDevice.wait( + Until.findObject(By.res(ASSISTANT_PACKAGE, ASSISTANT_GO_HOME_RES_ID)), + WAIT_TIME_MS); + assertNotNull( + "Can't find an assistant UI object with id: " + ASSISTANT_GO_HOME_RES_ID, object); + return object; + } + + @NonNull UiObject2 waitForNavigationUiObject(String resId) { String resPackage = getNavigationButtonResPackage(); final UiObject2 object = mDevice.wait( @@ -1268,6 +1380,16 @@ public final class LauncherInstrumentation { } } + void waitForObjectFocused(UiObject2 object, String waitReason) { + try { + assertTrue("Timed out waiting for object to be focused for " + waitReason + " " + + object.getResourceName(), + object.wait(Until.focused(true), WAIT_TIME_MS)); + } catch (StaleObjectException e) { + fail("The object disappeared from screen"); + } + } + @NonNull UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) { return waitForObjectsInContainer(container, selector).get(0); @@ -1516,8 +1638,11 @@ public final class LauncherInstrumentation { scroll( container, Direction.LEFT, - new Rect(leftGestureMargin, 0, - containerRect.width() - distance - rightGestureMarginInContainer, 0), + new Rect(leftGestureMargin, + 0, + Math.max(containerRect.width() - distance - leftGestureMargin, + rightGestureMarginInContainer), + 0), 10, true); } @@ -1690,7 +1815,7 @@ public final class LauncherInstrumentation { TestProtocol.TEST_INFO_RESPONSE_FIELD); } - boolean isGridOnlyOverviewEnabled() { + public boolean isGridOnlyOverviewEnabled() { return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean( TestProtocol.TEST_INFO_RESPONSE_FIELD); } @@ -1698,11 +1823,21 @@ public final class LauncherInstrumentation { public void sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope) { sendPointer(downTime, currentTime, action, point, gestureScope, - InputDevice.SOURCE_TOUCHSCREEN); + InputDevice.SOURCE_TOUCHSCREEN, false); + } + + private void injectEvent(InputEvent event) { + assertTrue("injectInputEvent failed: event=" + event, + mInstrumentation.getUiAutomation().injectInputEvent(event, true, false)); } public void sendPointer(long downTime, long currentTime, int action, Point point, GestureScope gestureScope, int source) { + sendPointer(downTime, currentTime, action, point, gestureScope, source, false); + } + + public void sendPointer(long downTime, long currentTime, int action, Point point, + GestureScope gestureScope, int source, boolean isRightClick) { final boolean hasTIS = hasTIS(); int pointerCount = mPointerCount; @@ -1731,16 +1866,49 @@ public final class LauncherInstrumentation { final MotionEvent event = isTrackpadGesture ? getTrackpadMotionEvent( - downTime, currentTime, action, point.x, point.y, pointerCount, - mTrackpadGestureType) + downTime, currentTime, action, point.x, point.y, pointerCount, + mTrackpadGestureType) : getMotionEvent(downTime, currentTime, action, point.x, point.y, source); if (action == MotionEvent.ACTION_BUTTON_PRESS || action == MotionEvent.ACTION_BUTTON_RELEASE) { event.setActionButton(MotionEvent.BUTTON_PRIMARY); } - assertTrue("injectInputEvent failed", - mInstrumentation.getUiAutomation().injectInputEvent(event, true, false)); - event.recycle(); + if (isRightClick) { + event.setButtonState(event.getButtonState() & MotionEvent.BUTTON_SECONDARY); + } + injectEvent(event); + } + + private KeyEvent createKeyEvent(int keyCode, int metaState, boolean actionDown) { + long eventTime = SystemClock.uptimeMillis(); + return KeyEvent.obtain( + eventTime, + eventTime, + actionDown ? ACTION_DOWN : ACTION_UP, + keyCode, + /* repeat= */ 0, + metaState, + KeyCharacterMap.VIRTUAL_KEYBOARD, + /* scancode= */ 0, + /* flags= */ 0, + InputDevice.SOURCE_KEYBOARD, + /* characters =*/ null); + } + + /** + * Sends a {@link KeyEvent} with {@link ACTION_DOWN} for the given key codes without sending + * a {@link KeyEvent} with {@link ACTION_UP}. + */ + public void pressAndHoldKeyCode(int keyCode, int metaState) { + injectEvent(createKeyEvent(keyCode, metaState, true)); + } + + + /** + * Sends a {@link KeyEvent} with {@link ACTION_UP} for the given key codes. + */ + public void unpressKeyCode(int keyCode, int metaState) { + injectEvent(createKeyEvent(keyCode, metaState, false)); } public long movePointer(long downTime, long startTime, long duration, Point from, Point to, @@ -1814,6 +1982,22 @@ public final class LauncherInstrumentation { return result; } + @NonNull + UiObject2 rightClickAndGet( + @NonNull final UiObject2 target, @NonNull String resName, Pattern rightClickEvent) { + final Point targetCenter = target.getVisibleCenter(); + final long downTime = SystemClock.uptimeMillis(); + sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, + GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, + /* isRightClick= */ true); + expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent); + final UiObject2 result = waitForLauncherObject(resName); + sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter, + GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE, + /* isRightClick= */ true); + return result; + } + private static int getSystemIntegerRes(Context context, String resName) { Resources res = context.getResources(); int resId = res.getIdentifier(resName, "integer", "android"); @@ -1854,6 +2038,7 @@ public final class LauncherInstrumentation { } } + /** Returns the bounds of the display as a Point where x is width and y is height. */ Point getRealDisplaySize() { final Rect displayBounds = getContext().getSystemService(WindowManager.class) .getMaximumWindowMetrics() @@ -1916,6 +2101,11 @@ public final class LauncherInstrumentation { : TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT); } + public boolean isTransientTaskbar() { + return getTestInfo(TestProtocol.REQUEST_IS_TRANSIENT_TASKBAR) + .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + /** Enables transient taskbar for testing purposes only. */ public void enableTransientTaskbar(boolean enable) { getTestInfo(enable @@ -1932,6 +2122,13 @@ public final class LauncherInstrumentation { getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR); } + // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup. + + /** Refreshes the known overview target in TIS. */ + public void refreshOverviewTarget() { + getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET); + } + public List<String> getHotseatIconNames() { return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES) .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); @@ -1946,14 +2143,16 @@ public final class LauncherInstrumentation { return String.join(", ", getActivities()); } - public boolean noLeakedActivities() { + /** Returns whether no leaked activities are detected. */ + public boolean noLeakedActivities(boolean requireOneActiveActivity) { final String[] activities = getActivities(); + for (String activity : activities) { if (activity.contains("(destroyed)")) { return false; } } - return activities.length <= 2; + return activities.length <= (requireOneActiveActivity ? 1 : 2); } public int getActivitiesCreated() { @@ -1994,7 +2193,8 @@ public final class LauncherInstrumentation { }; } - boolean isLauncher3() { + /** Returns whether the Launcher is a Launcher3 one */ + public boolean isLauncher3() { if (mIsLauncher3 == null) { mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName()); } @@ -2086,7 +2286,13 @@ public final class LauncherInstrumentation { ? containerBounds.right + 1 : containerBounds.left - 1; } - int y = containerBounds.top + containerBounds.height() / 2; + // If IME is visible and overlaps the container bounds, touch above it. + int bottomBound = Math.min( + containerBounds.bottom, + getRealDisplaySize().y - getImeInsets().bottom); + int y = (bottomBound + containerBounds.top) / 2; + // Do not tap in the status bar. + y = Math.max(y, getWindowInsets().top); final long downTime = SystemClock.uptimeMillis(); final Point tapTarget = new Point(x, y); @@ -2113,4 +2319,14 @@ public final class LauncherInstrumentation { } return result; } + + /** Executes a runnable and waits for the wallpaper-open animation completion. */ + public void executeAndWaitForWallpaperAnimation(Runnable r, String actionName) { + executeAndWaitForLauncherEvent( + () -> r.run(), + event -> TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE + .equals(event.getClassName().toString()), + () -> "Didn't detect finishing wallpaper-open animation", + actionName); + } } diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index e4cfc52a2c..f383e99584 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -16,8 +16,6 @@ package com.android.launcher3.tapl; -import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; - import android.graphics.Rect; import androidx.annotation.NonNull; @@ -36,6 +34,8 @@ import java.util.stream.Collectors; */ public final class OverviewTask { private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; + private static final String TASK_SNAPSHOT_1 = "snapshot"; + private static final String TASK_SNAPSHOT_2 = "bottomright_snapshot"; static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync"); static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect"); @@ -55,20 +55,75 @@ public final class OverviewTask { mOverview.verifyActiveContainer(); } + /** + * Returns the height of the visible task, or the combined height of two tasks in split with a + * divider between. + */ int getVisibleHeight() { + if (isTaskSplit()) { + return getCombinedSplitTaskHeight(); + } + return mTask.getVisibleBounds().height(); } + /** + * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider. + */ + private int getCombinedSplitTaskHeight() { + UiObject2 taskSnapshot1 = findObjectInTask(TASK_SNAPSHOT_1); + UiObject2 taskSnapshot2 = findObjectInTask(TASK_SNAPSHOT_2); + + // If the split task is partly off screen, taskSnapshot1 can be invisible. + if (taskSnapshot1 == null) { + return taskSnapshot2.getVisibleBounds().height(); + } + + int top = Math.min( + taskSnapshot1.getVisibleBounds().top, taskSnapshot2.getVisibleBounds().top); + int bottom = Math.max( + taskSnapshot1.getVisibleBounds().bottom, taskSnapshot2.getVisibleBounds().bottom); + + return bottom - top; + } + + /** + * Returns the width of the visible task, or the combined width of two tasks in split with a + * divider between. + */ int getVisibleWidth() { + if (isTaskSplit()) { + return getCombinedSplitTaskWidth(); + } + return mTask.getVisibleBounds().width(); } + /** + * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider. + */ + private int getCombinedSplitTaskWidth() { + UiObject2 taskSnapshot1 = findObjectInTask(TASK_SNAPSHOT_1); + UiObject2 taskSnapshot2 = findObjectInTask(TASK_SNAPSHOT_2); + + int left = Math.min( + taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left); + int right = Math.max( + taskSnapshot1.getVisibleBounds().right, taskSnapshot2.getVisibleBounds().right); + + return right - left; + } + int getTaskCenterX() { - return mTask.getVisibleCenter().x; + return mTask.getParent().getVisibleCenter().x; + } + + int getTaskCenterY() { + return mTask.getParent().getVisibleCenter().y; } float getExactCenterX() { - return mTask.getVisibleBounds().exactCenterX(); + return mTask.getParent().getVisibleBounds().exactCenterX(); } UiObject2 getUiObject() { @@ -92,7 +147,8 @@ public final class OverviewTask { boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher .getFocusedTaskHeightForTablet(); - List<Integer> originalTasksCenterX = getCurrentTasksCenterXList(); + List<Integer> originalTasksCenterX = + getCurrentTasksCenterXList().stream().sorted().toList(); boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible(); dismissBySwipingUp(); @@ -103,7 +159,8 @@ public final class OverviewTask { mOverview.getFocusedTaskForTablet()); } if (!isClearAllVisibleBeforeDismiss) { - List<Integer> currentTasksCenterX = getCurrentTasksCenterXList(); + List<Integer> currentTasksCenterX = + getCurrentTasksCenterXList().stream().sorted().toList(); if (originalTasksCenterX.size() == currentTasksCenterX.size()) { // Check for the same number of visible tasks before and after to // avoid asserting on cases of shifting all tasks to close the distance @@ -121,7 +178,7 @@ public final class OverviewTask { // Dismiss the task via flinging it up. final Rect taskBounds = mLauncher.getVisibleBounds(mTask); final int centerX = taskBounds.centerX(); - final int centerY = taskBounds.centerY(); + final int centerY = taskBounds.bottom - 1; mLauncher.executeAndWaitForLauncherEvent( () -> mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER), @@ -133,8 +190,8 @@ public final class OverviewTask { private List<Integer> getCurrentTasksCenterXList() { return mLauncher.isTablet() ? mOverview.getCurrentTasksForTablet().stream() - .map(OverviewTask::getTaskCenterX) - .collect(Collectors.toList()) + .map(OverviewTask::getTaskCenterX) + .collect(Collectors.toList()) : List.of(mOverview.getCurrentTask().getTaskCenterX()); } @@ -144,11 +201,8 @@ public final class OverviewTask { public LaunchedAppState open() { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { verifyActiveContainer(); - mLauncher.executeAndWaitForEvent( + mLauncher.executeAndWaitForLauncherStop( () -> mLauncher.clickLauncherObject(mTask), - event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED, - () -> "Launching task didn't open a new window: " - + mTask.getParent().getContentDescription(), "clicking an overview task"); if (mOverview.getContainerType() == LauncherInstrumentation.ContainerType.SPLIT_SCREEN_SELECT) { @@ -168,7 +222,7 @@ public final class OverviewTask { } } - /** Taps the task menu. */ + /** Taps the task menu. Returns the task menu object. */ @NonNull public OverviewTaskMenu tapMenu() { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); @@ -184,7 +238,27 @@ public final class OverviewTask { } } + /** Taps the task menu of the split task. Returns the split task's menu object. */ + @NonNull + public OverviewTaskMenu tapSplitTaskMenu() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to tap the split task's menu")) { + mLauncher.clickLauncherObject( + mLauncher.waitForObjectInContainer(mTask.getParent(), "bottomRight_icon")); + + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "tapped the split task's menu")) { + return new OverviewTaskMenu(mLauncher); + } + } + } + boolean isTaskSplit() { - return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null; + return findObjectInTask(TASK_SNAPSHOT_2) != null; + } + + private UiObject2 findObjectInTask(String resName) { + return mTask.getParent().findObject(mLauncher.getOverviewObjectSelector(resName)); } } diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java index 7c29a6cf87..3d2914d754 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java @@ -50,15 +50,19 @@ public class OverviewTaskMenu { } } - /** Taps the app info item from the overview task menu and returns the LaunchedAppState - * representing the App info settings page. */ + /** + * Taps the app info item from the overview task menu and returns the LaunchedAppState + * representing the App info settings page. + */ @NonNull public LaunchedAppState tapAppInfoMenuItem() { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "before tapping the app info menu item")) { - mLauncher.clickLauncherObject( - mLauncher.findObjectInContainer(mMenu, By.text("App info"))); + mLauncher.executeAndWaitForLauncherStop( + () -> mLauncher.clickLauncherObject( + mLauncher.findObjectInContainer(mMenu, By.text("App info"))), + "tapped app info menu item"); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( "tapped app info menu item")) { @@ -81,4 +85,11 @@ public class OverviewTaskMenu { return new OverviewTaskMenuItem(mLauncher, mLauncher.waitForObjectInContainer(mMenu, By.text(menuItemName))); } + + /** + * Taps outside task menu to dismiss it. + */ + public void touchOutsideTaskMenuToDismiss() { + mLauncher.touchOutsideContainer(mMenu, false); + } } diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java index 7f3f61d81f..fe2a63dd57 100644 --- a/tests/tapl/com/android/launcher3/tapl/Qsb.java +++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java @@ -22,13 +22,18 @@ import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; +import java.util.regex.Pattern; + /** * Operations on qsb from either Home screen or AllApp screen. */ -public abstract class Qsb { +public abstract class Qsb implements SearchInputSource { private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox"; private static final String ASSISTANT_ICON_RES_ID = "mic_icon"; + private static final String LENS_ICON_RES_ID = "lens_icon"; + private static final Pattern LENS_APP_RES_PATTERN = Pattern.compile( + ASSISTANT_APP_PACKAGE + ":id/lens.*"); protected final LauncherInstrumentation mLauncher; private final UiObject2 mContainer; private final String mQsbResName; @@ -76,6 +81,37 @@ public abstract class Qsb { } /** + * Launches lens app by tapping lens icon on qsb. + */ + @NonNull + public LaunchedAppState launchLens() { + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to click lens icon button"); + LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + UiObject2 lensIcon = mLauncher.waitForLauncherObject(LENS_ICON_RES_ID); + + LauncherInstrumentation.log("Qsb.launchLens before click " + + lensIcon.getVisibleCenter() + " in " + + mLauncher.getVisibleBounds(lensIcon)); + + mLauncher.clickLauncherObject(lensIcon); + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) { + // Package name is not enough to check if the app is launched, because many + // elements are having googlequicksearchbox as package name. So it checks if the + // corresponding app resource is displayed + BySelector selector = By.res(LENS_APP_RES_PATTERN); + mLauncher.assertTrue( + "Lens app didn't start: (" + selector + ")", + mLauncher.getDevice().wait(Until.hasObject(selector), + LauncherInstrumentation.WAIT_TIME_MS) + ); + return new LaunchedAppState(mLauncher); + } + } + } + + /** * Show search result page from tapping qsb. */ public SearchResultFromQsb showSearchResult() { @@ -92,6 +128,16 @@ public abstract class Qsb { } } + @Override + public LauncherInstrumentation getLauncher() { + return mLauncher; + } + + @Override + public SearchResultFromQsb getSearchResultForInput() { + return createSearchResult(); + } + protected SearchResultFromQsb createSearchResult() { return new SearchResultFromQsb(mLauncher); } diff --git a/tests/tapl/com/android/launcher3/tapl/SearchInputSource.java b/tests/tapl/com/android/launcher3/tapl/SearchInputSource.java new file mode 100644 index 0000000000..032948f851 --- /dev/null +++ b/tests/tapl/com/android/launcher3/tapl/SearchInputSource.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.tapl; + +import androidx.test.uiautomator.UiObject2; + +import com.android.launcher3.testing.shared.TestProtocol; + +/** + * Container that can be used to input a search query and retrieve a {@link SearchResultFromQsb} + * instance. + */ +interface SearchInputSource { + String INPUT_RES = "input"; + + /** Set the already focused search input edit text and update search results. */ + default SearchResultFromQsb searchForInput(String input) { + LauncherInstrumentation launcher = getLauncher(); + try (LauncherInstrumentation.Closable c = launcher.addContextLayer( + "want to search for result with an input"); + LauncherInstrumentation.Closable e = launcher.eventsCheck()) { + launcher.executeAndWaitForLauncherEvent( + () -> { + UiObject2 editText = launcher.waitForLauncherObject(INPUT_RES); + launcher.waitForObjectFocused(editText, "search input"); + editText.setText(input); + }, + event -> TestProtocol.SEARCH_RESULT_COMPLETE.equals(event.getClassName()), + () -> "Didn't receive a search result completed message", "searching"); + return getSearchResultForInput(); + } + } + + /** This method requires public access, however should not be called in tests. */ + LauncherInstrumentation getLauncher(); + + /** This method requires public access, however should not be called in tests. */ + SearchResultFromQsb getSearchResultForInput(); +} diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java index d02e747900..f0a8aa26c0 100644 --- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java +++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java @@ -25,9 +25,7 @@ import java.util.ArrayList; /** * Operations on search result page opened from qsb. */ -public class SearchResultFromQsb { - // The input resource id in the search box. - private static final String INPUT_RES = "input"; +public class SearchResultFromQsb implements SearchInputSource { private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background"; // This particular ID change should happen with caution @@ -39,15 +37,6 @@ public class SearchResultFromQsb { mLauncher.waitForLauncherObject("search_container_all_apps"); } - /** Set the input to the search input edit text and update search results. */ - public void searchForInput(String input) { - try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( - "want to search for result with an input"); - LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { - mLauncher.waitForLauncherObject(INPUT_RES).setText(input); - } - } - /** Find the app from search results with app name. */ public AppIcon findAppIcon(String appName) { UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName)); @@ -91,7 +80,7 @@ public class SearchResultFromQsb { * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only. * @param tapRight Tap on the right of bottom sheet if true, or left otherwise. */ - public Workspace dismissByTappingOutsideForTablet(boolean tapRight) { + public void dismissByTappingOutsideForTablet(boolean tapRight) { try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to tap outside AllApps bottom sheet on the " @@ -101,8 +90,22 @@ public class SearchResultFromQsb { mLauncher.touchOutsideContainer(allAppsBottomSheet, tapRight); try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer( "tapped outside AllApps bottom sheet")) { - return mLauncher.getWorkspace(); + verifyVisibleContainerOnDismiss(); } } } + + protected void verifyVisibleContainerOnDismiss() { + mLauncher.getWorkspace(); + } + + @Override + public LauncherInstrumentation getLauncher() { + return mLauncher; + } + + @Override + public SearchResultFromQsb getSearchResultForInput() { + return this; + } } diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java index 6c6ab05ac3..00291a3381 100644 --- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java +++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java @@ -45,4 +45,9 @@ public class SearchResultFromTaskbarQsb extends SearchResultFromQsb { protected TaskbarSearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) { return new TaskbarSearchWebSuggestion(mLauncher, webSuggestion); } + + @Override + protected void verifyVisibleContainerOnDismiss() { + mLauncher.getLaunchedAppState().assertTaskbarVisible(); + } } diff --git a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java index ce1c3c0e9d..2870877f93 100644 --- a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java +++ b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java @@ -15,7 +15,7 @@ */ package com.android.launcher3.tapl; -/** Launchable that can serve as a source for dragging and dropping to splitscreen. */ +/** {@link Launchable} that can serve as a source for dragging and dropping to splitscreen. */ interface SplitscreenDragSource { /** @@ -35,5 +35,6 @@ interface SplitscreenDragSource { } } + /** This method requires public access, however should not be called in tests. */ Launchable getLaunchable(); } diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java index 8671738fe7..a202c53375 100644 --- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java +++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java @@ -15,9 +15,9 @@ */ package com.android.launcher3.tapl; +import static android.view.KeyEvent.KEYCODE_META_RIGHT; + import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID; -import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING; -import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING; import android.graphics.Point; import android.graphics.Rect; @@ -31,6 +31,8 @@ import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; +import org.junit.Assert; + import java.util.List; import java.util.stream.Collectors; @@ -43,6 +45,15 @@ public final class Taskbar { Taskbar(LauncherInstrumentation launcher) { mLauncher = launcher; + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "expect new taskbar to be visible")) { + mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID); + } + + if (!mLauncher.isTransientTaskbar()) { + Assert.assertEquals("Persistent taskbar should fill screen width", + getVisibleBounds().width(), mLauncher.getRealDisplaySize().x); + } } /** @@ -59,40 +70,39 @@ public final class Taskbar { } /** - * Hides this taskbar. - * - * The taskbar must already be visible when calling this method. + * Stashes this taskbar. + * <p> + * The taskbar must already be unstashed and in transient mode when calling this method. */ - public void hide() { - mLauncher.getTestInfo(REQUEST_ENABLE_MANUAL_TASKBAR_STASHING); + public void swipeDownToStash() { + mLauncher.assertTrue("Taskbar is not transient, swipe down not supported", + mLauncher.isTransientTaskbar()); try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to hide the taskbar"); LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID); - final long downTime = SystemClock.uptimeMillis(); - Point stashTarget = new Point( - mLauncher.getRealDisplaySize().x - 1, mLauncher.getRealDisplaySize().y - 1); + Rect taskbarBounds = getVisibleBounds(); + int startX = taskbarBounds.centerX(); + int startY = taskbarBounds.centerY(); + int endX = startX; + int endY = mLauncher.getRealDisplaySize().y - 1; - mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, stashTarget, + mLauncher.linearGesture(startX, startY, endX, endY, 10, false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); - LauncherInstrumentation.log("hideTaskbar: sent down"); - - try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("pressed down")) { + LauncherInstrumentation.log("swipeDownToStash: sent linear swipe down gesture"); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "expect transient taskbar to be hidden after swipe down")) { mLauncher.waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID); - mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, stashTarget, - LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); } - } finally { - mLauncher.getTestInfo(REQUEST_DISABLE_MANUAL_TASKBAR_STASHING); } } /** * Opens the Taskbar all apps page. */ - public AllAppsFromTaskbar openAllApps() { + public TaskbarAllApps openAllApps() { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to open taskbar all apps"); LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { @@ -101,7 +111,26 @@ public final class Taskbar { mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID), getAllAppsButtonSelector())); - return new AllAppsFromTaskbar(mLauncher); + return getAllApps(); + } + } + + /** Opens the Taskbar all apps page with the meta keyboard shortcut. */ + public TaskbarAllApps openAllAppsFromKeyboardShortcut() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) { + mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT); + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "pressed meta key")) { + return getAllApps(); + } + } + } + + /** Returns {@link TaskbarAllApps} if it is open, otherwise fails. */ + public TaskbarAllApps getAllApps() { + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to get taskbar all apps object")) { + return new TaskbarAllApps(mLauncher); } } diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java index 0e0291f82c..3d39041124 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromTaskbar.java +++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java @@ -23,9 +23,9 @@ import com.android.launcher3.testing.shared.TestProtocol; /** * Operations on AllApps opened from the Taskbar. */ -public class AllAppsFromTaskbar extends AllApps { +public class TaskbarAllApps extends AllApps { - AllAppsFromTaskbar(LauncherInstrumentation launcher) { + TaskbarAllApps(LauncherInstrumentation launcher) { super(launcher); } @@ -68,4 +68,14 @@ public class AllAppsFromTaskbar extends AllApps { public TaskbarAllAppsQsb getQsb() { return new TaskbarAllAppsQsb(mLauncher, verifyActiveContainer()); } + + @Override + protected void verifyVisibleContainerOnDismiss() { + mLauncher.getLaunchedAppState().assertTaskbarVisible(); + } + + @Override + public boolean isHomeState() { + return false; + } } diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java index 099acd4716..064f80cfcc 100644 --- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java +++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java @@ -25,6 +25,7 @@ import java.util.regex.Pattern; public final class TaskbarAppIcon extends AppIcon implements SplitscreenDragSource { private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onTaskbarItemLongClick"); + private static final Pattern RIGHT_CLICK_EVENT = Pattern.compile("onTaskbarItemRightClick"); TaskbarAppIcon(LauncherInstrumentation launcher, UiObject2 icon) { super(launcher, icon); @@ -35,11 +36,26 @@ public final class TaskbarAppIcon extends AppIcon implements SplitscreenDragSour return LONG_CLICK_EVENT; } + protected Pattern getRightClickEvent() { + return RIGHT_CLICK_EVENT; + } + @Override public TaskbarAppIconMenu openDeepShortcutMenu() { return (TaskbarAppIconMenu) super.openDeepShortcutMenu(); } + /** + * Right-clicks the icon to open its menu. + */ + public TaskbarAppIconMenu openDeepShortcutMenuWithRightClick() { + try (LauncherInstrumentation.Closable e = mLauncher.addContextLayer( + "want to return the shortcut menu when icon is right-clicked.")) { + return createMenu(mLauncher.rightClickAndGet( + mObject, /* resName= */ "deep_shortcuts_container", getRightClickEvent())); + } + } + @Override protected TaskbarAppIconMenu createMenu(UiObject2 menu) { return new TaskbarAppIconMenu(mLauncher, menu); @@ -49,4 +65,9 @@ public final class TaskbarAppIcon extends AppIcon implements SplitscreenDragSour public Launchable getLaunchable() { return this; } + + @Override + protected boolean launcherStopsAfterLaunch() { + return false; + } } diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java index 424c58e260..e6fdda03cb 100644 --- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java +++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIconMenuItem.java @@ -54,4 +54,9 @@ public final class TaskbarAppIconMenuItem extends AppIconMenuItem implements Spl public Launchable getLaunchable() { return this; } + + @Override + protected boolean launcherStopsAfterLaunch() { + return false; + } } diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java index cd8ce42f49..f25af09d9e 100644 --- a/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java +++ b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java @@ -42,4 +42,9 @@ public class TaskbarSearchWebSuggestion extends SearchWebSuggestion implements public Launchable getLaunchable() { return this; } + + @Override + protected boolean launcherStopsAfterLaunch() { + return false; + } } diff --git a/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java index 8f51d04c3d..ec1cbd8ec1 100644 --- a/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java +++ b/tests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java @@ -15,6 +15,16 @@ */ package com.android.launcher3.tapl; +import static com.android.launcher3.tapl.Launchable.DEFAULT_DRAG_STEPS; +import static org.junit.Assert.assertTrue; + +import android.graphics.Point; +import android.graphics.Rect; +import android.os.SystemClock; +import android.view.MotionEvent; + +import androidx.test.uiautomator.UiObject2; + /** The resize frame that is shown for a widget on the workspace. */ public class WidgetResizeFrame { @@ -34,4 +44,36 @@ public class WidgetResizeFrame { mLauncher.getDevice().pressHome(); } } + + /** Resizes the widget to double its height, and returns the resize frame. */ + public WidgetResizeFrame resize() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to resize the widget frame.")) { + UiObject2 widget = mLauncher.waitForLauncherObject("widget_resize_frame"); + UiObject2 bottomResizeHandle = + mLauncher.waitForLauncherObject("widget_resize_bottom_handle"); + Rect originalWidgetSize = widget.getVisibleBounds(); + Point targetStart = bottomResizeHandle.getVisibleCenter(); + Point targetDest = bottomResizeHandle.getVisibleCenter(); + targetDest.offset(0, originalWidgetSize.height()); + + final long downTime = SystemClock.uptimeMillis(); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetStart, + LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + mLauncher.movePointer(targetStart, targetDest, DEFAULT_DRAG_STEPS, + true, downTime, downTime, true, + LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP, targetDest, + LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + + try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer( + "want to return resized widget resize frame")) { + float newHeight = mLauncher.waitForLauncherObject( + "widget_resize_frame").getVisibleBounds().height(); + assertTrue("Widget not resized.", newHeight >= originalWidgetSize.height() * 2); + return this; + } + } + } } diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java index 79b54ba0a3..105bc3bf50 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -34,7 +34,8 @@ import java.util.Collection; /** * All widgets container. */ -public final class Widgets extends LauncherInstrumentation.VisibleContainer { +public final class Widgets extends LauncherInstrumentation.VisibleContainer + implements KeyboardQuickSwitchSource { private static final int FLING_STEPS = 10; private static final int SCROLL_ATTEMPTS = 60; @@ -43,6 +44,21 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { verifyActiveContainer(); } + @Override + public LauncherInstrumentation getLauncher() { + return mLauncher; + } + + @Override + public LauncherInstrumentation.ContainerType getStartingContainerType() { + return getContainerType(); + } + + @Override + public boolean isHomeState() { + return true; + } + /** * Flings forward (down) and waits the fling's end. */ diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 6f6f2926b7..f8fa00c365 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -16,6 +16,7 @@ package com.android.launcher3.tapl; +import static android.view.KeyEvent.KEYCODE_META_RIGHT; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED; import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL; @@ -114,6 +115,20 @@ public final class Workspace extends Home { } } + /** Opens the Launcher all apps page with the meta keyboard shortcut. */ + public HomeAllApps openAllAppsFromKeyboardShortcut() { + try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable c = + mLauncher.addContextLayer("want to open all apps search")) { + verifyActiveContainer(); + mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT); + try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( + "pressed meta key")) { + return new HomeAllApps(mLauncher); + } + } + } + /** * Returns the home qsb. * @@ -320,7 +335,8 @@ public final class Workspace extends Home { homeAppIcon, () -> new Point(0, 0), () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), - null); + null, + /* startsActivity = */ false); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( "dragged the app across workspace")) { @@ -344,7 +360,8 @@ public final class Workspace extends Home { homeAppIcon, () -> getDropPointFromDropTargetBar(mLauncher, DELETE_TARGET_TEXT_ID), () -> mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), - /* expectDropEvents= */ null); + /* expectDropEvents= */ null, + /* startsActivity = */ false); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( "dragged the app to the drop bar")) { @@ -353,7 +370,6 @@ public final class Workspace extends Home { } } - /** * Uninstall the appIcon by dragging it to the 'uninstall' drop point of the drop_target_bar. * @@ -375,7 +391,8 @@ public final class Workspace extends Home { homeAppIcon, () -> getDropPointFromDropTargetBar(launcher, UNINSTALL_TARGET_TEXT_ID), expectLongClickEvents, - /* expectDropEvents= */null); + /* expectDropEvents= */null, + /* startsActivity = */ false); launcher.waitUntilLauncherObjectGone(DROP_BAR_RES_ID); @@ -449,14 +466,25 @@ public final class Workspace extends Home { o -> new FolderIcon(mLauncher, o)).collect(Collectors.toList()); } + private static void sendUp(LauncherInstrumentation launcher, Point dest, + long downTime) { + launcher.sendPointer( + downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest, + LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); + } + private static void dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime, - @Nullable Runnable expectedEvents) { - launcher.runToState( - () -> launcher.sendPointer( - downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest, - LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER), - NORMAL_STATE_ORDINAL, - "sending UP event"); + @Nullable Runnable expectedEvents, boolean startsActivity) { + if (startsActivity) { + launcher.executeAndWaitForLauncherStop( + () -> sendUp(launcher, dest, downTime), + "sending UP event"); + } else { + launcher.runToState( + () -> sendUp(launcher, dest, downTime), + NORMAL_STATE_ORDINAL, + "sending UP event"); + } if (expectedEvents != null) { expectedEvents.run(); } @@ -473,7 +501,8 @@ public final class Workspace extends Home { LauncherInstrumentation.EVENT_START); } dragIconToWorkspace( - launcher, launchable, dest, expectLongClickEvents, expectDropEvents); + launcher, launchable, dest, expectLongClickEvents, expectDropEvents, + startsActivity); } static void dragIconToWorkspaceCellPosition(LauncherInstrumentation launcher, @@ -502,7 +531,8 @@ public final class Workspace extends Home { destSupplier, /* isDecelerating= */ false, () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), - /* expectDropEvents= */ null); + /* expectDropEvents= */ null, + /* startsActivity = */ false); } static void dragIconToWorkspace( @@ -510,9 +540,10 @@ public final class Workspace extends Home { Launchable launchable, Supplier<Point> dest, Runnable expectLongClickEvents, - @Nullable Runnable expectDropEvents) { + @Nullable Runnable expectDropEvents, + boolean startsActivity) { dragIconToWorkspace(launcher, launchable, dest, /* isDecelerating */ true, - expectLongClickEvents, expectDropEvents); + expectLongClickEvents, expectDropEvents, startsActivity); } static void dragIconToWorkspace( @@ -521,7 +552,8 @@ public final class Workspace extends Home { Supplier<Point> dest, boolean isDecelerating, Runnable expectLongClickEvents, - @Nullable Runnable expectDropEvents) { + @Nullable Runnable expectDropEvents, + boolean startsActivity) { try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer( "want to drag icon to workspace")) { final long downTime = SystemClock.uptimeMillis(); @@ -553,7 +585,7 @@ public final class Workspace extends Home { launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(), false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); - dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents); + dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, startsActivity); } } @@ -586,7 +618,8 @@ public final class Workspace extends Home { launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(), false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); - dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents); + dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, + /* startsActivity = */ false); } } @@ -652,7 +685,8 @@ public final class Workspace extends Home { launcher.movePointer(dragStart, targetDest, DEFAULT_DRAG_STEPS, true, downTime, SystemClock.uptimeMillis(), false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER); - dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents); + dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, + /* startsActivity = */ false); } /** diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java index 141476c782..e5a2a2ef6c 100644 --- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java +++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java @@ -19,7 +19,7 @@ import android.graphics.Point; import java.util.function.Supplier; -/** Launchable that can serve as a source for dragging and dropping to the workspace. */ +/** {@link Launchable} that can serve as a source for dragging and dropping to the workspace. */ interface WorkspaceDragSource { /** @@ -81,7 +81,8 @@ interface WorkspaceDragSource { launchable, dest, launchable::addExpectedEventsForLongClick, - /*expectDropEvents= */ null); + /*expectDropEvents= */ null, + /* startsActivity = */ false); try (LauncherInstrumentation.Closable ignore = launcher.addContextLayer("dragged")) { WorkspaceAppIcon appIcon = |