Scroll to the selected widget picker row after headers are clicked

This keeps the row in view. Currently, this will scroll the row to the
centre-bottom of the screen due to the top padding being double counted,
but that will remedied in a future CL.

This also resolves the issue where the last row's widgets aren't visible
by handling that case specially

Fix: 188665456
Bug: 183378651
Test: verified locally
Change-Id: I9acb9087a8cdaf130ac5955c810c96462b368f36
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 7963431..3936ec8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -25,6 +25,7 @@
 import android.view.ViewGroup;
 import android.widget.TableRow;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.Adapter;
@@ -48,8 +49,10 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.OptionalInt;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Recycler view adapter for the widget tray.
@@ -87,6 +90,7 @@
                     || new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
                             .equals(mWidgetsContentVisiblePackageUserKey);
     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
+    @Nullable private RecyclerView mRecyclerView;
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
             WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
@@ -106,6 +110,16 @@
                         layoutInflater, /*onHeaderClickListener=*/ this, /* listAdapter= */ this));
     }
 
+    @Override
+    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+        mRecyclerView = recyclerView;
+    }
+
+    @Override
+    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+        mRecyclerView = null;
+    }
+
     public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
         mFilter = filter;
     }
@@ -168,12 +182,10 @@
         mAllEntries.forEach(entry -> {
             if (entry instanceof WidgetsListHeaderEntry) {
                 ((WidgetsListHeaderEntry) entry).setIsWidgetListShown(
-                        new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
-                                .equals(mWidgetsContentVisiblePackageUserKey));
+                        isHeaderForVisibleContent(entry));
             } else if (entry instanceof WidgetsListSearchHeaderEntry) {
                 ((WidgetsListSearchHeaderEntry) entry).setIsWidgetListShown(
-                        new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
-                                .equals(mWidgetsContentVisiblePackageUserKey));
+                        isHeaderForVisibleContent(entry));
             }
         });
         List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
@@ -183,6 +195,13 @@
         mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
     }
 
+    private boolean isHeaderForVisibleContent(WidgetsListBaseEntry entry) {
+        return (entry instanceof WidgetsListHeaderEntry
+                || entry instanceof WidgetsListSearchHeaderEntry)
+                && new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user)
+                .equals(mWidgetsContentVisiblePackageUserKey);
+    }
+
     /**
      * Resets any expanded widget header.
      */
@@ -247,12 +266,40 @@
         if (showWidgets) {
             mWidgetsContentVisiblePackageUserKey = packageUserKey;
             updateVisibleEntries();
+            // Scroll the layout manager to the header position to keep it anchored to the same
+            // position.
+            scrollToSelectedHeaderPosition();
         } else if (packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) {
             mWidgetsContentVisiblePackageUserKey = null;
             updateVisibleEntries();
         }
     }
 
+    private void scrollToSelectedHeaderPosition() {
+        OptionalInt selectedHeaderPosition =
+                IntStream.range(0, mVisibleEntries.size())
+                        .filter(index -> isHeaderForVisibleContent(mVisibleEntries.get(index)))
+                        .findFirst();
+        RecyclerView.LayoutManager layoutManager =
+                mRecyclerView == null ? null : mRecyclerView.getLayoutManager();
+        if (!selectedHeaderPosition.isPresent() || layoutManager == null) {
+            return;
+        }
+
+        // Scroll to the selected header position. LinearLayoutManager scrolls the minimum distance
+        // necessary, so this will keep the selected header in place during clicks, without
+        // interrupting the animation.
+        int position = selectedHeaderPosition.getAsInt();
+        if (position == mVisibleEntries.size() - 2) {
+            // If the selected header is in the last position (-1 for the content), then scroll to
+            // the final position so the last list of widgets will show.
+            layoutManager.scrollToPosition(mVisibleEntries.size() - 1);
+        } else {
+            // Otherwise, scroll to the position of the selected header.
+            layoutManager.scrollToPosition(position);
+        }
+    }
+
     /**
      * Sets the max horizontal spans that are allowed for grouping more than one widgets in a table
      * row.