Merge "TAPL: Verifying some interactions with system" into ub-launcher3-master
This commit is contained in:
commit
f7b2d40347
|
@ -100,6 +100,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
|
|||
@PortraitLandscape
|
||||
public void testOverview() throws Exception {
|
||||
startTestApps();
|
||||
// mLauncher.pressHome() also tests an important case of pressing home while in background.
|
||||
Overview overview = mLauncher.pressHome().switchToOverview();
|
||||
assertTrue("Launcher internal state didn't switch to Overview",
|
||||
isInState(LauncherState.OVERVIEW));
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (C) 2020 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.testing;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
|
||||
public final class TestLogging {
|
||||
public static synchronized void recordEvent(String event) {
|
||||
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
|
||||
Log.d(TestProtocol.TAPL_EVENTS_TAG, event);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ public final class TestProtocol {
|
|||
public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
|
||||
public static final int ALL_APPS_STATE_ORDINAL = 5;
|
||||
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
|
||||
public static final String TAPL_EVENTS_TAG = "TaplEvents";
|
||||
|
||||
public static String stateOrdinalToString(int ordinal) {
|
||||
switch (ordinal) {
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.android.launcher3.Launcher;
|
|||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
|
||||
/**
|
||||
* Class to handle long-clicks on workspace items and start drag as a result.
|
||||
|
@ -46,6 +47,7 @@ public class ItemLongClickListener {
|
|||
ItemLongClickListener::onAllAppsItemLongClick;
|
||||
|
||||
private static boolean onWorkspaceItemLongClick(View v) {
|
||||
TestLogging.recordEvent("onWorkspaceItemLongClick");
|
||||
Launcher launcher = Launcher.getLauncher(v.getContext());
|
||||
if (!canStartDrag(launcher)) return false;
|
||||
if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
|
||||
|
@ -75,6 +77,7 @@ public class ItemLongClickListener {
|
|||
}
|
||||
|
||||
private static boolean onAllAppsItemLongClick(View v) {
|
||||
TestLogging.recordEvent("onAllAppsItemLongClick");
|
||||
Launcher launcher = Launcher.getLauncher(v.getContext());
|
||||
if (!canStartDrag(launcher)) return false;
|
||||
// When we have exited all apps or are in transition, disregard long clicks
|
||||
|
|
|
@ -25,7 +25,6 @@ import static com.android.launcher3.LauncherState.NORMAL;
|
|||
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
|
@ -37,10 +36,9 @@ import com.android.launcher3.AbstractFloatingView;
|
|||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.views.OptionsPopupView;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
|
@ -168,6 +166,7 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe
|
|||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent event) {
|
||||
TestLogging.recordEvent("Workspace.longPress");
|
||||
if (mLongPressState == STATE_REQUESTED) {
|
||||
if (canHandleLongPress()) {
|
||||
mLongPressState = STATE_PENDING_PARENT_INFORM;
|
||||
|
@ -178,9 +177,6 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe
|
|||
mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
|
||||
Action.Direction.NONE, ContainerType.WORKSPACE,
|
||||
mWorkspace.getCurrentPage());
|
||||
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
|
||||
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on long press");
|
||||
}
|
||||
OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
|
||||
} else {
|
||||
cancelLongPress();
|
||||
|
|
|
@ -35,6 +35,7 @@ import com.android.launcher3.R;
|
|||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.touch.ItemLongClickListener;
|
||||
import com.android.launcher3.uioverrides.WallpaperColorInfo;
|
||||
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
|
||||
|
@ -90,6 +91,7 @@ abstract class BaseWidgetSheet extends AbstractSlideInView
|
|||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
TestLogging.recordEvent("Widgets.onLongClick");
|
||||
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
|
||||
|
||||
if (v instanceof WidgetCell) {
|
||||
|
|
|
@ -22,10 +22,15 @@ import androidx.test.uiautomator.By;
|
|||
import androidx.test.uiautomator.BySelector;
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* App icon, whether in all apps or in workspace/
|
||||
*/
|
||||
public final class AppIcon extends Launchable {
|
||||
|
||||
private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
|
||||
|
||||
AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
|
||||
super(launcher, icon);
|
||||
}
|
||||
|
@ -42,6 +47,11 @@ public final class AppIcon extends Launchable {
|
|||
mObject, "deep_shortcuts_container"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addExpectedEventsForLongClick() {
|
||||
mLauncher.expectEvent(LONG_CLICK_EVENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLongPressIndicator() {
|
||||
return "deep_shortcuts_container";
|
||||
|
|
|
@ -33,6 +33,10 @@ public class AppIconMenuItem extends Launchable {
|
|||
return mObject.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addExpectedEventsForLongClick() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLongPressIndicator() {
|
||||
return "drop_target_bar";
|
||||
|
|
|
@ -69,18 +69,24 @@ abstract class Launchable {
|
|||
* Drags an object to the center of homescreen.
|
||||
*/
|
||||
public void dragToWorkspace() {
|
||||
final Point launchableCenter = getObject().getVisibleCenter();
|
||||
final Point displaySize = mLauncher.getRealDisplaySize();
|
||||
final int width = displaySize.x / 2;
|
||||
Workspace.dragIconToWorkspace(
|
||||
mLauncher,
|
||||
this,
|
||||
new Point(
|
||||
launchableCenter.x >= width ?
|
||||
launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
|
||||
displaySize.y / 2),
|
||||
getLongPressIndicator());
|
||||
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
|
||||
final Point launchableCenter = getObject().getVisibleCenter();
|
||||
final Point displaySize = mLauncher.getRealDisplaySize();
|
||||
final int width = displaySize.x / 2;
|
||||
addExpectedEventsForLongClick();
|
||||
Workspace.dragIconToWorkspace(
|
||||
mLauncher,
|
||||
this,
|
||||
new Point(
|
||||
launchableCenter.x >= width
|
||||
? launchableCenter.x - width / 2
|
||||
: launchableCenter.x + width / 2,
|
||||
displaySize.y / 2),
|
||||
getLongPressIndicator());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void addExpectedEventsForLongClick();
|
||||
|
||||
protected abstract String getLongPressIndicator();
|
||||
}
|
||||
|
|
|
@ -79,6 +79,8 @@ import java.util.concurrent.TimeoutException;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
@ -92,6 +94,13 @@ public final class LauncherInstrumentation {
|
|||
private static final int GESTURE_STEP_MS = 16;
|
||||
private static long START_TIME = System.currentTimeMillis();
|
||||
|
||||
static final Pattern LOG_TIME = Pattern.compile(
|
||||
"[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9]");
|
||||
|
||||
static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
|
||||
"(?<time>[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
|
||||
+ ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<event>.*)");
|
||||
|
||||
// Types for launcher containers that the user is interacting with. "Background" is a
|
||||
// pseudo-container corresponding to inactive launcher covered by another app.
|
||||
public enum ContainerType {
|
||||
|
@ -146,6 +155,11 @@ public final class LauncherInstrumentation {
|
|||
|
||||
private Consumer<ContainerType> mOnSettledStateAction;
|
||||
|
||||
// Not null when we are collecting expected events to compare with actual ones.
|
||||
private List<Pattern> mExpectedEvents;
|
||||
|
||||
private String mTimeBeforeFirstLogEvent;
|
||||
|
||||
/**
|
||||
* Constructs the root of TAPL hierarchy. You get all other objects from it.
|
||||
*/
|
||||
|
@ -299,8 +313,11 @@ public final class LauncherInstrumentation {
|
|||
public void checkForAnomaly() {
|
||||
final String anomalyMessage = getAnomalyMessage();
|
||||
if (anomalyMessage != null) {
|
||||
failWithSystemHealth(
|
||||
"Tests are broken by a non-Launcher system error: " + anomalyMessage);
|
||||
String message = "Tests are broken by a non-Launcher system error: " + anomalyMessage;
|
||||
log("Hierarchy dump for: " + message);
|
||||
dumpViewHierarchy();
|
||||
|
||||
Assert.fail(formatSystemHealthMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,7 +356,7 @@ public final class LauncherInstrumentation {
|
|||
mOnSettledStateAction = onSettledStateAction;
|
||||
}
|
||||
|
||||
private String getSystemHealthMessage() {
|
||||
private String formatSystemHealthMessage(String message) {
|
||||
final String testPackage = getContext().getPackageName();
|
||||
|
||||
mInstrumentation.getUiAutomation().grantRuntimePermission(
|
||||
|
@ -347,30 +364,34 @@ public final class LauncherInstrumentation {
|
|||
mInstrumentation.getUiAutomation().grantRuntimePermission(
|
||||
testPackage, "android.permission.PACKAGE_USAGE_STATS");
|
||||
|
||||
return mSystemHealthSupplier != null
|
||||
final String systemHealth = mSystemHealthSupplier != null
|
||||
? mSystemHealthSupplier.apply(START_TIME)
|
||||
: TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
|
||||
|
||||
if (systemHealth != null) {
|
||||
return message
|
||||
+ ",\nperhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n"
|
||||
+ systemHealth + "\n>>>>>>>>>>>>>>>>>>";
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private void fail(String message) {
|
||||
checkForAnomaly();
|
||||
|
||||
failWithSystemHealth("http://go/tapl : " + getContextDescription() + message +
|
||||
" (visible state: " + getVisibleStateMessage() + ")");
|
||||
}
|
||||
|
||||
private void failWithSystemHealth(String message) {
|
||||
final String systemHealth = getSystemHealthMessage();
|
||||
if (systemHealth != null) {
|
||||
message = message
|
||||
+ ", perhaps because of system health problems:\n<<<<<<<<<<<<<<<<<<\n"
|
||||
+ systemHealth + "\n>>>>>>>>>>>>>>>>>>";
|
||||
}
|
||||
|
||||
message = "http://go/tapl : " + getContextDescription() + message
|
||||
+ " (visible state: " + getVisibleStateMessage() + ")";
|
||||
log("Hierarchy dump for: " + message);
|
||||
dumpViewHierarchy();
|
||||
|
||||
Assert.fail(message);
|
||||
final String eventMismatch = getEventMismatchMessage();
|
||||
|
||||
if (eventMismatch != null) {
|
||||
message = message + ",\nhaving produced wrong events:\n " + eventMismatch;
|
||||
}
|
||||
|
||||
Assert.fail(formatSystemHealthMessage(message));
|
||||
}
|
||||
|
||||
private String getContextDescription() {
|
||||
|
@ -582,7 +603,7 @@ public final class LauncherInstrumentation {
|
|||
try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
|
||||
mDevice.waitForIdle();
|
||||
runToState(
|
||||
() -> waitForSystemUiObject("home").click(),
|
||||
waitForSystemUiObject("home")::click,
|
||||
NORMAL_STATE_ORDINAL,
|
||||
!hasLauncherObject(WORKSPACE_RES_ID)
|
||||
&& (hasLauncherObject(APPS_RES_ID)
|
||||
|
@ -1099,4 +1120,104 @@ public final class LauncherInstrumentation {
|
|||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private List<String> getEvents() {
|
||||
final ArrayList<String> events = new ArrayList<>();
|
||||
try {
|
||||
final String logcatTimeParameter =
|
||||
mTimeBeforeFirstLogEvent != null ? " -t " + mTimeBeforeFirstLogEvent : "";
|
||||
final String logcatEvents = mDevice.executeShellCommand(
|
||||
"logcat -d --pid=" + getPid() + logcatTimeParameter
|
||||
+ " -s " + TestProtocol.TAPL_EVENTS_TAG);
|
||||
final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
|
||||
while (matcher.find()) {
|
||||
final String eventTime = matcher.group("time");
|
||||
if (eventTime.equals(mTimeBeforeFirstLogEvent)) continue;
|
||||
|
||||
events.add(matcher.group("event"));
|
||||
}
|
||||
return events;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecordingEvents() {
|
||||
Assert.assertTrue("Already recording events", mExpectedEvents == null);
|
||||
mExpectedEvents = new ArrayList<>();
|
||||
|
||||
try {
|
||||
final String lastLogLine =
|
||||
mDevice.executeShellCommand("logcat -d --pid=" + getPid() + " -t 1");
|
||||
final Matcher matcher = LOG_TIME.matcher(lastLogLine);
|
||||
mTimeBeforeFirstLogEvent = matcher.find() ? matcher.group().replaceAll(" ", "") : null;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopRecordingEvents() {
|
||||
mExpectedEvents = null;
|
||||
}
|
||||
|
||||
Closable eventsCheck() {
|
||||
// Entering events check block.
|
||||
startRecordingEvents();
|
||||
|
||||
return () -> {
|
||||
// Leaving events check block.
|
||||
if (mExpectedEvents == null) {
|
||||
return; // There was a failure. Noo need to report another one.
|
||||
}
|
||||
|
||||
// Wait until Launcher generates expected number of events.
|
||||
final long endTime = SystemClock.uptimeMillis() + WAIT_TIME_MS;
|
||||
while (SystemClock.uptimeMillis() < endTime
|
||||
&& getEvents().size() < mExpectedEvents.size()) {
|
||||
SystemClock.sleep(100);
|
||||
}
|
||||
|
||||
final String message = getEventMismatchMessage();
|
||||
if (message != null) {
|
||||
Assert.fail(formatSystemHealthMessage(
|
||||
"http://go/tapl : unexpected event sequence: " + message));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void expectEvent(Pattern expected) {
|
||||
if (mExpectedEvents != null) mExpectedEvents.add(expected);
|
||||
}
|
||||
|
||||
private String getEventMismatchMessage() {
|
||||
if (mExpectedEvents == null) return null;
|
||||
|
||||
try {
|
||||
final List<String> actual = getEvents();
|
||||
|
||||
for (int i = 0; i < mExpectedEvents.size(); ++i) {
|
||||
if (i >= actual.size()) {
|
||||
return formatEventMismatchMessage("too few actual events", actual, i);
|
||||
}
|
||||
if (!mExpectedEvents.get(i).matcher(actual.get(i)).find()) {
|
||||
return formatEventMismatchMessage("mismatched event", actual, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (actual.size() > mExpectedEvents.size()) {
|
||||
return formatEventMismatchMessage(
|
||||
"too many actual events", actual, mExpectedEvents.size());
|
||||
}
|
||||
} finally {
|
||||
stopRecordingEvents();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String formatEventMismatchMessage(String message, List<String> actual, int position) {
|
||||
return message + ", pos=" + position
|
||||
+ ", expected=" + mExpectedEvents
|
||||
+ ", actual" + actual;
|
||||
}
|
||||
}
|
|
@ -18,10 +18,15 @@ package com.android.launcher3.tapl;
|
|||
|
||||
import androidx.test.uiautomator.UiObject2;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Widget in workspace or a widget list.
|
||||
*/
|
||||
public final class Widget extends Launchable {
|
||||
|
||||
private static final Pattern LONG_CLICK_EVENT = Pattern.compile("Widgets.onLongClick");
|
||||
|
||||
Widget(LauncherInstrumentation launcher, UiObject2 icon) {
|
||||
super(launcher, icon);
|
||||
}
|
||||
|
@ -30,4 +35,9 @@ public final class Widget extends Launchable {
|
|||
protected String getLongPressIndicator() {
|
||||
return "drop_target_bar";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addExpectedEventsForLongClick() {
|
||||
mLauncher.expectEvent(LONG_CLICK_EVENT);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue