mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 23:36:47 +00:00
This CL refactors some methods in OverviewTask to rely on OverviewSplitTask. It also changes the BaseOverview getTasks function to retrieve the parent task and support cases where only the bottomright_snapshot is available in the recent tasks. Bug: 320633351 Test: TaplTestsSplitscreen Flag: N/A Change-Id: I226f895810ce3b46cc107f76cd85e2918abf7088
454 lines
17 KiB
Java
454 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2018 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 android.view.KeyEvent.KEYCODE_ESCAPE;
|
|
|
|
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
|
|
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
|
|
|
|
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 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;
|
|
|
|
/**
|
|
* Common overview panel for both Launcher and fallback recents
|
|
*/
|
|
public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
|
|
protected static final String TASK_RES_ID = "task";
|
|
|
|
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 static final int FLINGS_FOR_DISMISS_LIMIT = 40;
|
|
|
|
BaseOverview(LauncherInstrumentation launcher) {
|
|
super(launcher);
|
|
verifyActiveContainer();
|
|
verifyActionsViewVisibility();
|
|
}
|
|
|
|
@Override
|
|
protected LauncherInstrumentation.ContainerType getContainerType() {
|
|
return LauncherInstrumentation.ContainerType.FALLBACK_OVERVIEW;
|
|
}
|
|
|
|
/**
|
|
* Flings forward (left) and waits the fling's end.
|
|
*/
|
|
public void flingForward() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
|
flingForwardImpl();
|
|
}
|
|
}
|
|
|
|
private void flingForwardImpl() {
|
|
try (LauncherInstrumentation.Closable c =
|
|
mLauncher.addContextLayer("want to fling forward in overview")) {
|
|
LauncherInstrumentation.log("Overview.flingForward before fling");
|
|
final UiObject2 overview = verifyActiveContainer();
|
|
final int leftMargin =
|
|
mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth();
|
|
mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20,
|
|
false);
|
|
try (LauncherInstrumentation.Closable c2 =
|
|
mLauncher.addContextLayer("flung forwards")) {
|
|
verifyActiveContainer();
|
|
verifyActionsViewVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flings backward (right) and waits the fling's end.
|
|
*/
|
|
public void flingBackward() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
|
flingBackwardImpl();
|
|
}
|
|
}
|
|
|
|
private void flingBackwardImpl() {
|
|
try (LauncherInstrumentation.Closable c =
|
|
mLauncher.addContextLayer("want to fling backward in overview")) {
|
|
LauncherInstrumentation.log("Overview.flingBackward before fling");
|
|
final UiObject2 overview = verifyActiveContainer();
|
|
final int rightMargin =
|
|
mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth();
|
|
mLauncher.scroll(
|
|
overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
|
|
try (LauncherInstrumentation.Closable c2 =
|
|
mLauncher.addContextLayer("flung backwards")) {
|
|
verifyActiveContainer();
|
|
verifyActionsViewVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
private OverviewTask flingToFirstTask() {
|
|
OverviewTask currentTask = getCurrentTask();
|
|
|
|
while (mLauncher.getRealDisplaySize().x - currentTask.getUiObject().getVisibleBounds().right
|
|
<= mLauncher.getOverviewPageSpacing()) {
|
|
flingBackwardImpl();
|
|
currentTask = getCurrentTask();
|
|
}
|
|
|
|
return currentTask;
|
|
}
|
|
|
|
/**
|
|
* Dismissed all tasks by scrolling to Clear-all button and pressing it.
|
|
*/
|
|
public void dismissAllTasks() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"dismissing all tasks")) {
|
|
final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
|
|
for (int i = 0;
|
|
i < FLINGS_FOR_DISMISS_LIMIT
|
|
&& !verifyActiveContainer().hasObject(clearAllSelector);
|
|
++i) {
|
|
flingForwardImpl();
|
|
}
|
|
|
|
final Runnable clickClearAll = () -> mLauncher.clickLauncherObject(
|
|
mLauncher.waitForObjectInContainer(verifyActiveContainer(),
|
|
clearAllSelector));
|
|
if (mLauncher.is3PLauncher()) {
|
|
mLauncher.executeAndWaitForLauncherStop(
|
|
clickClearAll,
|
|
"clicking 'Clear All'");
|
|
} else {
|
|
mLauncher.runToState(
|
|
clickClearAll,
|
|
NORMAL_STATE_ORDINAL,
|
|
"clicking 'Clear All'");
|
|
}
|
|
|
|
mLauncher.waitUntilLauncherObjectGone(clearAllSelector);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Touch to the right of current task. This should dismiss overview and go back to Workspace.
|
|
*/
|
|
public Workspace touchOutsideFirstTask() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"touching outside the focused task")) {
|
|
|
|
if (getTaskCount() < 2) {
|
|
throw new IllegalStateException(
|
|
"Need to have at least 2 tasks");
|
|
}
|
|
|
|
OverviewTask currentTask = flingToFirstTask();
|
|
|
|
mLauncher.runToState(
|
|
() -> mLauncher.touchOutsideContainer(currentTask.getUiObject(),
|
|
/* tapRight= */ true,
|
|
/* halfwayToEdge= */ false),
|
|
NORMAL_STATE_ORDINAL,
|
|
"touching outside of first task");
|
|
|
|
return new Workspace(mLauncher);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Touch between two tasks
|
|
*/
|
|
public void touchBetweenTasks() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"touching outside the focused task")) {
|
|
if (getTaskCount() < 2) {
|
|
throw new IllegalStateException(
|
|
"Need to have at least 2 tasks");
|
|
}
|
|
|
|
OverviewTask currentTask = flingToFirstTask();
|
|
|
|
mLauncher.touchOutsideContainer(currentTask.getUiObject(),
|
|
/* tapRight= */ false,
|
|
/* halfwayToEdge= */ false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Touch either on the right or the left corner of the screen, 1 pixel from the bottom and
|
|
* from the sides.
|
|
*/
|
|
public void touchTaskbarBottomCorner(boolean tapRight) {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
|
Taskbar taskbar = new Taskbar(mLauncher);
|
|
if (mLauncher.isTransientTaskbar()) {
|
|
mLauncher.runToState(
|
|
() -> taskbar.touchBottomCorner(tapRight),
|
|
NORMAL_STATE_ORDINAL,
|
|
"touching taskbar");
|
|
// Tapping outside Transient Taskbar returns to Workspace, wait for that state.
|
|
new Workspace(mLauncher);
|
|
} else {
|
|
taskbar.touchBottomCorner(tapRight);
|
|
// Should stay in Overview.
|
|
verifyActiveContainer();
|
|
verifyActionsViewVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scrolls the current task via flinging forward until it is off screen.
|
|
*
|
|
* If only one task is present, it is only partially scrolled off screen and will still be
|
|
* the current task.
|
|
*/
|
|
public void scrollCurrentTaskOffScreen() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"want to scroll current task off screen in overview")) {
|
|
verifyActiveContainer();
|
|
|
|
OverviewTask task = getCurrentTask();
|
|
mLauncher.assertNotNull("current task is null", task);
|
|
mLauncher.scrollLeftByDistance(verifyActiveContainer(),
|
|
mLauncher.getRealDisplaySize().x - task.getUiObject().getVisibleBounds().left
|
|
+ mLauncher.getOverviewPageSpacing());
|
|
|
|
try (LauncherInstrumentation.Closable c2 =
|
|
mLauncher.addContextLayer("scrolled task off screen")) {
|
|
verifyActiveContainer();
|
|
verifyActionsViewVisibility();
|
|
|
|
if (getTaskCount() > 1) {
|
|
if (mLauncher.isTablet()) {
|
|
mLauncher.assertTrue("current task is not grid height",
|
|
getCurrentTask().getVisibleHeight() == mLauncher
|
|
.getGridTaskRectForTablet().height());
|
|
}
|
|
mLauncher.assertTrue("Current task not scrolled off screen",
|
|
!getCurrentTask().equals(task));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current task in the carousel, or fails if the carousel is empty.
|
|
*
|
|
* @return the task in the middle of the visible tasks list.
|
|
*/
|
|
@NonNull
|
|
public OverviewTask getCurrentTask() {
|
|
final List<UiObject2> taskViews = getTasks();
|
|
mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
|
|
|
|
// The widest, and most top-right task should be the current task
|
|
UiObject2 currentTask = Collections.max(taskViews,
|
|
Comparator.comparingInt((UiObject2 t) -> t.getVisibleBounds().width())
|
|
.thenComparingInt((UiObject2 t) -> t.getVisibleCenter().x)
|
|
.thenComparing(Comparator.comparing(
|
|
(UiObject2 t) -> t.getVisibleCenter().y).reversed()));
|
|
return new OverviewTask(mLauncher, currentTask, this);
|
|
}
|
|
|
|
/** Returns an overview task matching TestActivity {@param activityNumber}. */
|
|
@NonNull
|
|
public OverviewTask getTestActivityTask(int activityNumber) {
|
|
final List<UiObject2> taskViews = getTasks();
|
|
mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
|
|
|
|
final String activityName = "TestActivity" + activityNumber;
|
|
UiObject2 task = null;
|
|
for (UiObject2 taskView : taskViews) {
|
|
// TODO(b/239452415): Use equals instead of descEndsWith
|
|
if (taskView.getParent().hasObject(By.descEndsWith(activityName))) {
|
|
task = taskView;
|
|
break;
|
|
}
|
|
}
|
|
mLauncher.assertNotNull(
|
|
"Unable to find a task with " + activityName + " from the task list", task);
|
|
|
|
return new OverviewTask(mLauncher, task, this);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all tasks fully visible in the tablet grid overview.
|
|
*/
|
|
@NonNull
|
|
public List<OverviewTask> getCurrentTasksForTablet() {
|
|
final List<UiObject2> taskViews = getTasks();
|
|
mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
|
|
|
|
final int gridTaskWidth = mLauncher.getGridTaskRectForTablet().width();
|
|
|
|
return taskViews.stream().filter(t -> t.getVisibleBounds().width() == gridTaskWidth).map(
|
|
t -> new OverviewTask(mLauncher, t, this)).collect(Collectors.toList());
|
|
}
|
|
|
|
@NonNull
|
|
private List<UiObject2> getTasks() {
|
|
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"want to get overview tasks")) {
|
|
verifyActiveContainer();
|
|
return mLauncher.getDevice().findObjects(
|
|
mLauncher.getOverviewObjectSelector(TASK_RES_ID));
|
|
}
|
|
}
|
|
|
|
|
|
int getTaskCount() {
|
|
return getTasks().size();
|
|
}
|
|
|
|
/**
|
|
* Returns whether Overview has tasks.
|
|
*/
|
|
public boolean hasTasks() {
|
|
return getTasks().size() > 0;
|
|
}
|
|
|
|
/**
|
|
* Gets Overview Actions.
|
|
*
|
|
* @return The Overview Actions
|
|
*/
|
|
@NonNull
|
|
public OverviewActions getOverviewActions() {
|
|
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"want to get overview actions")) {
|
|
verifyActiveContainer();
|
|
UiObject2 overviewActions = mLauncher.waitForOverviewObject("action_buttons");
|
|
return new OverviewActions(overviewActions, mLauncher);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns if clear all button is visible.
|
|
*/
|
|
public boolean isClearAllVisible() {
|
|
return verifyActiveContainer().hasObject(
|
|
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;
|
|
}
|
|
boolean isTablet = mLauncher.isTablet();
|
|
if (isTablet && mLauncher.isGridOnlyOverviewEnabled()) {
|
|
return false;
|
|
}
|
|
OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
|
|
if (task == null) {
|
|
return false;
|
|
}
|
|
// In tablets, if focused task is not in center, overview actions aren't visible.
|
|
if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
|
|
return false;
|
|
}
|
|
// Overview actions aren't visible for split screen tasks.
|
|
return !task.isTaskSplit();
|
|
}
|
|
|
|
/**
|
|
* Presses the esc key to dismiss Overview.
|
|
*/
|
|
public Workspace 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.runToState(
|
|
() -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
|
|
NORMAL_STATE_ORDINAL, "pressing esc key");
|
|
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"pressed esc key")) {
|
|
return mLauncher.getWorkspace();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void verifyActionsViewVisibility() {
|
|
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"want to assert overview actions view visibility")) {
|
|
if (isActionsViewVisible()) {
|
|
mLauncher.waitForOverviewObject("action_buttons");
|
|
} else {
|
|
mLauncher.waitUntilOverviewObjectGone("action_buttons");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns Overview focused task if it exists.
|
|
*
|
|
* @throws IllegalStateException if not run on a tablet device.
|
|
*/
|
|
OverviewTask getFocusedTaskForTablet() {
|
|
if (!mLauncher.isTablet()) {
|
|
throw new IllegalStateException("Must be run on tablet device.");
|
|
}
|
|
final List<UiObject2> taskViews = getTasks();
|
|
if (taskViews.size() == 0) {
|
|
return null;
|
|
}
|
|
int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet();
|
|
for (UiObject2 task : taskViews) {
|
|
OverviewTask overviewTask = new OverviewTask(mLauncher, task, this);
|
|
|
|
if (overviewTask.getVisibleHeight() == focusedTaskHeight) {
|
|
return overviewTask;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|