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
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 855535b..67086a5 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -80,6 +80,7 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.PropertyListBuilder;
@@ -2238,8 +2239,9 @@
         }
         mPendingExecutor = executor;
         if (!isInState(ALL_APPS)) {
-            mAppsView.getAppsStore().setDeferUpdates(true);
-            mPendingExecutor.execute(() -> mAppsView.getAppsStore().setDeferUpdates(false));
+            mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
+            mPendingExecutor.execute(() -> mAppsView.getAppsStore().disableDeferUpdates(
+                    AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
         }
 
         executor.attachTo(this);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 0d43e21..d68ff44 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -628,11 +628,12 @@
         final boolean result = super.dispatchTouchEvent(ev);
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
-                if (result) mAllAppsStore.setDeferUpdates(true);
+                if (result) mAllAppsStore.enableDeferUpdates(
+                        AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mAllAppsStore.setDeferUpdates(false);
+                mAllAppsStore.disableDeferUpdates(AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
                 break;
         }
         return result;
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 8e7fec8..160042e 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -29,7 +29,6 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -38,12 +37,19 @@
  */
 public class AllAppsStore {
 
+    // Defer updates flag used to defer all apps updates to the next draw.
+    public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
+    // Defer updates flag used to defer all apps updates while the user interacts with all apps.
+    public static final int DEFER_UPDATES_USER_INTERACTION = 1 << 1;
+    // Defer updates flag used to defer all apps updates by a test's request.
+    public static final int DEFER_UPDATES_TEST = 1 << 2;
+
     private PackageUserKey mTempKey = new PackageUserKey(null, null);
     private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
     private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
 
-    private boolean mDeferUpdates = false;
+    private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
 
     public Collection<AppInfo> getApps() {
@@ -62,17 +68,22 @@
         return mComponentToAppMap.get(key);
     }
 
-    public void setDeferUpdates(boolean deferUpdates) {
-        if (mDeferUpdates != deferUpdates) {
-            mDeferUpdates = deferUpdates;
+    public void enableDeferUpdates(int flag) {
+        mDeferUpdatesFlags |= flag;
+    }
 
-            if (!mDeferUpdates && mUpdatePending) {
-                notifyUpdate();
-                mUpdatePending = false;
-            }
+    public void disableDeferUpdates(int flag) {
+        mDeferUpdatesFlags &= ~flag;
+        if (mDeferUpdatesFlags == 0 && mUpdatePending) {
+            notifyUpdate();
+            mUpdatePending = false;
         }
     }
 
+    public int getDeferUpdatesFlags() {
+        return mDeferUpdatesFlags;
+    }
+
     /**
      * Adds or updates existing apps in the list
      */
@@ -95,7 +106,7 @@
 
 
     private void notifyUpdate() {
-        if (mDeferUpdates) {
+        if (mDeferUpdatesFlags != 0) {
             mUpdatePending = true;
             return;
         }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index b8476aa..d2e1961 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -23,9 +23,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import java.util.concurrent.ExecutionException;
+
 public class TestInformationHandler implements ResourceBasedOverride {
 
     public static TestInformationHandler newInstance(Context context) {
@@ -77,6 +81,32 @@
             case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
                 TestProtocol.sDebugTracing = false;
                 break;
+
+            case TestProtocol.REQUEST_FREEZE_APP_LIST:
+                new MainThreadExecutor().execute(() ->
+                        mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
+                                AllAppsStore.DEFER_UPDATES_TEST));
+                break;
+
+            case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
+                new MainThreadExecutor().execute(() ->
+                        mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
+                                AllAppsStore.DEFER_UPDATES_TEST));
+                break;
+
+            case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
+                try {
+                    final int deferUpdatesFlags = new MainThreadExecutor().submit(() ->
+                            mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                            deferUpdatesFlags);
+                } catch (ExecutionException e) {
+                    throw new RuntimeException(e);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                break;
+            }
         }
         return response;
     }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 99efb22..e60b665 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -57,6 +57,7 @@
     }
 
     public static final String TEST_INFO_RESPONSE_FIELD = "response";
+
     public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
             "home-to-overview-swipe-height";
     public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT =
@@ -65,6 +66,10 @@
             "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
+    public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
+    public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
+    public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
+
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4f8b87c..d171004 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -108,47 +108,63 @@
     @Test
     @Ignore
     public void testPressHomeOnAllAppsContextMenu() throws Exception {
-        mLauncher.getWorkspace().switchToAllApps().getAppIcon("TestActivity7").openMenu();
+        final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        allApps.freeze();
+        try {
+            allApps.getAppIcon("TestActivity7").openMenu();
+        } finally {
+            allApps.unfreeze();
+        }
         mLauncher.pressHome();
     }
 
     public static void runAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
-        assertNotNull("allApps parameter is null", allApps);
+        allApps.freeze();
+        try {
+            assertNotNull("allApps parameter is null", allApps);
 
-        assertTrue(
-                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+            assertTrue(
+                    "Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
 
-        // Test flinging forward and backward.
-        test.executeOnLauncher(launcher -> assertEquals(
-                "All Apps started in already scrolled state", 0, test.getAllAppsScroll(launcher)));
+            // Test flinging forward and backward.
+            test.executeOnLauncher(launcher -> assertEquals(
+                    "All Apps started in already scrolled state", 0,
+                    test.getAllAppsScroll(launcher)));
 
-        allApps.flingForward();
-        assertTrue("Launcher internal state is not All Apps",
-                test.isInState(LauncherState.ALL_APPS));
-        final Integer flingForwardY = test.getFromLauncher(
-                launcher -> test.getAllAppsScroll(launcher));
-        test.executeOnLauncher(
-                launcher -> assertTrue("flingForward() didn't scroll App Apps", flingForwardY > 0));
+            allApps.flingForward();
+            assertTrue("Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
+            final Integer flingForwardY = test.getFromLauncher(
+                    launcher -> test.getAllAppsScroll(launcher));
+            test.executeOnLauncher(
+                    launcher -> assertTrue("flingForward() didn't scroll App Apps",
+                            flingForwardY > 0));
 
-        allApps.flingBackward();
-        assertTrue(
-                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
-        final Integer flingBackwardY = test.getFromLauncher(
-                launcher -> test.getAllAppsScroll(launcher));
-        test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
-                flingBackwardY < flingForwardY));
+            allApps.flingBackward();
+            assertTrue(
+                    "Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
+            final Integer flingBackwardY = test.getFromLauncher(
+                    launcher -> test.getAllAppsScroll(launcher));
+            test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
+                    flingBackwardY < flingForwardY));
 
-        // Test scrolling down to YouTube.
-        assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
-        // Test scrolling up to Camera.
-        assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
-        // Test failing to find a non-existing app.
-        final AllApps allAppsFinal = allApps;
-        expectFail("All apps: could find a non-existing app",
-                () -> allAppsFinal.getAppIcon("NO APP"));
+            // Test scrolling down to YouTube.
+            assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
+            // Test scrolling up to Camera.
+            assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
+            // Test failing to find a non-existing app.
+            final AllApps allAppsFinal = allApps;
+            expectFail("All apps: could find a non-existing app",
+                    () -> allAppsFinal.getAppIcon("NO APP"));
 
-        assertTrue(
-                "Launcher internal state is not All Apps", test.isInState(LauncherState.ALL_APPS));
+            assertTrue(
+                    "Launcher internal state is not All Apps",
+                    test.isInState(LauncherState.ALL_APPS));
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     @Test
@@ -199,12 +215,17 @@
     }
 
     public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
-        final AppIcon app = allApps.getAppIcon("TestActivity7");
-        assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
-        test.executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                test.isInBackground(launcher)));
+        allApps.freeze();
+        try {
+            final AppIcon app = allApps.getAppIcon("TestActivity7");
+            assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
+            test.executeOnLauncher(launcher -> assertTrue(
+                    "Launcher activity is the top activity; expecting another activity to be the top "
+                            + "one",
+                    test.isInBackground(launcher)));
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     @Test
@@ -260,20 +281,23 @@
     public void testLaunchMenuItem() throws Exception {
         if (!TestHelpers.isInLauncherProcess()) return;
 
-        final AppIconMenu menu = mLauncher.
+        final AllApps allApps = mLauncher.
                 getWorkspace().
-                switchToAllApps().
-                getAppIcon(APP_NAME).
-                openMenu();
+                switchToAllApps();
+        allApps.freeze();
+        try {
+            final AppIconMenu menu = allApps.
+                    getAppIcon(APP_NAME).
+                    openMenu();
 
-        executeOnLauncher(
-                launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
-                        isOptionsPopupVisible(launcher)));
+            executeOnLauncher(
+                    launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+                            isOptionsPopupVisible(launcher)));
 
-        final AppIconMenuItem menuItem = menu.getMenuItem(1);
-        final String itemName = menuItem.getText();
-
-        menuItem.launch(getAppPackageName());
+            menu.getMenuItem(1).launch(getAppPackageName());
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     @Test
@@ -282,12 +306,18 @@
         // 1. Open all apps and wait for load complete.
         // 2. Drag icon to homescreen.
         // 3. Verify that the icon works on homescreen.
-        mLauncher.getWorkspace().
-                switchToAllApps().
-                getAppIcon(APP_NAME).
-                dragToWorkspace().
-                getWorkspaceAppIcon(APP_NAME).
-                launch(getAppPackageName());
+        final AllApps allApps = mLauncher.getWorkspace().
+                switchToAllApps();
+        allApps.freeze();
+        try {
+            allApps.
+                    getAppIcon(APP_NAME).
+                    dragToWorkspace().
+                    getWorkspaceAppIcon(APP_NAME).
+                    launch(getAppPackageName());
+        } finally {
+            allApps.unfreeze();
+        }
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
@@ -302,21 +332,27 @@
         // 1. Open all apps and wait for load complete.
         // 2. Find the app and long press it to show shortcuts.
         // 3. Press icon center until shortcuts appear
-        final AppIconMenuItem menuItem = mLauncher.
+        final AllApps allApps = mLauncher.
                 getWorkspace().
-                switchToAllApps().
-                getAppIcon(APP_NAME).
-                openMenu().
-                getMenuItem(0);
-        final String shortcutName = menuItem.getText();
+                switchToAllApps();
+        allApps.freeze();
+        try {
+            final AppIconMenuItem menuItem = allApps.
+                    getAppIcon(APP_NAME).
+                    openMenu().
+                    getMenuItem(0);
+            final String shortcutName = menuItem.getText();
 
-        // 4. Drag the first shortcut to the home screen.
-        // 5. Verify that the shortcut works on home screen
-        //    (the app opens and has the same text as the shortcut).
-        menuItem.
-                dragToWorkspace().
-                getWorkspaceAppIcon(shortcutName).
-                launch(getAppPackageName());
+            // 4. Drag the first shortcut to the home screen.
+            // 5. Verify that the shortcut works on home screen
+            //    (the app opens and has the same text as the shortcut).
+            menuItem.
+                    dragToWorkspace().
+                    getWorkspaceAppIcon(shortcutName).
+                    launch(getAppPackageName());
+        } finally {
+            allApps.unfreeze();
+        }
     }
 
     public static String getAppPackageName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index d03035a..21d763e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -48,6 +48,7 @@
                 "apps_list_view");
         // Wait for the recycler to populate.
         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
+        verifyNotFrozen("All apps freeze flags upon opening all apps");
     }
 
     @Override
@@ -210,4 +211,25 @@
             verifyActiveContainer();
         }
     }
+
+    /**
+     * Freezes updating app list upon app install/uninstall/update.
+     */
+    public void freeze() {
+        mLauncher.getTestInfo(TestProtocol.REQUEST_FREEZE_APP_LIST);
+    }
+
+    /**
+     * Resumes updating app list upon app install/uninstall/update.
+     */
+    public void unfreeze() {
+        mLauncher.getTestInfo(TestProtocol.REQUEST_UNFREEZE_APP_LIST);
+        verifyNotFrozen("All apps freeze flags upon unfreezing");
+    }
+
+    private void verifyNotFrozen(String message) {
+        mLauncher.assertEquals(message, 0, mLauncher.getTestInfo(
+                TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS).
+                getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index e45fca8..8ebe525 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -292,6 +292,12 @@
         }
     }
 
+    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);