Files
Lawnchair/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
T
vadimt d4c90e12e9 Freezing all apps updates during certain tests
This CL adds a very low risk because most (but not all) changes affect
only Launcher behavior during the test.

This should fix a lab-only flake when all apps keeps changing while
the test is working with it.

Example: test figures out which icon to click, by the moment it clicks
there, there is another icon there, or the icon is under the search box,
and clicking opens IME.

Switching test devices to airplane mode didn't help. The earlier change
that prevents popup menu cancellation is not general enough.

Now the tests are given an API to explicitly freeze and unfreeze
all-apps, which should be a final solution.

Bug: 132900132
Bug: 133765434
Change-Id: I8b81cc9be004482beb6cdcdd05406e2d9b4c7629
2019-06-06 11:10:56 -07:00

812 lines
30 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.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 com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Configurator;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import com.android.launcher3.testing.TestProtocol;
import com.android.systemui.shared.system.QuickStepContract;
import org.junit.Assert;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
* produces all other objects.
*/
public final class LauncherInstrumentation {
private static final String TAG = "Tapl";
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
private static final int GESTURE_STEP_MS = 16;
// Types for launcher containers that the user is interacting with. "Background" is a
// pseudo-container corresponding to inactive launcher covered by another app.
enum ContainerType {
WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
}
public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
// Base class for launcher containers.
static abstract class VisibleContainer {
protected final LauncherInstrumentation mLauncher;
protected VisibleContainer(LauncherInstrumentation launcher) {
mLauncher = launcher;
launcher.setActiveContainer(this);
}
protected abstract ContainerType getContainerType();
/**
* Asserts that the launcher is in the mode matching 'this' object.
*
* @return UI object for the container.
*/
final UiObject2 verifyActiveContainer() {
mLauncher.assertTrue("Attempt to use a stale container",
this == sActiveContainer.get());
return mLauncher.verifyContainerType(getContainerType());
}
}
interface Closable extends AutoCloseable {
void close();
}
private static final String WORKSPACE_RES_ID = "workspace";
private static final String APPS_RES_ID = "apps_view";
private static final String OVERVIEW_RES_ID = "overview_panel";
private static final String WIDGETS_RES_ID = "widgets_list_view";
public static final int WAIT_TIME_MS = 10000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
private final UiDevice mDevice;
private final Instrumentation mInstrumentation;
private int mExpectedRotation = Surface.ROTATION_0;
private final Uri mTestProviderUri;
private final Deque<String> mDiagnosticContext = new LinkedList<>();
/**
* Constructs the root of TAPL hierarchy. You get all other objects from it.
*/
public LauncherInstrumentation(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
// Launcher should run in test harness so that custom accessibility protocol between
// Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
// into Launcher.
assertTrue("Device must run in a test harness",
TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
final String testPackage = getContext().getPackageName();
final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
// Launcher package. As during inproc tests the tested launcher may not be selected as the
// current launcher, choosing target package for inproc. For out-of-proc, use the installed
// launcher package.
final String authorityPackage = testPackage.equals(targetPackage) ?
getLauncherPackageName() :
targetPackage;
String testProviderAuthority = authorityPackage + ".TestInfo";
mTestProviderUri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(testProviderAuthority)
.build();
try {
mDevice.executeShellCommand("pm grant " + testPackage +
" android.permission.WRITE_SECURE_SETTINGS");
} catch (IOException e) {
fail(e.toString());
}
PackageManager pm = getContext().getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(
testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS);
ComponentName cn = new ComponentName(pi.packageName, pi.name);
if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
if (TestHelpers.isInLauncherProcess()) {
getContext().getPackageManager().setComponentEnabledSetting(
cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
} else {
try {
mDevice.executeShellCommand("pm enable " + cn.flattenToString());
} catch (IOException e) {
fail(e.toString());
}
}
}
}
Context getContext() {
return mInstrumentation.getContext();
}
Bundle getTestInfo(String request) {
return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
}
void setActiveContainer(VisibleContainer container) {
sActiveContainer = new WeakReference<>(container);
}
public NavigationModel getNavigationModel() {
final Context baseContext = mInstrumentation.getTargetContext();
try {
// Workaround, use constructed context because both the instrumentation context and the
// app context are not constructed with resources that take overlays into account
final Context ctx = baseContext.createPackageContext("android", 0);
for (int i = 0; i < 100; ++i) {
log("Interaction mode = " + getCurrentInteractionMode(ctx));
if (isGesturalMode(ctx)) {
return NavigationModel.ZERO_BUTTON;
} else if (isSwipeUpMode(ctx)) {
return NavigationModel.TWO_BUTTON;
} else if (isLegacyMode(ctx)) {
return NavigationModel.THREE_BUTTON;
}
Thread.sleep(100);
}
fail("Can't detect navigation mode");
} catch (Exception e) {
fail(e.toString());
}
return NavigationModel.THREE_BUTTON;
}
public static boolean isAvd() {
return Build.MODEL.contains("Cuttlefish");
}
static void log(String message) {
Log.d(TAG, message);
}
Closable addContextLayer(String piece) {
mDiagnosticContext.addLast(piece);
log("Added context: " + getContextDescription());
return () -> {
log("Removing context: " + getContextDescription());
mDiagnosticContext.removeLast();
};
}
private void dumpViewHierarchy() {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
mDevice.dumpWindowHierarchy(stream);
stream.flush();
stream.close();
for (String line : stream.toString().split("\\r?\\n")) {
Log.e(TAG, line.trim());
}
} catch (IOException e) {
Log.e(TAG, "error dumping XML to logcat", e);
}
}
private void fail(String message) {
log("Hierarchy dump for: " + getContextDescription() + message);
dumpViewHierarchy();
Assert.fail("http://go/tapl : " + getContextDescription() + message);
}
private String getContextDescription() {
return mDiagnosticContext.isEmpty() ? "" : String.join(", ", mDiagnosticContext) + "; ";
}
void assertTrue(String message, boolean condition) {
if (!condition) {
fail(message);
}
}
void assertNotNull(String message, Object object) {
assertTrue(message, object != null);
}
private void failEquals(String message, Object actual) {
fail(message + ". " + "Actual: " + actual);
}
private void assertEquals(String message, int expected, int actual) {
if (expected != actual) {
fail(message + " expected: " + expected + " but was: " + actual);
}
}
private void assertEquals(String message, String expected, String actual) {
if (!TextUtils.equals(expected, actual)) {
fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
}
}
void assertEquals(String message, long expected, long actual) {
if (expected != actual) {
fail(message + " expected: " + expected + " but was: " + actual);
}
}
void assertNotEquals(String message, int unexpected, int actual) {
if (unexpected == actual) {
failEquals(message, actual);
}
}
public void setExpectedRotation(int expectedRotation) {
mExpectedRotation = expectedRotation;
}
private UiObject2 verifyContainerType(ContainerType containerType) {
assertEquals("Unexpected display rotation",
mExpectedRotation, mDevice.getDisplayRotation());
final NavigationModel navigationModel = getNavigationModel();
final boolean hasRecentsButton = hasSystemUiObject("recent_apps");
final boolean hasHomeButton = hasSystemUiObject("home");
assertTrue("Presence of recents button doesn't match the interaction mode, mode="
+ navigationModel.name() + ", hasRecents=" + hasRecentsButton,
(navigationModel == NavigationModel.THREE_BUTTON) == hasRecentsButton);
assertTrue("Presence of home button doesn't match the interaction mode, mode="
+ navigationModel.name() + ", hasHome=" + hasHomeButton,
(navigationModel != NavigationModel.ZERO_BUTTON) == hasHomeButton);
log("verifyContainerType: " + containerType);
try (Closable c = addContextLayer(
"but the current state is not " + containerType.name())) {
switch (containerType) {
case WORKSPACE: {
waitForLauncherObject(APPS_RES_ID);
waitUntilGone(OVERVIEW_RES_ID);
waitUntilGone(WIDGETS_RES_ID);
return waitForLauncherObject(WORKSPACE_RES_ID);
}
case WIDGETS: {
waitUntilGone(WORKSPACE_RES_ID);
waitUntilGone(APPS_RES_ID);
waitUntilGone(OVERVIEW_RES_ID);
return waitForLauncherObject(WIDGETS_RES_ID);
}
case ALL_APPS: {
waitUntilGone(WORKSPACE_RES_ID);
waitUntilGone(OVERVIEW_RES_ID);
waitUntilGone(WIDGETS_RES_ID);
return waitForLauncherObject(APPS_RES_ID);
}
case OVERVIEW: {
if (mDevice.isNaturalOrientation()) {
waitForLauncherObject(APPS_RES_ID);
} else {
waitUntilGone(APPS_RES_ID);
}
waitUntilGone(WORKSPACE_RES_ID);
waitUntilGone(WIDGETS_RES_ID);
return waitForLauncherObject(OVERVIEW_RES_ID);
}
case BASE_OVERVIEW: {
return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
}
case BACKGROUND: {
waitUntilGone(WORKSPACE_RES_ID);
waitUntilGone(APPS_RES_ID);
waitUntilGone(OVERVIEW_RES_ID);
waitUntilGone(WIDGETS_RES_ID);
return null;
}
default:
fail("Invalid state: " + containerType);
return null;
}
}
}
Parcelable executeAndWaitForEvent(Runnable command,
UiAutomation.AccessibilityEventFilter eventFilter, String message) {
try {
final AccessibilityEvent event =
mInstrumentation.getUiAutomation().executeAndWaitForEvent(
command, eventFilter, WAIT_TIME_MS);
assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
return event.getParcelableData();
} catch (TimeoutException e) {
fail(message);
return null;
}
}
Bundle getAnswerFromLauncher(UiObject2 view, String requestTag) {
// Send a fake set-text request to Launcher to initiate a response with requested data.
final String responseTag = requestTag + TestProtocol.RESPONSE_MESSAGE_POSTFIX;
return (Bundle) executeAndWaitForEvent(
() -> view.setText(requestTag),
event -> responseTag.equals(event.getClassName()),
"Launcher didn't respond to request: " + requestTag);
}
/**
* Presses nav bar home button.
*
* @return the Workspace object.
*/
public Workspace pressHome() {
// Click home, then wait for any accessibility event, then wait until accessibility events
// stop.
// We need waiting for any accessibility event generated after pressing Home because
// otherwise waitForIdle may return immediately in case when there was a big enough pause in
// accessibility events prior to pressing Home.
final String action;
if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
final Point displaySize = getRealDisplaySize();
if (hasLauncherObject("deep_shortcuts_container")) {
linearGesture(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
assertTrue("Context menu is still visible afterswiping up to home",
!hasLauncherObject("deep_shortcuts_container"));
}
if (hasLauncherObject(WORKSPACE_RES_ID)) {
log(action = "already at home");
} else {
log(action = "swiping up to home");
final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName()))
? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
swipeToState(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState);
}
} else {
log(action = "clicking home button");
executeAndWaitForEvent(
() -> {
log("LauncherInstrumentation.pressHome before clicking");
waitForSystemUiObject("home").click();
},
event -> true,
"Pressing Home didn't produce any events");
mDevice.waitForIdle();
}
try (LauncherInstrumentation.Closable c = addContextLayer(
"performed action to switch to Home - " + action)) {
return getWorkspace();
}
}
/**
* Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
* launcher is not in that state.
*
* @return Workspace object.
*/
@NonNull
public Workspace getWorkspace() {
try (LauncherInstrumentation.Closable c = addContextLayer("want to get workspace object")) {
return new Workspace(this);
}
}
/**
* Gets the Workspace object if the current state is "background home", i.e. some other app is
* active. Fails if the launcher is not in that state.
*
* @return Background object.
*/
@NonNull
public Background getBackground() {
return new Background(this);
}
/**
* Gets the Widgets object if the current state is showing all widgets. Fails if the launcher is
* not in that state.
*
* @return Widgets object.
*/
@NonNull
public Widgets getAllWidgets() {
try (LauncherInstrumentation.Closable c = addContextLayer("want to get widgets")) {
return new Widgets(this);
}
}
/**
* Gets the Overview object if the current state is showing the overview panel. Fails if the
* launcher is not in that state.
*
* @return Overview object.
*/
@NonNull
public Overview getOverview() {
try (LauncherInstrumentation.Closable c = addContextLayer("want to get overview")) {
return new Overview(this);
}
}
/**
* Gets the Base overview object if either Launcher is in overview state or the fallback
* overview activity is showing. Fails otherwise.
*
* @return BaseOverview object.
*/
@NonNull
public BaseOverview getBaseOverview() {
return new BaseOverview(this);
}
/**
* Gets the All Apps object if the current state is showing the all apps panel opened by swiping
* from workspace. Fails if the launcher is not in that state. Please don't call this method if
* App Apps was opened by swiping up from Overview, as it won't fail and will return an
* incorrect object.
*
* @return All Aps object.
*/
@NonNull
public AllApps getAllApps() {
try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
return new AllApps(this);
}
}
/**
* Gets the All Apps object if the current state is showing the all apps panel opened by swiping
* from overview. Fails if the launcher is not in that state. Please don't call this method if
* App Apps was opened by swiping up from home, as it won't fail and will return an
* incorrect object.
*
* @return All Aps object.
*/
@NonNull
public AllAppsFromOverview getAllAppsFromOverview() {
try (LauncherInstrumentation.Closable c = addContextLayer("want to get all apps object")) {
return new AllAppsFromOverview(this);
}
}
void waitUntilGone(String resId) {
assertTrue("Unexpected launcher object visible: " + resId,
mDevice.wait(Until.gone(getLauncherObjectSelector(resId)),
WAIT_TIME_MS));
}
private boolean hasSystemUiObject(String resId) {
return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
}
@NonNull
UiObject2 waitForSystemUiObject(String resId) {
final UiObject2 object = mDevice.wait(
Until.findObject(By.res(SYSTEMUI_PACKAGE, resId)), WAIT_TIME_MS);
assertNotNull("Can't find a systemui object with id: " + resId, object);
return object;
}
@NonNull
UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
final UiObject2 object = container.findObject(selector);
assertNotNull("Can't find an object with selector: " + selector, object);
return object;
}
@NonNull
List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
return container.findObjects(getLauncherObjectSelector(resName));
}
@NonNull
UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
final UiObject2 object = container.wait(
Until.findObject(getLauncherObjectSelector(resName)),
WAIT_TIME_MS);
assertNotNull("Can't find a launcher object id: " + resName + " in container: " +
container.getResourceName(), object);
return object;
}
@NonNull
UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
final UiObject2 object = container.wait(
Until.findObject(selector),
WAIT_TIME_MS);
assertNotNull("Can't find a launcher object id: " + selector + " in container: " +
container.getResourceName(), object);
return object;
}
@Nullable
private boolean hasLauncherObject(String resId) {
return mDevice.hasObject(getLauncherObjectSelector(resId));
}
@NonNull
UiObject2 waitForLauncherObject(String resName) {
return waitForObjectBySelector(getLauncherObjectSelector(resName));
}
@NonNull
UiObject2 waitForFallbackLauncherObject(String resName) {
return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName));
}
private UiObject2 waitForObjectBySelector(BySelector selector) {
final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
assertNotNull("Can't find a launcher object; selector: " + selector, object);
return object;
}
BySelector getLauncherObjectSelector(String resName) {
return By.res(getLauncherPackageName(), resName);
}
BySelector getFallbackLauncherObjectSelector(String resName) {
return By.res(getOverviewPackageName(), resName);
}
String getLauncherPackageName() {
return mDevice.getLauncherPackageName();
}
@NonNull
public UiDevice getDevice() {
return mDevice;
}
void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) {
final Bundle parcel = (Bundle) executeAndWaitForEvent(
() -> linearGesture(startX, startY, endX, endY, steps),
event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
"Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
+ ", " + endX + ", " + endY);
assertEquals("Swipe switched launcher to a wrong state;",
TestProtocol.stateOrdinalToString(expectedState),
TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
}
void scroll(UiObject2 container, Direction direction, float percent, Rect margins, int steps) {
final Rect rect = container.getVisibleBounds();
if (margins != null) {
rect.left += margins.left;
rect.top += margins.top;
rect.right -= margins.right;
rect.bottom -= margins.bottom;
}
final int startX;
final int startY;
final int endX;
final int endY;
switch (direction) {
case UP: {
startX = endX = rect.centerX();
final int vertCenter = rect.centerY();
final float halfGestureHeight = rect.height() * percent / 2.0f;
startY = (int) (vertCenter - halfGestureHeight);
endY = (int) (vertCenter + halfGestureHeight);
}
break;
case DOWN: {
startX = endX = rect.centerX();
final int vertCenter = rect.centerY();
final float halfGestureHeight = rect.height() * percent / 2.0f;
startY = (int) (vertCenter + halfGestureHeight);
endY = (int) (vertCenter - halfGestureHeight);
}
break;
default:
fail("Unsupported direction");
return;
}
executeAndWaitForEvent(
() -> linearGesture(startX, startY, endX, endY, steps),
event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
"Didn't receive a scroll end message: " + startX + ", " + startY
+ ", " + endX + ", " + endY);
}
// Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
// fixed interval each time.
private void linearGesture(int startX, int startY, int endX, int endY, int steps) {
final long downTime = SystemClock.uptimeMillis();
final Point start = new Point(startX, startY);
final Point end = new Point(endX, endY);
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
final long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end);
sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end);
}
void waitForIdle() {
mDevice.waitForIdle();
}
float getDisplayDensity() {
return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
}
int getTouchSlop() {
return ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
public Resources getResources() {
return getContext().getResources();
}
private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
float x, float y) {
MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
properties.id = 0;
properties.toolType = Configurator.getInstance().getToolType();
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.pressure = 1;
coords.size = 1;
coords.x = x;
coords.y = y;
return MotionEvent.obtain(downTime, eventTime, action, 1,
new MotionEvent.PointerProperties[]{properties},
new MotionEvent.PointerCoords[]{coords},
0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
}
void sendPointer(long downTime, long currentTime, int action, Point point) {
final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
mInstrumentation.getUiAutomation().injectInputEvent(event, true);
event.recycle();
}
long movePointer(long downTime, long startTime, long duration, Point from, Point to) {
final Point point = new Point();
long steps = duration / GESTURE_STEP_MS;
long currentTime = startTime;
for (long i = 0; i < steps; ++i) {
sleep(GESTURE_STEP_MS);
currentTime += GESTURE_STEP_MS;
final float progress = (currentTime - startTime) / (float) duration;
point.x = from.x + (int) (progress * (to.x - from.x));
point.y = from.y + (int) (progress * (to.y - from.y));
sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point);
}
return currentTime;
}
public static boolean isGesturalMode(Context context) {
return QuickStepContract.isGesturalMode(getCurrentInteractionMode(context));
}
public static boolean isSwipeUpMode(Context context) {
return QuickStepContract.isSwipeUpMode(getCurrentInteractionMode(context));
}
public static boolean isLegacyMode(Context context) {
return QuickStepContract.isLegacyMode(getCurrentInteractionMode(context));
}
private static int getCurrentInteractionMode(Context context) {
return getSystemIntegerRes(context, "config_navBarInteractionMode");
}
private static int getSystemIntegerRes(Context context, String resName) {
Resources res = context.getResources();
int resId = res.getIdentifier(resName, "integer", "android");
if (resId != 0) {
return res.getInteger(resId);
} else {
Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
return -1;
}
}
private static int getSystemDimensionResId(Context context, String resName) {
Resources res = context.getResources();
int resId = res.getIdentifier(resName, "dimen", "android");
if (resId != 0) {
return resId;
} else {
Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
return -1;
}
}
static void sleep(int duration) {
SystemClock.sleep(duration);
}
int getEdgeSensitivityWidth() {
try {
final Context context = mInstrumentation.getTargetContext().createPackageContext(
"android", 0);
return context.getResources().getDimensionPixelSize(
getSystemDimensionResId(context, "config_backGestureInset")) + 1;
} catch (PackageManager.NameNotFoundException e) {
fail("Can't get edge sensitivity: " + e);
return 0;
}
}
Point getRealDisplaySize() {
final Point size = new Point();
getContext().getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(size);
return size;
}
}