Revert "Updating the scroll calculation from recyclerView to avoid view inflation"

This reverts commit 20bbe95ddbe0d93a50e18aba4623cc844ecfb9e5.

Reason for revert: Causing flake in Ironwood test: b/248295569

Test: ABTD
Before: Flaky, 14/50 PASSED
https://android-build.googleplex.com/builds/abtd/run/L33900000956890639

Revert: 50/50 PASSED
https://android-build.googleplex.com/builds/abtd/run/L49200000956887317

Change-Id: I41f4428c74e581323f90c716a7852b5e553ae27d
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 2f927d3..747b755 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -91,7 +92,8 @@
     protected int getAvailableScrollHeight() {
         // AvailableScrollHeight = Total height of the all items - first page height
         int firstPageHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
-        int availableScrollHeight = computeVerticalScrollRange() - firstPageHeight;
+        int totalHeightOfAllItems = getItemsHeight(/* untilIndex= */ getAdapter().getItemCount());
+        int availableScrollHeight = totalHeightOfAllItems - firstPageHeight;
         return Math.max(0, availableScrollHeight);
     }
 
@@ -144,7 +146,10 @@
 
         // IF scroller is at the very top OR there is no scroll bar because there is probably not
         // enough items to scroll, THEN it's okay for the container to be pulled down.
-        return computeVerticalScrollOffset() == 0;
+        if (getCurrentScrollY() == 0) {
+            return true;
+        }
+        return getAdapter() == null || getAdapter().getItemCount() == 0;
     }
 
     /**
@@ -155,6 +160,53 @@
     }
 
     /**
+     * @return the scroll top of this recycler view.
+     */
+    public int getCurrentScrollY() {
+        Adapter adapter = getAdapter();
+        if (adapter == null) {
+            return -1;
+        }
+        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
+            return -1;
+        }
+
+        int itemPosition = NO_POSITION;
+        View child = null;
+
+        LayoutManager layoutManager = getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            // Use the LayoutManager as the source of truth for visible positions. During
+            // animations, the view group child may not correspond to the visible views that appear
+            // at the top.
+            itemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
+            child = layoutManager.findViewByPosition(itemPosition);
+        }
+
+        if (child == null) {
+            // If the layout manager returns null for any reason, which can happen before layout
+            // has occurred for the position, then look at the child of this view as a ViewGroup.
+            child = getChildAt(0);
+            itemPosition = getChildAdapterPosition(child);
+        }
+        if (itemPosition == NO_POSITION) {
+            return -1;
+        }
+        return getPaddingTop() + getItemsHeight(itemPosition)
+                - layoutManager.getDecoratedTop(child);
+    }
+
+    /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index
+     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
+     * it returns the full height of all the items.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    protected abstract int getItemsHeight(int untilIndex);
+
+    /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      * <p>Override in each subclass of this base class.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c85924e..6bdfa1c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2779,7 +2779,7 @@
             View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
                     preferredItem, packageAndUserAndApp);
 
-            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
+            if (v != null && activeRecyclerView.getCurrentScrollY() > 0) {
                 RectF locationBounds = new RectF();
                 FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
                         new Rect());
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index e0b4951..33d2f2b 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -26,9 +26,7 @@
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
 
-import com.android.launcher3.util.ScrollableLayoutManager;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.List;
@@ -64,10 +62,10 @@
     /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
-    public class AppsGridLayoutManager extends ScrollableLayoutManager {
+    public class AppsGridLayoutManager extends GridLayoutManager {
 
         public AppsGridLayoutManager(Context context) {
-            super(context);
+            super(context, 1, GridLayoutManager.VERTICAL, false);
         }
 
         @Override
@@ -127,15 +125,6 @@
             }
             return extraRows;
         }
-
-        @Override
-        protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
-            AllAppsGridAdapter.AdapterItem item = mApps.getAdapterItems().get(position);
-            // only account for the first icon in the row since they are the same size within a row
-            return (isIconViewType(item.viewType) && item.rowAppIndex != 0)
-                    ? heightUntilLastPos
-                    : (heightUntilLastPos + mCachedSizes.get(item.viewType));
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 3d06fb5..21a7dfb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
@@ -24,6 +26,7 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -46,10 +49,40 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LATENCY = Utilities.isPropertyEnabled(SEARCH_LOGGING);
 
+    protected AlphabeticalAppsList<?> mApps;
     protected final int mNumAppsPerRow;
