Merge "Initial implementation for TAPL support for gestural navigation" into ub-launcher3-master
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index d39a38e..fbeb3a2 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.MotionEvent;
 import android.widget.TextView;
 
@@ -41,10 +42,12 @@
      */
     public AppIconMenu openMenu() {
         final Point iconCenter = mObject.getVisibleCenter();
-        mLauncher.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+        final long downTime = SystemClock.uptimeMillis();
+        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, iconCenter);
         final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
                 "deep_shortcuts_container");
-        mLauncher.sendPointer(MotionEvent.ACTION_UP, iconCenter);
+        mLauncher.sendPointer(
+                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter);
         return new AppIconMenu(mLauncher, deepShortcutsContainer);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 2cde8ec..ef509ed 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -22,6 +22,10 @@
 
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
@@ -33,6 +37,8 @@
  * indicate Launcher as long as Launcher is not in Overview state.
  */
 public class Background extends LauncherInstrumentation.VisibleContainer {
+    private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+    private static final int ZERO_BUTTON_SWIPE_UP_HOLD_DURATION = 400;
 
     Background(LauncherInstrumentation launcher) {
         super(launcher);
@@ -59,19 +65,41 @@
     }
 
     protected void goToOverviewUnchecked(int expectedState) {
-        if (mLauncher.isSwipeUpEnabled()) {
-            final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
-            final int startY = getSwipeStartY();
-            final int swipeHeight = mLauncher.getTestInfo(
-                    getSwipeHeightRequestName()).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        switch (mLauncher.getNavigationModel()) {
+            case ZERO_BUTTON: {
+                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                final int startY = getSwipeStartY();
+                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+                final Point start = new Point(centerX, startY);
+                final Point end =
+                        new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
 
-            mLauncher.swipe(
-                    centerX, startY, centerX,
-                    startY - swipeHeight - mLauncher.getTouchSlop(),
-                    expectedState);
-        } else {
-            mLauncher.waitForSystemUiObject("recent_apps").click();
+                final long downTime = SystemClock.uptimeMillis();
+                mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
+                mLauncher.movePointer(downTime, ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION, start, end);
+                LauncherInstrumentation.sleep(ZERO_BUTTON_SWIPE_UP_HOLD_DURATION);
+                mLauncher.sendPointer(
+                        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end);
+                break;
+            }
+
+            case TWO_BUTTON: {
+                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                final int startY = getSwipeStartY();
+                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
+                mLauncher.swipe(
+                        centerX, startY, centerX,
+                        startY - swipeHeight - mLauncher.getTouchSlop(),
+                        expectedState);
+                break;
+            }
+
+            case THREE_BUTTON:
+                mLauncher.waitForSystemUiObject("recent_apps").click();
+                break;
         }
     }
 
@@ -80,6 +108,6 @@
     }
 
     protected int getSwipeStartY() {
-        return mLauncher.waitForSystemUiObject("home").getVisibleBounds().centerY();
+        return mLauncher.waitForSystemUiObject("navigation_bar_frame").getVisibleBounds().centerY();
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 6e92dad..8cf1262 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -44,8 +44,10 @@
      * Flings forward (left) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 overview = verifyActiveContainer();
         LauncherInstrumentation.log("Overview.flingForward before fling");
+        final UiObject2 overview = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        overview.setGestureMargins(margin, 0, 0, 0);
         overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
@@ -71,8 +73,10 @@
      * Flings backward (right) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 overview = verifyActiveContainer();
         LauncherInstrumentation.log("Overview.flingBackward before fling");
+        final UiObject2 overview = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        overview.setGestureMargins(0, 0, margin, 0);
         overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index e3850ff..f44022b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
 
 import android.app.ActivityManager;
@@ -30,14 +31,17 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.ViewConfiguration;
 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.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
@@ -59,6 +63,7 @@
 public final class LauncherInstrumentation {
 
     private static final String TAG = "Tapl";
+    private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -66,6 +71,8 @@
         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;
@@ -150,7 +157,11 @@
         sActiveContainer = new WeakReference<>(container);
     }
 
-    public boolean isSwipeUpEnabled() {
+    public NavigationModel getNavigationModel() {
+        return isSwipeUpEnabled() ? NavigationModel.TWO_BUTTON : NavigationModel.THREE_BUTTON;
+    }
+
+    private boolean isSwipeUpEnabled() {
         final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
         return SwipeUpSetting.isSwipeUpSettingAvailable() ?
                 Settings.Secure.getInt(
@@ -286,24 +297,49 @@
         // 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.
-        executeAndWaitForEvent(
-                () -> {
-                    log("LauncherInstrumentation.pressHome before clicking");
-                    waitForSystemUiObject("home").click();
-                },
-                event -> true,
-                "Pressing Home didn't produce any events");
-        mDevice.waitForIdle();
+        if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+            if (hasLauncherObject(WORKSPACE_RES_ID)) {
+                log("0-button pressHome: already in workspace");
+            } else if (hasLauncherObject(OVERVIEW_RES_ID)) {
+                log("0-button pressHome: overview");
+                mDevice.pressHome();
+            } else if (hasLauncherObject(WIDGETS_RES_ID)) {
+                log("0-button pressHome: widgets");
+                mDevice.pressHome();
+            } else if (hasLauncherObject(APPS_RES_ID)) {
+                log("0-button pressHome: all apps");
+                mDevice.pressHome();
+            } else {
+                log("0-button pressHome: another app");
+                assertTrue("Launcher is visible, don't know how to go home",
+                        !mDevice.hasObject(By.pkg(getLauncherPackageName())));
+                final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
 
-        // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
-        executeAndWaitForEvent(
-                () -> {
-                    log("LauncherInstrumentation.pressHome before clicking");
-                    waitForSystemUiObject("home").click();
-                },
-                event -> true,
-                "Pressing Home didn't produce any events");
-        mDevice.waitForIdle();
+                swipe(
+                        navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+                        navBar.getVisibleBounds().centerX(), 0,
+                        BACKGROUND_APP_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
+            }
+        } else {
+            executeAndWaitForEvent(
+                    () -> {
+                        log("LauncherInstrumentation.pressHome before clicking");
+                        waitForSystemUiObject("home").click();
+                    },
+                    event -> true,
+                    "Pressing Home didn't produce any events");
+            mDevice.waitForIdle();
+
+            // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
+            executeAndWaitForEvent(
+                    () -> {
+                        log("LauncherInstrumentation.pressHome before clicking");
+                        waitForSystemUiObject("home").click();
+                    },
+                    event -> true,
+                    "Pressing Home didn't produce any events");
+            mDevice.waitForIdle();
+        }
         return getWorkspace();
     }
 
@@ -424,6 +460,11 @@
         return object;
     }
 
+    @Nullable
+    private boolean hasLauncherObject(String resId) {
+        return mDevice.hasObject(getLauncherObjectSelector(resId));
+    }
+
     @NonNull
     UiObject2 waitForLauncherObject(String resName) {
         final BySelector selector = getLauncherObjectSelector(resName);
@@ -446,8 +487,12 @@
     }
 
     void swipe(int startX, int startY, int endX, int endY, int expectedState) {
+        swipe(startX, startY, endX, endY, expectedState, 60);
+    }
+
+    void swipe(int startX, int startY, int endX, int endY, int expectedState, int steps) {
         final Bundle parcel = (Bundle) executeAndWaitForEvent(
-                () -> mDevice.swipe(startX, startY, endX, endY, 60),
+                () -> mDevice.swipe(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);
@@ -459,13 +504,6 @@
         mDevice.waitForIdle();
     }
 
-    void sendPointer(int action, Point point) {
-        final MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
-                SystemClock.uptimeMillis(), action, point.x, point.y, 0);
-        mInstrumentation.sendPointerSync(event);
-        event.recycle();
-    }
-
     float getDisplayDensity() {
         return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
     }
@@ -473,4 +511,51 @@
     int getTouchSlop() {
         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
+
+    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();
+    }
+
+    void movePointer(long downTime, long duration, Point from, Point to) {
+        final Point point = new Point();
+        for (; ; ) {
+            sleep(16);
+
+            final long currentTime = SystemClock.uptimeMillis();
+            final float progress = (currentTime - downTime) / (float) duration;
+            if (progress > 1) return;
+
+            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);
+        }
+    }
+
+    static void sleep(int duration) {
+        try {
+            Thread.sleep(duration);
+        } catch (InterruptedException e) {
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index e3ef74a..7d97acd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -136,6 +136,8 @@
      */
     public void flingForward() {
         final UiObject2 workspace = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        workspace.setGestureMargins(0, 0, margin, 0);
         workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
@@ -147,6 +149,8 @@
      */
     public void flingBackward() {
         final UiObject2 workspace = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        workspace.setGestureMargins(margin, 0, 0, 0);
         workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();