Merge "Implementing support for item diffing instead of creating out the complete UI on every update" into tm-dev
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 9369bdc..b6d3fc5 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -23,7 +23,6 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 
-import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -197,13 +196,6 @@
         if (mScrollbar != null) {
             mScrollbar.reattachThumbToScroll();
         }
-        if (getLayoutManager() instanceof LinearLayoutManager) {
-            LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
-            if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
-                // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
-                return;
-            }
-        }
         scrollToPosition(0);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index e279f59..47f2dd5 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -29,11 +29,13 @@
 import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.AppLauncher;
 
+import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -95,11 +97,15 @@
         mHeader.reset(false);
     }
 
-    /** Invoke when the search results change. */
-    public void onSearchResultsChanged() {
-        for (int i = 0; i < mAH.size(); i++) {
-            if (mAH.get(i).mRecyclerView != null) {
-                mAH.get(i).mRecyclerView.onSearchResultsChanged();
+    /**
+     * Sets results list for search
+     */
+    public void setSearchResults(ArrayList<AdapterItem> results) {
+        if (getApps().setSearchResults(results)) {
+            for (int i = 0; i < mAH.size(); i++) {
+                if (mAH.get(i).mRecyclerView != null) {
+                    mAH.get(i).mRecyclerView.onSearchResultsChanged();
+                }
             }
         }
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
index f97eb28..7067fa2 100644
--- a/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
+++ b/src/com/android/launcher3/allapps/AllAppsFastScrollHelper.java
@@ -37,10 +37,10 @@
      * Smooth scrolls the recycler view to the given section.
      */
     public void smoothScrollToSection(FastScrollSectionInfo info) {
-        if (mTargetFastScrollPosition == info.fastScrollToItem.position) {
+        if (mTargetFastScrollPosition == info.position) {
             return;
         }
-        mTargetFastScrollPosition = info.fastScrollToItem.position;
+        mTargetFastScrollPosition = info.position;
         mRv.getLayoutManager().startSmoothScroll(new MyScroller(mTargetFastScrollPosition));
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 7dbe711..c4e977b 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -71,6 +71,26 @@
         public void onChanged() {
             mCachedScrollPositions.clear();
         }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            onChanged();
+        }
     };
 
     // The empty-search result background
@@ -241,17 +261,14 @@
         // Find the fastscroll section that maps to this touch fraction
         List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
                 mApps.getFastScrollerSections();
-        AlphabeticalAppsList.FastScrollSectionInfo lastInfo = fastScrollSections.get(0);
-        for (int i = 1; i < fastScrollSections.size(); i++) {
-            AlphabeticalAppsList.FastScrollSectionInfo info = fastScrollSections.get(i);
-            if (info.touchFraction > touchFraction) {
-                break;
-            }
-            lastInfo = info;
+        int count = fastScrollSections.size();
+        if (count == 0) {
+            return "";
         }
-
-        mFastScrollHelper.smoothScrollToSection(lastInfo);
-        return lastInfo.sectionName;
+        int index = Utilities.boundToRange((int) (touchFraction * count), 0, count - 1);
+        AlphabeticalAppsList.FastScrollSectionInfo section = fastScrollSections.get(index);
+        mFastScrollHelper.smoothScrollToSection(section);
+        return section.sectionName;
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index cdaf80a..4ccfd39 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,9 +15,14 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_EMPTY_SEARCH;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_SEARCH_MARKET;
 
 import android.content.Context;
 
+import androidx.recyclerview.widget.DiffUtil;
+
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
@@ -52,14 +57,13 @@
      */
     public static class FastScrollSectionInfo {
         // The section name
-        public String sectionName;
-        // The AdapterItem to scroll to for this section
-        public AdapterItem fastScrollToItem;
-        // The touch fraction that should map to this fast scroll section info
-        public float touchFraction;
+        public final String sectionName;
+        // The item position
+        public final int position;
 
-        public FastScrollSectionInfo(String sectionName) {
+        public FastScrollSectionInfo(String sectionName, int position) {
             this.sectionName = sectionName;
+            this.position = position;
         }
     }
 
@@ -108,13 +112,6 @@
     }
 
     /**
-     * Returns all the apps.
-     */
-    public List<AppInfo> getApps() {
-        return mApps;
-    }
-
-    /**
      * Returns fast scroller sections of all the current filtered applications.
      */
     public List<FastScrollSectionInfo> getFastScrollerSections() {
@@ -235,77 +232,49 @@
      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
      */
     public void updateAdapterItems() {
-        refillAdapterItems();
-        refreshRecyclerView();
-    }
-
-    private void refreshRecyclerView() {
-        if (mAdapter != null) {
-            mAdapter.notifyDataSetChanged();
-        }
-    }
-
-    private void refillAdapterItems() {
-        String lastSectionName = null;
-        FastScrollSectionInfo lastFastScrollerSectionInfo = null;
-        int position = 0;
-
+        List<AdapterItem> oldItems = new ArrayList<>(mAdapterItems);
         // Prepare to update the list of sections, filtered apps, etc.
-        mAccessibilityResultsCount = 0;
         mFastScrollerSections.clear();
         mAdapterItems.clear();
+        mAccessibilityResultsCount = 0;
 
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
         // ordered set of sections
-
         if (!hasFilter()) {
-            mAccessibilityResultsCount = mApps.size();
+            int position = 0;
             if (mWorkAdapterProvider != null) {
                 position += mWorkAdapterProvider.addWorkItems(mAdapterItems);
                 if (!mWorkAdapterProvider.shouldShowWorkApps()) {
                     return;
                 }
             }
+            String lastSectionName = null;
             for (AppInfo info : mApps) {
-                String sectionName = info.sectionName;
+                mAdapterItems.add(AdapterItem.asApp(info));
 
+                String sectionName = info.sectionName;
                 // Create a new section if the section names do not match
                 if (!sectionName.equals(lastSectionName)) {
                     lastSectionName = sectionName;
-                    lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
-                    mFastScrollerSections.add(lastFastScrollerSectionInfo);
+                    mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, position));
                 }
-
-                // Create an app item
-                AdapterItem appItem = AdapterItem.asApp(position++, info);
-                if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
-                    lastFastScrollerSectionInfo.fastScrollToItem = appItem;
-                }
-
-                mAdapterItems.add(appItem);
+                position++;
             }
         } else {
-            int count = mSearchResults.size();
-            for (int i = 0; i < count; i++) {
-                AdapterItem adapterItem = mSearchResults.get(i);
-                adapterItem.position = i;
-                mAdapterItems.add(adapterItem);
-
-                if (adapterItem.isCountedForAccessibility()) {
-                    mAccessibilityResultsCount++;
-                }
-            }
+            mAdapterItems.addAll(mSearchResults);
             if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
                 // Append the search market item
                 if (hasNoFilteredResults()) {
-                    mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+                    mAdapterItems.add(new AdapterItem(VIEW_TYPE_EMPTY_SEARCH));
                 } else {
-                    mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
+                    mAdapterItems.add(new AdapterItem(VIEW_TYPE_ALL_APPS_DIVIDER));
                 }
-                mAdapterItems.add(AdapterItem.asMarketSearch(position++));
-
+                mAdapterItems.add(new AdapterItem(VIEW_TYPE_SEARCH_MARKET));
             }
         }
+        mAccessibilityResultsCount = (int) mAdapterItems.stream()
+                .filter(AdapterItem::isCountedForAccessibility).count();
+
         if (mNumAppsPerRowAllApps != 0) {
             // Update the number of rows in the adapter after we do all the merging (otherwise, we
             // would have to shift the values again)
@@ -328,19 +297,43 @@
                 }
             }
             mNumAppRowsInAdapter = rowIndex + 1;
+        }
 
-            // Pre-calculate all the fast scroller fractions
-            float perSectionTouchFraction = 1f / mFastScrollerSections.size();
-            float cumulativeTouchFraction = 0f;
-            for (FastScrollSectionInfo info : mFastScrollerSections) {
-                AdapterItem item = info.fastScrollToItem;
-                if (!BaseAllAppsAdapter.isIconViewType(item.viewType)) {
-                    info.touchFraction = 0f;
-                    continue;
-                }
-                info.touchFraction = cumulativeTouchFraction;
-                cumulativeTouchFraction += perSectionTouchFraction;
-            }
+        if (mAdapter != null) {
+            DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
+                    .dispatchUpdatesTo(mAdapter);
         }
     }
