Fixing scrolling up in App Apps.

Done by scrolling only when scroll position is not zero. This way, the
scroll gesture can't close All Apps.

Bug: 110103162
Test: TaplTests suite
Change-Id: Icfe47d2bcc0210ae221df169d6c35cd1be10ff86
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7c5bb1a..a851318 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -602,4 +602,8 @@
         msg.setAsynchronous(true);
         handler.sendMessage(msg);
     }
+
+    public interface Consumer<T> {
+        void accept(T var1);
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index fdf32af..40cf0f3 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,6 +21,7 @@
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Process;
 import android.support.animation.DynamicAnimation;
 import android.support.annotation.NonNull;
@@ -48,6 +49,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -549,4 +551,16 @@
                     && verticalFadingEdge);
         }
     }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (AccessibilityManagerCompat.processTestRequest(
+                mLauncher, "TAPL_GET_SCROLL", action, arguments,
+                response ->
+                        response.putInt("scrollY", getActiveRecyclerView().getCurrentScrollY()))) {
+            return true;
+        }
+
+        return super.performAccessibilityAction(action, arguments);
+    }
 }
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 29fc2bc..32fb533 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -18,9 +18,11 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
+import android.os.Bundle;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.launcher3.Utilities;
 
@@ -49,17 +51,60 @@
     }
 
     public static void sendEventToTest(Context context, String eventTag) {
-        if (!Utilities.IS_RUNNING_IN_TEST_HARNESS) return;
+        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+        if (accessibilityManager == null) return;
 
+        sendEventToTest(accessibilityManager, eventTag, null);
+    }
+
+    private static void sendEventToTest(
+            AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
+        final AccessibilityEvent e = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_ANNOUNCEMENT);
+        e.setClassName(eventTag);
+        e.setParcelableData(data);
+        accessibilityManager.sendAccessibilityEvent(e);
+    }
+
+    /**
+     * Returns accessibility manager to be used for communication with UI Automation tests.
+     * The tests may exchange custom accessibility messages with the launcher; the accessibility
+     * manager is used in these communications.
+     *
+     * If the launcher runs not under a test, the return is null, and no attempt to process or send
+     * custom accessibility messages should be made.
+     */
+    private static AccessibilityManager getAccessibilityManagerForTest(Context context) {
+        // If not running in a test harness, don't participate in test exchanges.
+        if (!Utilities.IS_RUNNING_IN_TEST_HARNESS) return null;
+
+        // Additional safety check: when running under UI Automation, accessibility is enabled,
+        // but the list of accessibility services is empty. Return null if this is not the case.
         final AccessibilityManager accessibilityManager = getManager(context);
-        if (accessibilityManager.isEnabled() &&
+        if (!accessibilityManager.isEnabled() ||
                 accessibilityManager.getEnabledAccessibilityServiceList(
-                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK).size() == 0) {
-
-            final AccessibilityEvent e = AccessibilityEvent.obtain(
-                    AccessibilityEvent.TYPE_ANNOUNCEMENT);
-            e.setClassName(eventTag);
-            accessibilityManager.sendAccessibilityEvent(e);
+                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK).size() > 0) {
+            return null;
         }
+
+        return accessibilityManager;
+    }
+
+    public static boolean processTestRequest(Context context, String eventTag, int action,
+            Bundle request, Utilities.Consumer<Bundle> responseFiller) {
+        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+        if (accessibilityManager == null) return false;
+
+        // The test sends a request via a ACTION_SET_TEXT.
+        if (action == AccessibilityNodeInfo.ACTION_SET_TEXT &&
+                eventTag.equals(request.getCharSequence(
+                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE))) {
+            final Bundle response = new Bundle();
+            responseFiller.accept(response);
+            AccessibilityManagerCompat.sendEventToTest(
+                    accessibilityManager, eventTag + "_RESPONSE", response);
+            return true;
+        }
+        return false;
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index d849e2d..e270b46 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -72,28 +72,33 @@
         return new AppIcon(mLauncher, appIcon);
     }
 
-    protected int getBottomMarginForSwipeUp() {
-        return 5;
-    }
-
     private void scrollBackToBeginning() {
         final UiObject2 allAppsContainer = verifyActiveContainer();
+        final UiObject2 searchBox =
+                mLauncher.waitForObjectInContainer(allAppsContainer, "search_container_all_apps");
 
         int attempts = 0;
-        allAppsContainer.setGestureMargins(5, 600, 5, getBottomMarginForSwipeUp());
+        allAppsContainer.setGestureMargins(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
 
-        while (allAppsContainer.scroll(Direction.UP, 0.5f)) {
-            mLauncher.waitForIdle();
-            verifyActiveContainer();
+        for (int scroll = getScroll(allAppsContainer);
+                scroll != 0;
+                scroll = getScroll(allAppsContainer)) {
+            assertTrue("Negative scroll position", scroll > 0);
 
             assertTrue("Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                     ++attempts <= MAX_SCROLL_ATTEMPTS);
+
+            allAppsContainer.scroll(Direction.UP, 1);
         }
 
-        mLauncher.waitForIdle();
         verifyActiveContainer();
     }
 
+    private int getScroll(UiObject2 allAppsContainer) {
+        return mLauncher.getAnswerFromLauncher(allAppsContainer, "TAPL_GET_SCROLL").
+                getInt("scrollY", -1);
+    }
+
     private void ensureIconVisible(UiObject2 appIcon, UiObject2 allAppsContainer) {
         final int appHeight = appIcon.getVisibleBounds().height();
         if (appHeight < MIN_INTERACT_SIZE) {
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
index bc0dfc6..7ed2dc2 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java
@@ -30,11 +30,6 @@
         verifyActiveContainer();
     }
 
-    @Override
-    protected int getBottomMarginForSwipeUp() {
-        return 600;
-    }
-
     /**
      * Swipes down to switch back to Overview whence we came from.
      *
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index fc32fed..0be09f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -23,6 +23,7 @@
 import android.app.ActivityManager;
 import android.app.UiAutomation;
 import android.content.res.Resources;
+import android.os.Bundle;
 import android.provider.Settings;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -32,8 +33,7 @@
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
-
-import org.junit.Assert;
+import android.view.accessibility.AccessibilityEvent;
 
 import java.lang.ref.WeakReference;
 import java.util.concurrent.TimeoutException;
@@ -156,18 +156,30 @@
         }
     }
 
-    private void executeAndWaitForEvent(Runnable command,
+    private Bundle executeAndWaitForEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, String message) {
         try {
-            assertNotNull("executeAndWaitForEvent returned null (this can't happen)",
+            final AccessibilityEvent event =
                     InstrumentationRegistry.getInstrumentation().getUiAutomation()
                             .executeAndWaitForEvent(
-                                    command, eventFilter, WAIT_TIME_MS));
+                                    command, eventFilter, WAIT_TIME_MS);
+            assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
+            return (Bundle) 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 + "_RESPONSE";
+        return executeAndWaitForEvent(
+                () -> view.setText(requestTag),
+                event -> responseTag.equals(event.getClassName()),
+                "Launcher didn't respond to request: " + requestTag);
+    }
+
     /**
      * Presses nav bar home button.
      *