+
+    // The specific view heights that we use to calculate scroll
+    private final SparseIntArray mViewHeights = new SparseIntArray();
+    private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
     private final AllAppsFastScrollHelper mFastScrollHelper;
 
-    protected AlphabeticalAppsList<?> mApps;
+
+    private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
+        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();
+        }
+    };
 
     public AllAppsRecyclerView(Context context) {
         this(context, null);
@@ -89,8 +122,12 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows
                 * (mNumAppsPerRow + 1));
+
+        mViewHeights.clear();
+        mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
     }
 
+
     @Override
     public void onDraw(Canvas c) {
         if (DEBUG) {
@@ -163,6 +200,17 @@
     }
 
     @Override
+    public void setAdapter(Adapter adapter) {
+        if (getAdapter() != null) {
+            getAdapter().unregisterAdapterDataObserver(mObserver);
+        }
+        super.setAdapter(adapter);
+        if (adapter != null) {
+            adapter.registerAdapterDataObserver(mObserver);
+        }
+    }
+
+    @Override
     protected boolean isPaddingOffsetRequired() {
         return true;
     }
@@ -183,13 +231,13 @@
         List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
-        if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
+        if (items.isEmpty() || mNumAppsPerRow == 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = computeVerticalScrollOffset();
+        int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -244,6 +292,51 @@
         }
     }
 
+    @Override
+    protected int getItemsHeight(int position) {
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
+        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
+                ? items.get(position) : null;
+        int y = mCachedScrollPositions.get(position, -1);
+        if (y < 0) {
+            y = 0;
+            for (int i = 0; i < position; i++) {
+                AllAppsGridAdapter.AdapterItem item = items.get(i);
+                if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
+                    // Break once we reach the desired row
+                    if (posItem != null && posItem.viewType == item.viewType &&
+                            posItem.rowIndex == item.rowIndex) {
+                        break;
+                    }
+                    // Otherwise, only account for the first icon in the row since they are the same
+                    // size within a row
+                    if (item.rowAppIndex == 0) {
+                        y += mViewHeights.get(item.viewType, 0);
+                    }
+                } else {
+                    // Rest of the views span the full width
+                    int elHeight = mViewHeights.get(item.viewType);
+                    if (elHeight == 0) {
+                        ViewHolder holder = findViewHolderForAdapterPosition(i);
+                        if (holder == null) {
+                            holder = getAdapter().createViewHolder(this, item.viewType);
+                            getAdapter().onBindViewHolder(holder, i);
+                            holder.itemView.measure(UNSPECIFIED, UNSPECIFIED);
+                            elHeight = holder.itemView.getMeasuredHeight();
+
+                            getRecycledViewPool().putRecycledView(holder);
+                        } else {
+                            elHeight = holder.itemView.getMeasuredHeight();
+                        }
+                    }
+                    y += elHeight;
+                }
+            }
+            mCachedScrollPositions.put(position, y);
+        }
+        return y;
+    }
+
     public int getScrollBarTop() {
         return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
     }
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 7660e16..879a159 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -103,8 +103,7 @@
             new RecyclerView.OnScrollListener() {
                 @Override
                 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                    updateHeaderScroll(
-                            ((AllAppsRecyclerView) recyclerView).computeVerticalScrollOffset());
+                    updateHeaderScroll(((AllAppsRecyclerView) recyclerView).getCurrentScrollY());
                 }
             };
 
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 73efd3f..8ec2aeb 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -68,7 +68,7 @@
                         mAnimator.cancel();
                     }
 
-                    int current = -mCurrentRV.computeVerticalScrollOffset();
+                    int current = -mCurrentRV.getCurrentScrollY();
                     boolean headerCollapsed = mHeaderCollapsed;
                     moved(current);
                     applyVerticalMove();
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 269baf0..d3c9bc9 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -112,12 +112,12 @@
 
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> l.getAppsView().getActiveRecyclerView().computeVerticalScrollOffset());
+                        l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
             }
 
             case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