+
+    private static class MyDiffCallback extends DiffUtil.Callback {
+
+        private final List<AdapterItem> mOldList;
+        private final List<AdapterItem> mNewList;
+
+        MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList) {
+            mOldList = oldList;
+            mNewList = newList;
+        }
+
+        @Override
+        public int getOldListSize() {
+            return mOldList.size();
+        }
+
+        @Override
+        public int getNewListSize() {
+            return mNewList.size();
+        }
+
+        @Override
+        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+            return mOldList.get(oldItemPosition).isSameAs(mNewList.get(newItemPosition));
+        }
+
+        @Override
+        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+            return mOldList.get(oldItemPosition).isContentSame(mNewList.get(newItemPosition));
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 8ac2536..c7c4607 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -88,10 +88,8 @@
      */
     public static class AdapterItem {
         /** Common properties */
-        // The index of this adapter item in the list
-        public int position;
         // The type of this item
-        public int viewType;
+        public final int viewType;
 
         // The row that this item shows up on
         public int rowIndex;
@@ -100,50 +98,37 @@
         // The associated ItemInfoWithIcon for the item
         public AppInfo itemInfo = null;
 
+        public AdapterItem(int viewType) {
+            this.viewType = viewType;
+        }
+
         /**
          * Factory method for AppIcon AdapterItem
          */
-        public static AdapterItem asApp(int pos, AppInfo appInfo) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = VIEW_TYPE_ICON;
-            item.position = pos;
+        public static AdapterItem asApp(AppInfo appInfo) {
+            AdapterItem item = new AdapterItem(VIEW_TYPE_ICON);
             item.itemInfo = appInfo;
             return item;
         }
 
-        /**
-         * Factory method for empty search results view
-         */
-        public static AdapterItem asEmptySearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = VIEW_TYPE_EMPTY_SEARCH;
-            item.position = pos;
-            return item;
-        }
-
-        /**
-         * Factory method for a dividerView in AllAppsSearch
-         */
-        public static AdapterItem asAllAppsDivider(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
-            item.position = pos;
-            return item;
-        }
-
-        /**
-         * Factory method for a market search button
-         */
-        public static AdapterItem asMarketSearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = VIEW_TYPE_SEARCH_MARKET;
-            item.position = pos;
-            return item;
-        }
-
         protected boolean isCountedForAccessibility() {
             return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
         }
