mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
In this test: 1. Private Space is created. 2. Lock Button is clicked, locked view is verified. 3. Unlock Button is clicked, unlocked view is verified. 4. Private Space is removed. Doc: go/private-space-tapl Bug: 322882655 Test: TaplPrivateSpaceTest Flag: NA Change-Id: I1c399a96ea14a4464113576d31eed8db1caf1f2a
470 lines
20 KiB
Java
470 lines
20 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 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;
|
|
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
|
|
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.os.Bundle;
|
|
import android.widget.TextView;
|
|
|
|
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.StaleObjectException;
|
|
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;
|
|
|
|
/**
|
|
* Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
|
|
*/
|
|
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_UP = Pattern.compile(
|
|
"Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
|
|
private static final String UNLOCK_BUTTON_VIEW_RES_ID = "ps_lock_unlock_button";
|
|
|
|
private final int mHeight;
|
|
private final int mIconHeight;
|
|
|
|
AllApps(LauncherInstrumentation launcher) {
|
|
super(launcher);
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
mHeight = mLauncher.getVisibleBounds(allAppsContainer).height();
|
|
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
|
|
// Wait for the recycler to populate.
|
|
mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
|
|
verifyNotFrozen("All apps freeze flags upon opening all apps");
|
|
mIconHeight = mLauncher.getTestInfo(TestProtocol.REQUEST_ICON_HEIGHT)
|
|
.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;
|
|
try {
|
|
icon = appListRecycler.findObject(appIconSelector);
|
|
} catch (StaleObjectException e) {
|
|
mLauncher.fail("All apps recycler disappeared from screen");
|
|
return false;
|
|
}
|
|
if (icon == null) {
|
|
LauncherInstrumentation.log("hasClickableIcon: icon not visible");
|
|
return false;
|
|
}
|
|
final Rect iconBounds = mLauncher.getVisibleBounds(icon);
|
|
LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
|
|
if (iconBounds.height() < mIconHeight / 2) {
|
|
LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
|
|
return false;
|
|
}
|
|
if (hasSearchBox() && iconCenterInSearchBox(allAppsContainer, icon)) {
|
|
LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
|
|
return false;
|
|
}
|
|
if (iconCenterInRecyclerTopPadding(appListRecycler, icon)) {
|
|
LauncherInstrumentation.log(
|
|
"hasClickableIcon: icon center is under the app list recycler's top padding.");
|
|
return false;
|
|
}
|
|
if (iconBounds.bottom > displayBottom) {
|
|
LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset");
|
|
return false;
|
|
}
|
|
LauncherInstrumentation.log("hasClickableIcon: icon is clickable");
|
|
return true;
|
|
}
|
|
|
|
private boolean iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon) {
|
|
final Point iconCenter = icon.getVisibleCenter();
|
|
return mLauncher.getVisibleBounds(getSearchBox(allAppsContainer)).contains(
|
|
iconCenter.x, iconCenter.y);
|
|
}
|
|
|
|
private boolean iconCenterInRecyclerTopPadding(UiObject2 appsListRecycler, UiObject2 icon) {
|
|
final Point iconCenter = icon.getVisibleCenter();
|
|
|
|
return iconCenter.y <= mLauncher.getVisibleBounds(appsListRecycler).top
|
|
+ getAppsListRecyclerTopPadding();
|
|
}
|
|
|
|
/**
|
|
* Finds an icon. If the icon doesn't exist, return null.
|
|
* Scrolls the app list when needed to make sure the icon is visible.
|
|
*
|
|
* @param appName name of the app.
|
|
* @return The app if found, and null if not found.
|
|
*/
|
|
@Nullable
|
|
public AppIcon tryGetAppIcon(String appName) {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"getting app icon " + appName + " on all apps")) {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
|
|
|
|
int deviceHeight = mLauncher.getRealDisplaySize().y;
|
|
int bottomGestureStartOnScreen = mLauncher.getBottomGestureStartOnScreen();
|
|
final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
|
|
if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
|
|
bottomGestureStartOnScreen)) {
|
|
scrollBackToBeginning();
|
|
int attempts = 0;
|
|
int scroll = getAllAppsScroll();
|
|
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
|
|
while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
|
|
bottomGestureStartOnScreen)) {
|
|
mLauncher.scrollToLastVisibleRow(
|
|
allAppsContainer,
|
|
getBottomVisibleIconBounds(allAppsContainer),
|
|
mLauncher.getVisibleBounds(appListRecycler).top
|
|
+ getAppsListRecyclerTopPadding()
|
|
- mLauncher.getVisibleBounds(allAppsContainer).top,
|
|
getAppsListRecyclerBottomPadding());
|
|
verifyActiveContainer();
|
|
final int newScroll = getAllAppsScroll();
|
|
LauncherInstrumentation.log(
|
|
String.format("tryGetAppIcon: scrolled from %d to %d", scroll,
|
|
newScroll));
|
|
mLauncher.assertTrue(
|
|
"Scrolled in a wrong direction in AllApps: from " + scroll + " to "
|
|
+ newScroll, newScroll >= scroll);
|
|
if (newScroll == scroll) break;
|
|
|
|
mLauncher.assertTrue(
|
|
"Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
|
|
++attempts <= MAX_SCROLL_ATTEMPTS);
|
|
scroll = newScroll;
|
|
}
|
|
}
|
|
verifyActiveContainer();
|
|
}
|
|
// Ignore bottom offset selection here as there might not be any scroll more scroll
|
|
// region available.
|
|
if (hasClickableIcon(
|
|
allAppsContainer, appListRecycler, appIconSelector, deviceHeight)) {
|
|
|
|
final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
|
|
appIconSelector);
|
|
return createAppIcon(appIcon);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @return visible bounds of the top-most visible icon in the container. */
|
|
protected Rect getTopVisibleIconBounds(UiObject2 allAppsContainer) {
|
|
return mLauncher.getVisibleBounds(Collections.min(getVisibleIcons(allAppsContainer),
|
|
Comparator.comparingInt(i -> mLauncher.getVisibleBounds(i).top)));
|
|
}
|
|
|
|
/** @return visible bounds of the bottom-most visible icon in the container. */
|
|
protected Rect getBottomVisibleIconBounds(UiObject2 allAppsContainer) {
|
|
return mLauncher.getVisibleBounds(Collections.max(getVisibleIcons(allAppsContainer),
|
|
Comparator.comparingInt(i -> mLauncher.getVisibleBounds(i).top)));
|
|
}
|
|
|
|
@NonNull
|
|
private List<UiObject2> getVisibleIcons(UiObject2 allAppsContainer) {
|
|
return mLauncher.getObjectsInContainer(allAppsContainer, "icon")
|
|
.stream()
|
|
.filter(icon ->
|
|
mLauncher.getVisibleBounds(icon).top
|
|
< mLauncher.getBottomGestureStartOnScreen())
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
|
|
* sure the icon is visible.
|
|
*
|
|
* @param appName name of the app.
|
|
* @return The app.
|
|
*/
|
|
@NonNull
|
|
public AppIcon getAppIcon(String appName) {
|
|
AppIcon appIcon = tryGetAppIcon(appName);
|
|
mLauncher.assertNotNull("Unable to scroll to a clickable icon: " + appName, appIcon);
|
|
// appIcon.getAppName() checks for content description, so it is possible that it can have
|
|
// trailing words. So check if the content description contains the appName.
|
|
mLauncher.assertTrue("Wrong app icon name.", appIcon.getAppName().contains(appName));
|
|
return appIcon;
|
|
}
|
|
|
|
@NonNull
|
|
protected abstract AppIcon createAppIcon(UiObject2 icon);
|
|
|
|
protected abstract boolean hasSearchBox();
|
|
|
|
protected abstract int getAppsListRecyclerTopPadding();
|
|
|
|
protected int getAppsListRecyclerBottomPadding() {
|
|
return mLauncher.getTestInfo(TestProtocol.REQUEST_ALL_APPS_BOTTOM_PADDING)
|
|
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
|
|
}
|
|
|
|
private void scrollBackToBeginning() {
|
|
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"want to scroll back in all apps")) {
|
|
LauncherInstrumentation.log("Scrolling to the beginning");
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
|
|
int attempts = 0;
|
|
final Rect margins = new Rect(
|
|
/* left= */ 0,
|
|
mHeight / 2,
|
|
/* right= */ 0,
|
|
/* bottom= */ getAppsListRecyclerBottomPadding());
|
|
|
|
for (int scroll = getAllAppsScroll();
|
|
scroll != 0;
|
|
scroll = getAllAppsScroll()) {
|
|
mLauncher.assertTrue("Negative scroll position", scroll > 0);
|
|
|
|
mLauncher.assertTrue(
|
|
"Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
|
|
++attempts <= MAX_SCROLL_ATTEMPTS);
|
|
|
|
mLauncher.scroll(
|
|
allAppsContainer,
|
|
Direction.UP,
|
|
margins,
|
|
/* steps= */ 12,
|
|
/* slowDown= */ false);
|
|
}
|
|
|
|
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
|
|
verifyActiveContainer();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected abstract int getAllAppsScroll();
|
|
|
|
protected UiObject2 getAppListRecycler(UiObject2 allAppsContainer) {
|
|
return mLauncher.waitForObjectInContainer(allAppsContainer, "apps_list_view");
|
|
}
|
|
|
|
protected UiObject2 getAllAppsHeader(UiObject2 allAppsContainer) {
|
|
return mLauncher.waitForObjectInContainer(allAppsContainer, "all_apps_header");
|
|
}
|
|
|
|
protected UiObject2 getSearchBox(UiObject2 allAppsContainer) {
|
|
return mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
|
|
}
|
|
|
|
/**
|
|
* Flings forward (down) and waits the fling's end.
|
|
*/
|
|
public void flingForward() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c =
|
|
mLauncher.addContextLayer("want to fling forward in all apps")) {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
// Start the gesture in the center to avoid starting at elements near the top.
|
|
mLauncher.scroll(
|
|
allAppsContainer,
|
|
Direction.DOWN,
|
|
new Rect(0, 0, 0, mHeight / 2),
|
|
/* steps= */ 10,
|
|
/* slowDown= */ false);
|
|
verifyActiveContainer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flings backward (up) and waits the fling's end.
|
|
*/
|
|
public void flingBackward() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
|
|
LauncherInstrumentation.Closable c =
|
|
mLauncher.addContextLayer("want to fling backward in all apps")) {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
// Start the gesture in the center, for symmetry with forward.
|
|
mLauncher.scroll(
|
|
allAppsContainer,
|
|
Direction.UP,
|
|
new Rect(0, mHeight / 2, 0, 0),
|
|
/* steps= */ 10,
|
|
/*slowDown= */ false);
|
|
verifyActiveContainer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Freezes updating app list upon app install/uninstall/update.
|
|
*/
|
|
public void freeze() {
|
|
mLauncher.getTestInfo(TestProtocol.REQUEST_FREEZE_APP_LIST);
|
|
}
|
|
|
|
/**
|
|
* Resumes updating app list upon app install/uninstall/update.
|
|
*/
|
|
public void unfreeze() {
|
|
mLauncher.getTestInfo(TestProtocol.REQUEST_UNFREEZE_APP_LIST);
|
|
}
|
|
|
|
private void verifyNotFrozen(String message) {
|
|
mLauncher.assertEquals(message, 0, getFreezeFlags() & DEFER_UPDATES_TEST);
|
|
mLauncher.assertTrue(message, mLauncher.waitAndGet(() -> getFreezeFlags() == 0,
|
|
WAIT_TIME_MS, DEFAULT_POLL_INTERVAL));
|
|
}
|
|
|
|
private int getFreezeFlags() {
|
|
final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
|
|
return testInfo == null ? 0 : testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
touchOutside(tapRight, container);
|
|
try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
|
|
"tapped outside AllApps bottom sheet")) {
|
|
verifyVisibleContainerOnDismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void touchOutside(boolean tapRight, UiObject2 container) {
|
|
mLauncher.touchOutsideContainer(container, tapRight, false);
|
|
}
|
|
|
|
/** Presses the meta keyboard shortcut to dismiss AllApps. */
|
|
public void dismissByKeyboardShortcut() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
|
pressMetaKey();
|
|
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
|
|
"pressed meta key")) {
|
|
verifyVisibleContainerOnDismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void pressMetaKey() {
|
|
mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
|
|
}
|
|
|
|
/** Presses the esc key to dismiss AllApps. */
|
|
public void dismissByEscKey() {
|
|
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
|
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")) {
|
|
verifyVisibleContainerOnDismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns PredictionRow if present in view. */
|
|
@NonNull
|
|
public PredictionRow getPredictionRowView() {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
final UiObject2 allAppsHeader = getAllAppsHeader(allAppsContainer);
|
|
return new PredictionRow(mLauncher, allAppsHeader);
|
|
}
|
|
|
|
/** Returns PrivateSpaceContainer if present in view. */
|
|
@NonNull
|
|
public PrivateSpaceContainer getPrivateSpaceUnlockedView() {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
|
|
return new PrivateSpaceContainer(mLauncher, appListRecycler, this, true);
|
|
}
|
|
|
|
/** Returns PrivateSpaceContainer in locked state, if present in view. */
|
|
@NonNull
|
|
public PrivateSpaceContainer getPrivateSpaceLockedView() {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
|
|
return new PrivateSpaceContainer(mLauncher, appListRecycler, this, false);
|
|
}
|
|
|
|
/**
|
|
* Toggles Lock/Unlock of Private Space, changing the All Apps Ui.
|
|
*/
|
|
public void togglePrivateSpace() {
|
|
final UiObject2 allAppsContainer = verifyActiveContainer();
|
|
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
|
|
UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(appListRecycler,
|
|
UNLOCK_BUTTON_VIEW_RES_ID);
|
|
mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
|
|
mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
|
|
unLockButtonView.click();
|
|
}
|
|
|
|
protected abstract void verifyVisibleContainerOnDismiss();
|
|
|
|
/**
|
|
* Return the QSB UI object on the AllApps screen.
|
|
*
|
|
* @return the QSB UI object.
|
|
*/
|
|
@NonNull
|
|
public abstract Qsb getQsb();
|
|
} |