-                        l -> WidgetsFullSheet.getWidgetsView(l).computeVerticalScrollOffset());
+                        l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
             }
 
             case TestProtocol.REQUEST_TARGET_INSETS: {
diff --git a/src/com/android/launcher3/util/ScrollableLayoutManager.java b/src/com/android/launcher3/util/ScrollableLayoutManager.java
deleted file mode 100644
index 17eaefd..0000000
--- a/src/com/android/launcher3/util/ScrollableLayoutManager.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2022 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.util;
-
-import android.content.Context;
-import android.util.SparseIntArray;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-import androidx.recyclerview.widget.RecyclerView.State;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
-
-/**
- * Extension of {@link GridLayoutManager} with support for smooth scrolling
- */
-public class ScrollableLayoutManager extends GridLayoutManager {
-
-    // keyed on item type
-    protected final SparseIntArray mCachedSizes = new SparseIntArray();
-
-    private RecyclerView mRv;
-
-    /**
-     * Precalculated total height keyed on the item position. This is always incremental.
-     * Subclass can override {@link #incrementTotalHeight} to incorporate the layout logic.
-     * For example all-apps should have same values for items in same row,
-     *     sample values: 0, 10, 10, 10, 10, 20, 20, 20, 20
-     * whereas widgets will have strictly increasing values
-     *     sample values: 0, 10, 50, 60, 110
-     */
-
-    //
-    private int[] mTotalHeightCache = new int[1];
-    private int mLastValidHeightIndex = 0;
-
-    public ScrollableLayoutManager(Context context) {
-        super(context, 1, GridLayoutManager.VERTICAL, false);
-    }
-
-    @Override
-    public void onAttachedToWindow(RecyclerView view) {
-        super.onAttachedToWindow(view);
-        mRv = view;
-    }
-
-    @Override
-    public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) {
-        super.layoutDecorated(child, left, top, right, bottom);
-        mCachedSizes.put(
-                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
-    }
-
-    @Override
-    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
-            int bottom) {
-        super.layoutDecoratedWithMargins(child, left, top, right, bottom);
-        mCachedSizes.put(
-                mRv.getChildViewHolder(child).getItemViewType(), child.getMeasuredHeight());
-    }
-
-    @Override
-    public int computeVerticalScrollExtent(State state) {
-        return mRv == null ? 0 : mRv.getHeight();
-    }
-
-    @Override
-    public int computeVerticalScrollOffset(State state) {
-        Adapter adapter = mRv == null ? null : mRv.getAdapter();
-        if (adapter == null) {
-            return 0;
-        }
-        if (adapter.getItemCount() == 0 || getChildCount() == 0) {
-            return 0;
-        }
-        View child = getChildAt(0);
-        ViewHolder holder = mRv.findContainingViewHolder(child);
-        if (holder == null) {
-            return 0;
-        }
-        int itemPosition = holder.getLayoutPosition();
-        if (itemPosition < 0) {
-            return 0;
-        }
-        return getPaddingTop() + getItemsHeight(adapter, itemPosition) - getDecoratedTop(child);
-    }
-
-    @Override
-    public int computeVerticalScrollRange(State state) {
-        Adapter adapter = mRv == null ? null : mRv.getAdapter();
-        return adapter == null ? 0 : getItemsHeight(adapter, adapter.getItemCount());
-    }
-
-    /**
-     * Returns the sum of the height, in pixels, of this list adapter's items from index
-     * 0 (inclusive) until {@code untilIndex} (exclusive). If untilIndex is same as the itemCount,
-     * it returns the full height of all the items.
-     *
-     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
-     * sum of all items' height.
-     */
-    private int getItemsHeight(Adapter adapter, int untilIndex) {
-        final int totalItems = adapter.getItemCount();
-        if (mTotalHeightCache.length < (totalItems + 1)) {
-            mTotalHeightCache = new int[totalItems + 1];
-            mLastValidHeightIndex = 0;
-        }
-        if (untilIndex > totalItems) {
-            untilIndex = totalItems;
-        } else if (untilIndex < 0) {
-            untilIndex = 0;
-        }
-        if (untilIndex <= mLastValidHeightIndex) {
-            return mTotalHeightCache[untilIndex];
-        }
-
-        int totalItemsHeight = mTotalHeightCache[mLastValidHeightIndex];
-        for (int i = mLastValidHeightIndex; i < untilIndex; i++) {
-            totalItemsHeight = incrementTotalHeight(adapter, i, totalItemsHeight);
-            mTotalHeightCache[i + 1] = totalItemsHeight;
-        }
-        mLastValidHeightIndex = untilIndex;
-        return totalItemsHeight;
-    }
-
-    /**
-     * The current implementation assumes a linear list with every item taking up the whole row.
-     * Subclasses should override this method to account for any spanning logic
-     */
-    protected int incrementTotalHeight(Adapter adapter, int position, int heightUntilLastPos) {
-        return heightUntilLastPos + mCachedSizes.get(adapter.getItemViewType(position));
-    }
-
-    private void invalidateScrollCache() {
-        mLastValidHeightIndex = 0;
-    }
-
-    @Override
-    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
-        super.onItemsAdded(recyclerView, positionStart, itemCount);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsChanged(RecyclerView recyclerView) {
-        super.onItemsChanged(recyclerView);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
-        super.onItemsRemoved(recyclerView, positionStart, itemCount);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
-        super.onItemsMoved(recyclerView, from, to, itemCount);
-        invalidateScrollCache();
-    }
-
-    @Override
-    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
-            Object payload) {
-        super.onItemsUpdated(recyclerView, positionStart, itemCount, payload);
-        invalidateScrollCache();
-    }
-}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 3af2e3c..40e4ce1 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -119,6 +119,7 @@
     // prevent jumping, this offset is applied as the user scrolls.
     protected int mTouchOffsetY;
     protected int mThumbOffsetY;