+
+        /**
+         * Returns true if the items represent the same object
+         */
+        public boolean isSameAs(AdapterItem other) {
+            return (other.viewType != viewType) && (other.getClass() == getClass());
+        }
+
+        /**
+         * This is called only if {@link #isSameAs} returns true to check if the contents are same
+         * as well. Returning true will prevent redrawing of thee item.
+         */
+        public boolean isContentSame(AdapterItem other) {
+            return itemInfo == null && other.itemInfo == null;
+        }
     }
 
     protected final T mActivityContext;
diff --git a/src/com/android/launcher3/allapps/WorkAdapterProvider.java b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
index ce44958..76d08c8 100644
--- a/src/com/android/launcher3/allapps/WorkAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/WorkAdapterProvider.java
@@ -19,10 +19,10 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Button;
 import android.widget.TextView;
 
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.views.ActivityContext;
 
@@ -107,13 +107,9 @@
     public int addWorkItems(ArrayList<AllAppsGridAdapter.AdapterItem> adapterItems) {
         if (mState == WorkProfileManager.STATE_DISABLED) {
             //add disabled card here.
-            AllAppsGridAdapter.AdapterItem disabledCard = new AllAppsGridAdapter.AdapterItem();
-            disabledCard.viewType = VIEW_TYPE_WORK_DISABLED_CARD;
-            adapterItems.add(disabledCard);
+            adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
         } else if (mState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
-            AllAppsGridAdapter.AdapterItem eduCard = new AllAppsGridAdapter.AdapterItem();
-            eduCard.viewType = VIEW_TYPE_WORK_EDU_CARD;
-            adapterItems.add(eduCard);
+            adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
         }
 
         return adapterItems.size();
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index bc2c318..6539c05 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.search.SearchCallback;
@@ -57,7 +56,6 @@
     private final AllAppsSearchBarController mSearchBarController;
     private final SpannableStringBuilder mSearchQueryBuilder;
 
-    private AlphabeticalAppsList<?> mApps;
     private ActivityAllAppsContainerView<?> mAppsView;
 
     // The amount of pixels to shift down and overlap with the rest of the content.
@@ -131,7 +129,6 @@
 
     @Override
     public void initializeSearch(ActivityAllAppsContainerView<?> appsView) {
-        mApps = appsView.getApps();
         mAppsView = appsView;
         mSearchBarController.initialize(
                 new DefaultAppSearchAlgorithm(getContext()),
@@ -170,17 +167,14 @@
     @Override
     public void onSearchResult(String query, ArrayList<AdapterItem> items) {
         if (items != null) {
-            mApps.setSearchResults(items);
-            notifyResultChanged();
+            mAppsView.setSearchResults(items);
             mAppsView.setLastSearchQuery(query);
         }
     }
 
     @Override
     public void clearSearchResult() {
-        if (mApps.setSearchResults(null)) {
-            notifyResultChanged();
-        }
+        mAppsView.setSearchResults(null);
 
         // Clear the search query
         mSearchQueryBuilder.clear();
@@ -189,10 +183,6 @@
         mAppsView.onClearSearchResult();
     }
 
-    private void notifyResultChanged() {
-        mAppsView.onSearchResultsChanged();
-    }
-
     @Override
     public void setInsets(Rect insets) {
         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 33d0082..4eceb71 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -85,8 +85,7 @@
         for (int i = 0; i < total && resultCount < MAX_RESULTS_COUNT; i++) {
             AppInfo info = apps.get(i);
             if (StringMatcherUtility.matches(queryTextLower, info.title.toString(), matcher)) {
-                AdapterItem appItem = AdapterItem.asApp(resultCount, info);
-                result.add(appItem);
+                result.add(AdapterItem.asApp(info));
                 resultCount++;
             }
         }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index f780f03..755e4a9 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -239,21 +239,6 @@
         mHeaderViewDimensionsProvider = headerViewDimensionsProvider;
     }
 
-    @Override
-    public void scrollToTop() {
-        if (mScrollbar != null) {
-            mScrollbar.reattachThumbToScroll();
-        }
-
-        if (getLayoutManager() instanceof LinearLayoutManager) {
-            if (getCurrentScrollY() == 0) {
-                // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
-                return;
-            }
-        }
-        scrollToPosition(0);
-    }
-
     /**
      * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
      * {@code untilIndex}.
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index f31e4f3..7c1be1d 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -25,11 +25,14 @@
 import android.util.Log;
 import android.view.View;
 
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
 import com.android.launcher3.allapps.WorkAdapterProvider;
 import com.android.launcher3.allapps.WorkEduCard;
+import com.android.launcher3.allapps.WorkPausedCard;
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 
@@ -38,6 +41,7 @@
 import org.junit.Test;
 
 import java.util.Objects;
+import java.util.function.Predicate;
 
 public class WorkProfileTest extends AbstractLauncherUiTest {
 
@@ -130,6 +134,8 @@
             return manager.getCurrentState() == WorkProfileManager.STATE_DISABLED;
         }, LauncherInstrumentation.WAIT_TIME_MS);
 
+        waitForWorkCard("Work paused card not shown", view -> view instanceof WorkPausedCard);
+
         // start work profile toggle ON test
         executeOnLauncher(l -> {
             ActivityAllAppsContainerView<?> allApps = l.getAppsView();
@@ -154,9 +160,19 @@
             l.getAppsView().getWorkManager().reset();
         });
 
-        waitForLauncherCondition("Work profile education not shown",
-                l -> l.getAppsView().getActiveRecyclerView()
-                        .findViewHolderForAdapterPosition(0).itemView instanceof WorkEduCard,
-                LauncherInstrumentation.WAIT_TIME_MS);
+        waitForWorkCard("Work profile education not shown", view -> view instanceof WorkEduCard);
+    }
+
+    private void waitForWorkCard(String message, Predicate<View> workCardCheck) {
+        waitForLauncherCondition(message, l -> {
+            l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+            ViewHolder holder = l.getAppsView().getActiveRecyclerView()
+                    .findViewHolderForAdapterPosition(0);
+            try {
+                return holder != null && workCardCheck.test(holder.itemView);
+            } finally {
+                l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+            }
+        }, LauncherInstrumentation.WAIT_TIME_MS);
     }
 }