Merge "TAPL: Verifying some interactions with system" into ub-launcher3-master

This commit is contained in:
Vadim Tryshev 2020-01-15 22:32:13 +00:00 committed by Android (Google) Code Review
commit f7b2d40347
11 changed files with 218 additions and 35 deletions

View File

@ -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));

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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();

View File

@ -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) {

View File

@ -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";

View File

@ -33,6 +33,10 @@ public class AppIconMenuItem extends Launchable {
return mObject.getText();
}
@Override
protected void addExpectedEventsForLongClick() {
}
@Override
protected String getLongPressIndicator() {
return "drop_target_bar";

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}
}