+    protected int mRvOffsetY;
 
     // Fast scroller popup
     private TextView mPopupView;
@@ -206,11 +207,16 @@
 
     public void setThumbOffsetY(int y) {
         if (mThumbOffsetY == y) {
+            int rvCurrentOffsetY = mRv.getCurrentScrollY();
+            if (mRvOffsetY != rvCurrentOffsetY) {
+                mRvOffsetY = mRv.getCurrentScrollY();
+            }
             return;
         }
         updatePopupY(y);
         mThumbOffsetY = y;
         invalidate();
+        mRvOffsetY = mRv.getCurrentScrollY();
     }
 
     public int getThumbOffsetY() {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 5969e3e..35fa7a4 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.util.AttributeSet;
+import android.util.SparseIntArray;
 import android.view.MotionEvent;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -27,7 +28,6 @@
 
 import com.android.launcher3.FastScrollRecyclerView;
 import com.android.launcher3.R;
-import com.android.launcher3.util.ScrollableLayoutManager;
 
 /**
  * The widgets recycler view.
@@ -42,6 +42,14 @@
     private boolean mTouchDownOnScroller;
     private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
 
+    /**
+     * There is always 1 or 0 item of VIEW_TYPE_WIDGETS_LIST. Other types have fixes sizes, so the
+     * the size can be used for all other items of same type. Caching the last know size for
+     * VIEW_TYPE_WIDGETS_LIST allows us to use it to estimate full size even when
+     * VIEW_TYPE_WIDGETS_LIST is not visible on the screen.
+     */
+    private final SparseIntArray mCachedSizes = new SparseIntArray();
+
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -60,7 +68,9 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        setLayoutManager(new ScrollableLayoutManager(getContext()));
+        // create a layout manager with Launcher's context so that scroll position
+        // can be preserved during screen rotation.
+        setLayoutManager(new LinearLayoutManager(getContext()));
     }
 
     @Override
@@ -104,7 +114,7 @@
         }
 
         // Skip early if, there no child laid out in the container.
-        int scrollY = computeVerticalScrollOffset();
+        int scrollY = getCurrentScrollY();
         if (scrollY < 0) {
             mScrollbar.setThumbOffsetY(-1);
             return;
@@ -154,6 +164,39 @@
     }
 
     /**
+     * Returns the sum of the height, in pixels, of this list adapter's items from index 0 until
+     * {@code untilIndex}.
+     *
+     * <p>If the untilIndex is larger than the total number of items in this adapter, returns the
+     * sum of all items' height.
+     */
+    @Override
+    protected int getItemsHeight(int untilIndex) {
+        // Initialize cache
+        int childCount = getChildCount();
+        int startPosition;
+        if (childCount > 0
+                && ((startPosition = getChildAdapterPosition(getChildAt(0))) != NO_POSITION)) {
+            int loopCount = Math.min(getChildCount(), getAdapter().getItemCount() - startPosition);
+            for (int i = 0; i < loopCount; i++) {
+                mCachedSizes.put(
+                        mAdapter.getItemViewType(startPosition + i),
+                        getChildAt(i).getMeasuredHeight());
+            }
+        }
+
+        if (untilIndex > mAdapter.getItems().size()) {
+            untilIndex = mAdapter.getItems().size();
+        }
+        int totalItemsHeight = 0;
+        for (int i = 0; i < untilIndex; i++) {
+            int type = mAdapter.getItemViewType(i);
+            totalItemsHeight += mCachedSizes.get(type);
+        }
+        return totalItemsHeight;
+    }
+
+    /**
      * Provides dimensions of the header view that is shown at the top of a
      * {@link WidgetsRecyclerView}.
      */