Merge "Add TaskbarView as a child of Hotseat" into sc-dev
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f36439d..2671604 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -24,6 +24,8 @@
 
     <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
 
+    <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
+
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
diff --git a/go/quickstep/res/values/config.xml b/go/quickstep/res/values/config.xml
index 9dca137..a21381c 100644
--- a/go/quickstep/res/values/config.xml
+++ b/go/quickstep/res/values/config.xml
@@ -20,5 +20,5 @@
     <string name="niu_actions_package" translatable="false"/>
 
     <!-- Feature Flags -->
-    <bool name="enable_niu_actions">false</bool>
+    <bool name="enable_niu_actions">true</bool>
 </resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index b102a39..36a4e7f 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -20,11 +20,21 @@
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
 import android.annotation.SuppressLint;
+import android.app.ActivityTaskManager;
+import android.app.IAssistDataReceiver;
+import android.app.assist.AssistContent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.launcher3.R;
 import com.android.quickstep.views.OverviewActionsView;
@@ -40,6 +50,8 @@
     public static final String ACTION_TRANSLATE = "com.android.quickstep.ACTION_TRANSLATE";
     public static final String ACTION_SEARCH = "com.android.quickstep.ACTION_SEARCH";
     public static final String ELAPSED_NANOS = "niu_actions_elapsed_realtime_nanos";
+    public static final String ACTIONS_URL = "niu_actions_app_url";
+    private static final String TAG = "TaskOverlayFactoryGo";
 
     // Empty constructor required for ResourceBasedOverride
     public TaskOverlayFactoryGo(Context context) {}
@@ -56,11 +68,16 @@
      * @param <T> The type of View in which the overlay will be placed
      */
     public static final class TaskOverlayGo<T extends OverviewActionsView> extends TaskOverlay {
+        private static final String ASSIST_KEY_CONTENT = "content";
 
-        private String mPackageName;
+        private String mNIUPackageName;
+        private int mTaskId;
+        private Bundle mAssistData;
+        private final Handler mMainThreadHandler;
 
         private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
             super(taskThumbnailView);
+            mMainThreadHandler = new Handler(Looper.getMainLooper());
         }
 
         /**
@@ -70,16 +87,39 @@
         public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
                 boolean rotated) {
             getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
-            mPackageName =
+            mNIUPackageName =
                     mApplicationContext.getResources().getString(R.string.niu_actions_package);
 
-            if (thumbnail == null || TextUtils.isEmpty(mPackageName)) {
+            if (thumbnail == null || TextUtils.isEmpty(mNIUPackageName)) {
                 return;
             }
 
             getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
             boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
             getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
+
+            mTaskId = task.key.id;
+            AssistDataReceiverImpl receiver = new AssistDataReceiverImpl();
+            receiver.setOverlay(this);
+
+            try {
+                ActivityTaskManager.getService().requestAssistDataForTask(receiver, mTaskId,
+                        mApplicationContext.getPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to request AssistData");
+            }
+        }
+
+        /**
+         * Called when AssistDataReceiverImpl receives data from ActivityTaskManagerService's
+         * AssistDataRequester
+         */
+        public void onAssistDataReceived(Bundle data) {
+            mMainThreadHandler.post(() -> {
+                if (data != null) {
+                    mAssistData = data;
+                }
+            });
         }
 
         private void sendNIUIntent(String actionType) {
@@ -88,11 +128,21 @@
         }
 
         private Intent createNIUIntent(String actionType) {
-            return new Intent(actionType)
+            Intent intent = new Intent(actionType)
                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
                     .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
-                    .setPackage(mPackageName)
+                    .setPackage(mNIUPackageName)
                     .putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
+
+            if (mAssistData != null) {
+                final AssistContent content = mAssistData.getParcelable(ASSIST_KEY_CONTENT);
+                Uri webUri = (content == null) ? null : content.getWebUri();
+                if (webUri != null) {
+                    intent.putExtra(ACTIONS_URL, webUri.toString());
+                }
+            }
+
+            return intent;
         }
 
         protected class OverlayUICallbacksGoImpl extends OverlayUICallbacksImpl
@@ -131,6 +181,26 @@
     }
 
     /**
+     * Basic AssistDataReceiver. This is passed to ActivityTaskManagerService, which then requests
+     * the data.
+     */
+    private static final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
+        private TaskOverlayGo mOverlay;
+
+        public void setOverlay(TaskOverlayGo overlay) {
+            mOverlay = overlay;
+        }
+
+        @Override
+        public void onHandleAssistData(Bundle data) {
+            mOverlay.onAssistDataReceived(data);
+        }
+
+        @Override
+        public void onHandleAssistScreenshot(Bitmap screenshot) {}
+    }
+
+    /**
      * Callbacks the Ui can generate. This is the only way for a Ui to call methods on the
      * controller.
      */
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index 585b6ad..38c9919 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
 filegroup {
     name: "launcher3-quickstep-robolectric-src",
     path: "robolectric_tests",
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index b1b4d70..df3657d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -73,6 +73,7 @@
 
     public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
     private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
+    private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
 
     private static final boolean IS_DEBUG = false;
     private static final String TAG = "QuickstepModelDelegate";
@@ -217,7 +218,7 @@
         registerWidgetsPredictor(apm.createAppPredictionSession(
                 new AppPredictionContext.Builder(context)
                         .setUiSurface("widgets")
-                        .setPredictedTargetCount(mIDP.numColumns)
+                        .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
                         .build()));
     }
 
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6da2201..b492825 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -617,7 +617,7 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (!mDeviceState.isTwoButtonNavMode()) {
+            if (mDeviceState.isTwoButtonNavMode()) {
                 performHapticFeedback();
             }
         }
@@ -1334,7 +1334,11 @@
     }
 
     private void invalidateHandler() {
-        mInputConsumerProxy.destroy();
+        if (!LIVE_TILE.get() || !mActivityInterface.isInLiveTileMode()
+                || mGestureState.getEndTarget() != RECENTS) {
+            mInputConsumerProxy.destroy();
+            mTaskAnimationManager.setLiveTileCleanUpHandler(null);
+        }
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
@@ -1526,6 +1530,7 @@
                         apps[apps.length - 1] = appearedTaskTarget;
                         launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
                     });
+            mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
             ActivityManagerWrapper.getInstance().registerTaskStackListener(
                     mLiveTileRestartListener);
         }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 02c2763..9a454f2 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -50,6 +50,7 @@
     private GestureState mLastGestureState;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
     private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
+    private Runnable mLiveTileCleanUpHandler;
     private Context mCtx;
 
     TaskAnimationManager(Context ctx) {
@@ -169,6 +170,10 @@
         mLaunchOtherTaskHandler = handler;
     }
 
+    public void setLiveTileCleanUpHandler(Runnable runnable) {
+        mLiveTileCleanUpHandler = runnable;
+    }
+
     /**
      * Finishes the running recents animation.
      */
@@ -206,6 +211,11 @@
      * Cleans up the recents animation entirely.
      */
     private void cleanUpRecentsAnimation() {
+        if (mLiveTileCleanUpHandler != null) {
+            mLiveTileCleanUpHandler.run();
+            mLiveTileCleanUpHandler = null;
+        }
+
         // Release all the target leashes
         if (mTargets != null) {
             mTargets.release();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4747f18..1fb9465 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -887,6 +887,9 @@
             if (mGestureState != null) {
                 mGestureState.dump(pw);
             }
+            pw.println("Input state:");
+            pw.println("  mInputMonitorCompat=" + mInputMonitorCompat);
+            pw.println("  mInputEventReceiver=" + mInputEventReceiver);
             SysUINavigationMode.INSTANCE.get(this).dump(pw);
             pw.println("TouchState:");
             BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index e042b35..a0af68a 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -101,7 +101,7 @@
     @Override
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
-            mActivity.getStateManager().goToState(NORMAL, false /* animate */);
+            mActivity.getStateManager().moveToRestState();
         } else {
             LauncherState state = mActivity.getStateManager().getState();
             mActivity.getAllAppsController().setState(state);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7adfc1c..f216985 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1018,6 +1018,7 @@
             mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+            mLiveTileTaskViewSimulator.gridProgress.value = 0;
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1740,6 +1741,7 @@
         for (int i = 0; i < taskCount; i++) {
             getTaskViewAt(i).setGridProgress(gridProgress);
         }
+        mLiveTileTaskViewSimulator.gridProgress.value = gridProgress;
         mClearAllButton.setGridProgress(gridProgress);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 809adcb..2b7e6fd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -298,7 +298,6 @@
     private boolean mEndQuickswitchCuj;
 
     private View mContextualChipWrapper;
-    private View mContextualChip;
     private final float[] mIconCenterCoords = new float[2];
     private final float[] mChipCenterCoords = new float[2];
 
@@ -447,9 +446,9 @@
         }
         mModalness = modalness;
         mIconView.setAlpha(comp(modalness));
-        if (mContextualChip != null) {
-            mContextualChip.setScaleX(comp(modalness));
-            mContextualChip.setScaleY(comp(modalness));
+        if (mContextualChipWrapper != null) {
+            mContextualChipWrapper.setScaleX(comp(modalness));
+            mContextualChipWrapper.setScaleY(comp(modalness));
         }
         mDigitalWellBeingToast.updateBannerOffset(modalness,
                 mCurrentFullscreenParams.mCurrentDrawnInsets.top
@@ -689,10 +688,10 @@
                 .getInterpolation(progress);
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
-        if (mContextualChip != null && mContextualChipWrapper != null) {
+        if (mContextualChipWrapper != null && mContextualChipWrapper != null) {
             mContextualChipWrapper.setAlpha(scale);
-            mContextualChip.setScaleX(scale);
-            mContextualChip.setScaleY(scale);
+            mContextualChipWrapper.setScaleX(Math.min(scale, comp(mModalness)));
+            mContextualChipWrapper.setScaleY(Math.min(scale, comp(mModalness)));
         }
         mDigitalWellBeingToast.updateBannerOffset(1f - scale,
                 mCurrentFullscreenParams.mCurrentDrawnInsets.top
@@ -797,14 +796,12 @@
             int expectedChipHeight = getExpectedViewHeight(view);
             float chipOffset = getResources().getDimension(R.dimen.chip_hint_vertical_offset);
             layoutParams.bottomMargin = -expectedChipHeight - (int) chipOffset;
-            mContextualChip = ((FrameLayout) mContextualChipWrapper).getChildAt(0);
-            mContextualChip.setScaleX(0f);
-            mContextualChip.setScaleY(0f);
+            mContextualChipWrapper.setScaleX(0f);
+            mContextualChipWrapper.setScaleY(0f);
             addView(view, getChildCount(), layoutParams);
-            if (mContextualChip != null) {
-                mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
-            }
             if (mContextualChipWrapper != null) {
+                float scale = comp(mModalness);
+                mContextualChipWrapper.animate().scaleX(scale).scaleY(scale).setDuration(50);
                 mChipTouchDelegate = new TransformingTouchDelegate(mContextualChipWrapper);
             }
         }
@@ -825,7 +822,6 @@
         }
         View oldContextualChipWrapper = mContextualChipWrapper;
         mContextualChipWrapper = null;
-        mContextualChip = null;
         mChipTouchDelegate = null;
         return oldContextualChipWrapper;
     }
diff --git a/res/drawable/middle_item_primary.xml b/res/drawable/middle_item_primary.xml
index c975714..0c04ea1 100644
--- a/res/drawable/middle_item_primary.xml
+++ b/res/drawable/middle_item_primary.xml
@@ -16,5 +16,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <solid android:color="?attr/popupColorPrimary"/>
-    <corners android:radius="@dimen/popup_middle_item_radius" />
+    <corners android:radius="@dimen/popup_smaller_radius" />
 </shape>
\ No newline at end of file
diff --git a/res/drawable/widgets_list_single_item_ripple.xml b/res/drawable/widgets_list_single_item_ripple.xml
new file mode 100644
index 0000000..b8b6f42
--- /dev/null
+++ b/res/drawable/widgets_list_single_item_ripple.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, 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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+        </shape>
+    </item>
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorBackground" />
+            <corners
+                android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 7b85d9b..a3d0070 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -18,10 +18,11 @@
     android:layout_height="wrap_content">
 
     <!-- The image of the widget. This view does not support padding. Any placement adjustment
-         should be done using margins. -->
+         should be done using margins.
+          width & height are set at runtime after scaling the preview image. -->
     <com.android.launcher3.widget.WidgetImageView
         android:id="@+id/widget_preview"
-        android:layout_width="wrap_content"
+        android:layout_width="0dp"
         android:layout_height="0dp"
         android:layout_weight="1"
         android:importantForAccessibility="no"
@@ -38,7 +39,7 @@
         android:singleLine="true"
         android:maxLines="1"
         android:textColor="?android:attr/textColorPrimary"
-        android:textSize="14sp" />
+        android:textSize="@dimen/widget_cell_font_size" />
 
     <!-- The original dimensions of the widget (can't be the same text as above due to different
          style. -->
@@ -48,7 +49,7 @@
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
         android:textColor="?android:attr/textColorTertiary"
-        android:textSize="14sp"
+        android:textSize="@dimen/widget_cell_font_size"
         android:alpha="0.8" />
 
     <TextView
@@ -56,7 +57,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:textSize="14sp"
+        android:textSize="@dimen/widget_cell_font_size"
         android:textColor="?android:attr/textColorTertiary"
         android:maxLines="2"
         android:ellipsize="end"
diff --git a/res/layout/widgets_bottom_sheet.xml b/res/layout/widgets_bottom_sheet.xml
index d18ba56..8002d1d 100644
--- a/res/layout/widgets_bottom_sheet.xml
+++ b/res/layout/widgets_bottom_sheet.xml
@@ -24,41 +24,7 @@
     android:elevation="@dimen/deep_shortcuts_elevation"
     android:layout_gravity="bottom"
     android:theme="?attr/widgetsTheme">
-    <View
-        android:layout_width="48dp"
-        android:layout_height="2dp"
-        android:layout_gravity="center_horizontal"
-        android:layout_marginBottom="16dp"
-        android:background="?android:attr/textColorSecondary"/>
-    <TextView
-        style="@style/TextHeadline"
-        android:id="@+id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="24sp"/>
 
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:paddingTop="4dp"
-        android:fontFamily="sans-serif"
-        android:textColor="?android:attr/textColorTertiary"
-        android:textSize="14sp"
-        android:text="@string/long_press_widget_to_add"/>
-
-    <ScrollView
-        android:id="@+id/widgets_table_scroll_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fadeScrollbars="false"
-        android:layout_marginVertical="16dp">
-        <include layout="@layout/widgets_table_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_horizontal" />
-    </ScrollView>
+    <include layout="@layout/widgets_bottom_sheet_content" />
 
 </com.android.launcher3.widget.WidgetsBottomSheet>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
new file mode 100644
index 0000000..a9d523a
--- /dev/null
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <View
+        android:layout_width="48dp"
+        android:layout_height="2dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginBottom="16dp"
+        android:background="?android:attr/textColorSecondary"/>
+    <TextView
+        style="@style/TextHeadline"
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="24sp"/>
+
+    <ScrollView
+        android:id="@+id/widgets_table_scroll_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fadeScrollbars="false"
+        android:layout_marginVertical="16dp">
+        <include layout="@layout/widgets_table_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal" />
+    </ScrollView>
+</merge>
diff --git a/res/layout/widgets_full_sheet_search_and_recommendations.xml b/res/layout/widgets_full_sheet_search_and_recommendations.xml
index 1219f57..e5df175 100644
--- a/res/layout/widgets_full_sheet_search_and_recommendations.xml
+++ b/res/layout/widgets_full_sheet_search_and_recommendations.xml
@@ -18,12 +18,15 @@
     android:id="@+id/search_and_recommendations_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="16dp"
+    android:paddingHorizontal="16dp"
+    android:layout_marginBottom="16dp"
     android:orientation="vertical">
     <View
         android:id="@+id/collapse_handle"
         android:layout_width="48dp"
         android:layout_height="2dp"
+        android:layout_marginTop="16dp"
+        android:elevation="2dp"
         android:layout_gravity="center_horizontal"
         android:background="?android:attr/textColorSecondary"/>
     <TextView
@@ -36,4 +39,11 @@
         android:textColor="?android:attr/textColorSecondary"
         android:text="@string/widget_button_text"/>
     <include layout="@layout/widgets_search_bar"/>
+
+    <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+        android:id="@+id/recommended_widget_table"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:visibility="gone" />
 </LinearLayout>
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
index 021058b..1db7462 100644
--- a/res/layout/widgets_search_bar.xml
+++ b/res/layout/widgets_search_bar.xml
@@ -6,7 +6,8 @@
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:layout_marginTop="16dp"
-    android:background="@drawable/bg_widgets_searchbox">
+    android:background="@drawable/bg_widgets_searchbox"
+    android:elevation="2dp">
 
     <com.android.launcher3.ExtendedEditText
         android:id="@+id/widgets_search_bar_edit_text"
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 6baf39e..24aac10 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -17,17 +17,17 @@
 */
 -->
 <resources>
-    <color name="popup_color_primary_light">@android:color/system_primary_50</color>
-    <color name="popup_color_secondary_light">@android:color/system_primary_100</color>
-    <color name="popup_color_tertiary_light">@android:color/system_primary_300</color>
-    <color name="popup_color_primary_dark">@android:color/system_primary_800</color>
-    <color name="popup_color_secondary_dark">@android:color/system_primary_900</color>
-    <color name="popup_color_tertiary_dark">@android:color/system_primary_700</color>
+    <color name="popup_color_primary_light">@android:color/system_neutral1_50</color>
+    <color name="popup_color_secondary_light">@android:color/system_neutral2_100</color>
+    <color name="popup_color_tertiary_light">@android:color/system_neutral2_300</color>
+    <color name="popup_color_primary_dark">@android:color/system_neutral1_800</color>
+    <color name="popup_color_secondary_dark">@android:color/system_neutral1_900</color>
+    <color name="popup_color_tertiary_dark">@android:color/system_neutral2_700</color>
 
-    <color name="workspace_text_color_light">@android:color/system_primary_50</color>
-    <color name="workspace_text_color_dark">@android:color/system_primary_900</color>
+    <color name="workspace_text_color_light">@android:color/system_neutral1_50</color>
+    <color name="workspace_text_color_dark">@android:color/system_neutral1_900</color>
 
-    <color name="text_color_primary_dark">@android:color/system_primary_50</color>
-    <color name="text_color_secondary_dark">@android:color/system_primary_200</color>
-    <color name="text_color_tertiary_dark">@android:color/system_primary_400</color>
+    <color name="text_color_primary_dark">@android:color/system_neutral1_50</color>
+    <color name="text_color_secondary_dark">@android:color/system_neutral2_200</color>
+    <color name="text_color_tertiary_dark">@android:color/system_neutral2_400</color>
 </resources>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c607c98..9d6c936 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -109,6 +109,7 @@
 <!-- Widget tray -->
     <dimen name="widget_cell_vertical_padding">8dp</dimen>
     <dimen name="widget_cell_horizontal_padding">16dp</dimen>
+    <dimen name="widget_cell_font_size">14sp</dimen>
 
 
     <dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
@@ -186,14 +187,13 @@
     <dimen name="deep_shortcuts_elevation">0dp</dimen>
     <dimen name="bg_popup_item_width">234dp</dimen>
     <dimen name="bg_popup_item_height">56dp</dimen>
-    <dimen name="bg_popup_item_condensed_height">48dp</dimen>
     <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
     <dimen name="deep_shortcut_icon_size">32dp</dimen>
     <dimen name="popup_margin">2dp</dimen>
     <dimen name="popup_single_item_radius">100dp</dimen>
-    <dimen name="popup_middle_item_radius">4dp</dimen>
+    <dimen name="popup_smaller_radius">4dp</dimen>
     <dimen name="deep_shortcut_drawable_padding">12dp</dimen>
     <dimen name="deep_shortcut_drag_handle_size">16dp</dimen>
     <dimen name="popup_padding_start">10dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1eb123b..7c372830 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -269,8 +269,6 @@
     <string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
 
     <!-- Strings for widgets & more in the popup container/bottom sheet -->
-    <!-- Title for a bottom sheet that shows widgets for a particular app -->
-    <string name="widgets_bottom_sheet_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> widgets</string>
 
     <!-- Accessibility title for the popup containing a list of widgets. [CHAR_LIMIT=50] -->
     <string name="widgets_list">Widgets list</string>
diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp
index 50309b7..bf32362 100644
--- a/robolectric_tests/Android.bp
+++ b/robolectric_tests/Android.bp
@@ -16,6 +16,15 @@
 // Launcher Robolectric test target.
 //
 //        "robolectric_android-all-stub", not needed, we write our own stubs
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
 filegroup {
     name: "launcher3-robolectric-resources",
     path: "resources",
@@ -47,4 +56,3 @@
         timeout: 36000,
     },
 }
-
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 8071782..d02efb0 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -100,6 +100,8 @@
     private int mRunningVInc;
     private int mMinHSpan;
     private int mMinVSpan;
+    private int mMaxHSpan;
+    private int mMaxVSpan;
     private int mDeltaX;
     private int mDeltaY;
     private int mDeltaXAddOn;
@@ -183,6 +185,8 @@
 
         mMinHSpan = info.minSpanX;
         mMinVSpan = info.minSpanY;
+        mMaxHSpan = info.maxSpanX;
+        mMaxVSpan = info.maxSpanY;
 
         mWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(getContext(),
                 widgetView.getAppWidgetInfo().provider, null);
@@ -314,7 +318,7 @@
         // expandability.
         mTempRange1.set(cellX, spanX + cellX);
         int hSpanDelta = mTempRange1.applyDeltaAndBound(mLeftBorderActive, mRightBorderActive,
-                hSpanInc, mMinHSpan, mCellLayout.getCountX(), mTempRange2);
+                hSpanInc, mMinHSpan, mMaxHSpan, mCellLayout.getCountX(), mTempRange2);
         cellX = mTempRange2.start;
         spanX = mTempRange2.size();
         if (hSpanDelta != 0) {
@@ -323,7 +327,7 @@
 
         mTempRange1.set(cellY, spanY + cellY);
         int vSpanDelta = mTempRange1.applyDeltaAndBound(mTopBorderActive, mBottomBorderActive,
-                vSpanInc, mMinVSpan, mCellLayout.getCountY(), mTempRange2);
+                vSpanInc, mMinVSpan, mMaxVSpan, mCellLayout.getCountY(), mTempRange2);
         cellY = mTempRange2.start;
         spanY = mTempRange2.size();
         if (vSpanDelta != 0) {
@@ -642,12 +646,15 @@
          * @param minSize minimum size after with the moving edge should not be shifted any further.
          *                For eg, if delta = -3 when moving the endEdge brings the size to less than
          *                minSize, only delta = -2 will applied
+         * @param maxSize maximum size after with the moving edge should not be shifted any further.
+         *                For eg, if delta = -3 when moving the endEdge brings the size to greater
+         *                than maxSize, only delta = -2 will applied
          * @param maxEnd The maximum value to the end edge (start edge is always restricted to 0)
          * @return the amount of increase when endEdge was moves and the amount of decrease when
          * the start edge was moved.
          */
         public int applyDeltaAndBound(boolean moveStart, boolean moveEnd, int delta,
-                int minSize, int maxEnd, IntRange out) {
+                int minSize, int maxSize, int maxEnd, IntRange out) {
             applyDelta(moveStart, moveEnd, delta, out);
             if (out.start < 0) {
                 out.start = 0;
@@ -662,6 +669,13 @@
                     out.end = out.start + minSize;
                 }
             }
+            if (out.size() > maxSize) {
+                if (moveStart) {
+                    out.start = out.end - maxSize;
+                } else if (moveEnd) {
+                    out.end = out.start + maxSize;
+                }
+            }
             return moveEnd ? out.size() - size() : size() - out.size();
         }
     }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 459b9a8..4740079 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -21,14 +21,9 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.animation.AnimatorSet;
-import android.animation.FloatArrayEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
@@ -45,10 +40,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.Thunk;
 
 /**
  * Implements a DropTarget.
@@ -72,6 +64,7 @@
 
     private static final int[] sTempCords = new int[2];
     private static final int DRAG_VIEW_DROP_DURATION = 285;
+    private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
 
     public static final int TOOLTIP_DEFAULT = 0;
     public static final int TOOLTIP_LEFT = 1;
@@ -89,9 +82,6 @@
     /** An item must be dragged at least this many pixels before this drop target is enabled. */
     private final int mDragDistanceThreshold;
 
-    /** The paint applied to the drag view on hover */
-    protected int mHoverColor = 0;
-
     protected CharSequence mText;
     protected ColorStateList mOriginalTextColor;
     protected Drawable mDrawable;
@@ -101,7 +91,6 @@
     private int mToolTipLocation;
 
     private AnimatorSet mCurrentColorAnim;
-    @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter;
 
     public ButtonDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -177,8 +166,7 @@
             mToolTip.showAsDropDown(this, x, y);
         }
 
-        d.dragView.setColor(mHoverColor);
-        animateTextColor(mHoverColor);
+        d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.cancel();
         }
@@ -190,51 +178,15 @@
         // Do nothing
     }
 
-    protected void resetHoverColor() {
-        animateTextColor(mOriginalTextColor.getDefaultColor());
-    }
-
-    private void animateTextColor(int targetColor) {
-        if (mCurrentColorAnim != null) {
-            mCurrentColorAnim.cancel();
-        }
-
-        mCurrentColorAnim = new AnimatorSet();
-        mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION);
-
-        if (mSrcFilter == null) {
-            mSrcFilter = new ColorMatrix();
-            mDstFilter = new ColorMatrix();
-            mCurrentFilter = new ColorMatrix();
-        }
-
-        int defaultTextColor = mOriginalTextColor.getDefaultColor();
-        Themes.setColorChangeOnMatrix(defaultTextColor, getTextColor(), mSrcFilter);
-        Themes.setColorChangeOnMatrix(defaultTextColor, targetColor, mDstFilter);
-
-        ValueAnimator anim1 = ValueAnimator.ofObject(
-                new FloatArrayEvaluator(mCurrentFilter.getArray()),
-                mSrcFilter.getArray(), mDstFilter.getArray());
-        anim1.addUpdateListener((anim) -> {
-            mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
-            invalidate();
-        });
-
-        mCurrentColorAnim.play(anim1);
-        mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, TEXT_COLOR, targetColor));
-        mCurrentColorAnim.start();
-    }
-
     @Override
     public final void onDragExit(DragObject d) {
         hideTooltip();
 
         if (!d.dragComplete) {
             d.dragView.setColor(0);
-            resetHoverColor();
+            d.dragView.setAlpha(1f);
         } else {
-            // Restore the hover color
-            d.dragView.setColor(mHoverColor);
+            d.dragView.setAlpha(DRAG_VIEW_HOVER_OVER_OPACITY);
         }
     }
 
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index cc119c9..e46aad2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -53,9 +53,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        // Get the hover color
-        mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
-
         setDrawable(R.drawable.ic_remove_shadow);
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 02571a0..fa19ee6 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.ResourceUtils.pxFromDp;
+import static com.android.launcher3.Utilities.dpiFromPx;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -38,6 +39,8 @@
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.WindowBounds;
 
+import java.io.PrintWriter;
+
 public class DeviceProfile {
 
     private static final float TABLET_MIN_DPS = 600;
@@ -91,8 +94,10 @@
     public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
+    private final int extraSpace;
     public int workspaceTopPadding;
     public int workspaceBottomPadding;
+    public int extraHotseatBottomPadding;
 
     // Workspace page indicator
     public final int workspacePageIndicatorHeight;
@@ -109,7 +114,6 @@
     public int workspaceCellPaddingXPx;
 
     public int cellYPaddingPx;
-    public int cellYPaddingOriginalPx;
 
     // Folder
     public float folderLabelTextScale;
@@ -199,8 +203,7 @@
         mInfo = info;
 
         // Constants from resources
-        float swDPs = Utilities.dpiFromPx(
-                Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
+        float swDPs = dpiFromPx(Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
         boolean allowRotation = context.getResources().getBoolean(R.bool.allow_rotation);
         // Tablet UI is built with assumption that simulated landscape is disabled.
         isTablet = allowRotation && swDPs >= TABLET_MIN_DPS;
@@ -295,22 +298,22 @@
                         + (isScalableGrid ? 0 : hotseatExtraVerticalSize)));
 
         // Calculate all of the remaining variables.
-        int extraSpace = updateAvailableDimensions(res);
+        extraSpace = updateAvailableDimensions(res);
         // Now that we have all of the variables calculated, we can tune certain sizes.
         if (isScalableGrid) {
             DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace);
             workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace);
             workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace);
 
-            float hotseatBarBottomPadding = padding.getHotseatBottomPadding(extraSpace);
-            hotseatBarSizePx += hotseatBarBottomPadding;
-            hotseatBarBottomPaddingPx += hotseatBarBottomPadding;
+            extraHotseatBottomPadding = padding.getHotseatBottomPadding(extraSpace);
+            hotseatBarSizePx += extraHotseatBottomPadding;
+            hotseatBarBottomPaddingPx += extraHotseatBottomPadding;
         } else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
             // We increase the hotseat size when there is extra space.
             // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
             // in portrait mode closer together by adding more height to the hotseat.
             // Note: This calculation was created after noticing a pattern in the design spec.
-            extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
+            int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
                     - workspacePageIndicatorHeight;
             hotseatBarSizePx += extraSpace;
             hotseatBarBottomPaddingPx += extraSpace;
@@ -794,6 +797,93 @@
         }
     }
 
+    private String pxToDpStr(String name, float value) {
+        return "\t" + name + ": " + value + "px (" + dpiFromPx(value, mInfo.metrics) + "dp)";
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + "DeviceProfile:");
+        writer.println(prefix + "\t1 dp = " + mInfo.metrics.density + " px");
+
+        writer.println(prefix + "\tisTablet:" + isTablet);
+        writer.println(prefix + "\tisLargeTablet:" + isLargeTablet);
+        writer.println(prefix + "\tisPhone:" + isPhone);
+        writer.println(prefix + "\ttransposeLayoutWithOrientation:"
+                + transposeLayoutWithOrientation);
+
+        writer.println(prefix + "\tisLandscape:" + isLandscape);
+        writer.println(prefix + "\tisMultiWindowMode:" + isMultiWindowMode);
+
+        writer.println(prefix + pxToDpStr("windowX", windowX));
+        writer.println(prefix + pxToDpStr("windowY", windowY));
+        writer.println(prefix + pxToDpStr("widthPx", widthPx));
+        writer.println(prefix + pxToDpStr("heightPx", heightPx));
+
+        writer.println(prefix + pxToDpStr("availableWidthPx", availableWidthPx));
+        writer.println(prefix + pxToDpStr("availableHeightPx", availableHeightPx));
+
+        writer.println(prefix + "\taspectRatio:" + aspectRatio);
+
+        writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
+
+        writer.println(prefix + "\tinv.minCellWidth:" + inv.minCellWidth + "dp");
+        writer.println(prefix + "\tinv.minCellHeight:" + inv.minCellHeight + "dp");
+
+        writer.println(prefix + pxToDpStr("cellWidthPx", cellWidthPx));
+        writer.println(prefix + pxToDpStr("cellHeightPx", cellHeightPx));
+
+        writer.println(prefix + pxToDpStr("getCellSize().x", getCellSize().x));
+        writer.println(prefix + pxToDpStr("getCellSize().y", getCellSize().y));
+
+        writer.println(prefix + "\tinv.iconSize:" + inv.iconSize + "dp");
+        writer.println(prefix + pxToDpStr("iconSizePx", iconSizePx));
+        writer.println(prefix + pxToDpStr("iconTextSizePx", iconTextSizePx));
+        writer.println(prefix + pxToDpStr("iconDrawablePaddingPx", iconDrawablePaddingPx));
+
+        writer.println(prefix + pxToDpStr("folderCellWidthPx", folderCellWidthPx));
+        writer.println(prefix + pxToDpStr("folderCellHeightPx", folderCellHeightPx));
+        writer.println(prefix + pxToDpStr("folderChildIconSizePx", folderChildIconSizePx));
+        writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
+        writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
+                folderChildDrawablePaddingPx));
+
+        writer.println(prefix + pxToDpStr("cellLayoutBorderSpacingPx",
+                cellLayoutBorderSpacingPx));
+        writer.println(prefix + pxToDpStr("desiredWorkspaceLeftRightMarginPx",
+                desiredWorkspaceLeftRightMarginPx));
+
+        writer.println(prefix + pxToDpStr("allAppsIconSizePx", allAppsIconSizePx));
+        writer.println(prefix + pxToDpStr("allAppsIconTextSizePx", allAppsIconTextSizePx));
+        writer.println(prefix + pxToDpStr("allAppsIconDrawablePaddingPx",
+                allAppsIconDrawablePaddingPx));
+        writer.println(prefix + pxToDpStr("allAppsCellHeightPx", allAppsCellHeightPx));
+
+        writer.println(prefix + pxToDpStr("hotseatBarSizePx", hotseatBarSizePx));
+        writer.println(prefix + pxToDpStr("hotseatCellHeightPx", hotseatCellHeightPx));
+        writer.println(prefix + pxToDpStr("hotseatBarTopPaddingPx", hotseatBarTopPaddingPx));
+        writer.println(prefix + pxToDpStr("hotseatBarBottomPaddingPx", hotseatBarBottomPaddingPx));
+        writer.println(prefix + pxToDpStr("hotseatBarSidePaddingStartPx",
+                hotseatBarSidePaddingStartPx));
+        writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
+                hotseatBarSidePaddingEndPx));
+
+        writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
+
+        writer.println(prefix + pxToDpStr("taskbarSize", taskbarSize));
+        writer.println(prefix + pxToDpStr("nonOverlappingTaskbarInset",
+                nonOverlappingTaskbarInset));
+
+        writer.println(prefix + pxToDpStr("workspacePadding.left", workspacePadding.left));
+        writer.println(prefix + pxToDpStr("workspacePadding.top", workspacePadding.top));
+        writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
+        writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
+
+        writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
+        writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
+        writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
+        writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
+    }
+
     private static Context getContext(Context c, Info info, int orientation) {
         Configuration config = new Configuration(c.getResources().getConfiguration());
         config.orientation = orientation;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c57f621..89c0f66 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2634,6 +2634,7 @@
         mDragLayer.dump(prefix, writer);
         mStateManager.dump(prefix, writer);
         mPopupDataProvider.dump(prefix, writer);
+        mDeviceProfile.dump(prefix, writer);
 
         try {
             FileLog.flushAll(writer);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index b084eb1..72eff62 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * An abstraction of the original Workspace which supports browsing through a
@@ -304,6 +305,21 @@
     }
 
     /**
+     * Returns the currently visible pages.
+     */
+    protected Iterable<View> getVisiblePages() {
+        int panelCount = getPanelCount();
+        List<View> visiblePages = new ArrayList<>(panelCount);
+        for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
+            View page = getPageAt(i);
+            if (page != null) {
+                visiblePages.add(page);
+            }
+        }
+        return visiblePages;
+    }
+
+    /**
      * Returns true if the view is on one of the current pages, false otherwise.
      */
     public boolean isVisible(View child) {
@@ -1052,10 +1068,7 @@
         // Try canceling the long press. It could also have been scheduled
         // by a distant descendant, so use the mAllowLongPress flag to block
         // everything
-        final View currentPage = getPageAt(mCurrentPage);
-        if (currentPage != null) {
-            currentPage.cancelLongPress();
-        }
+        getVisiblePages().forEach(View::cancelLongPress);
     }
 
     protected float getScrollProgress(int screenCenter, View v, int page) {
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 8bc5ad0..858b72e 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -44,7 +44,6 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
 import java.net.URISyntaxException;
@@ -109,15 +108,12 @@
         mCurrentAccessibilityAction = action;
 
         if (action == UNINSTALL) {
-            mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
             setDrawable(R.drawable.ic_uninstall_shadow);
             updateText(R.string.uninstall_drop_target_label);
         } else if (action == DISMISS_PREDICTION) {
-            mHoverColor = Themes.getColorAccent(getContext());
             setDrawable(R.drawable.ic_block_shadow);
             updateText(R.string.dismiss_prediction_label);
         } else if (action == RECONFIGURE) {
-            mHoverColor = Themes.getColorAccent(getContext());
             setDrawable(R.drawable.ic_setup_shadow);
             updateText(R.string.gadget_setup_text);
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 78c404f..591de04 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
@@ -63,7 +62,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -564,37 +562,9 @@
     /**
      * Handles selection on focused view and returns success
      */
-    public boolean selectFocusedView(View v) {
-        ItemInfo headerItem = getHighlightedItemFromHeader();
-        if (headerItem != null) {
-            return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
-        }
-        AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
-        if (focusedItem != null) {
-            View focusedView = getActiveRecyclerView().getLayoutManager()
-                    .findViewByPosition(focusedItem.position);
-            if (focusedView != null && mSearchAdapterProvider.onAdapterItemSelected(focusedItem,
-                    focusedView)) {
-                return true;
-            }
-        }
-        if (focusedItem != null && focusedItem.appInfo != null) {
-            ItemInfo itemInfo = focusedItem.appInfo;
-            return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
-        }
-        return false;
-    }
-
-    /**
-     * Returns the ItemInfo of a focused view inside {@link FloatingHeaderView}
-     */
-    public ItemInfo getHighlightedItemFromHeader() {
-        View view = getFloatingHeaderView().getFocusedChild();
-        if (view != null && view.getTag() instanceof ItemInfo) {
-            return ((ItemInfo) view.getTag());
-        }
-
-        return null;
+    public boolean launchHighlightedItem() {
+        if (mSearchAdapterProvider == null) return false;
+        return mSearchAdapterProvider.launchHighlightedItem();
     }
 
     public SearchAdapterProvider getSearchAdapterProvider() {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index f307a53..66575eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -189,6 +189,7 @@
             case SCROLL_STATE_DRAGGING:
                 mgr.logger().sendToInteractionJankMonitor(
                         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+                requestFocus();
                 getWindowInsetsController().hide(WindowInsets.Type.ime());
                 break;
             case SCROLL_STATE_IDLE:
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index 269e390..0bd2f44 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -22,13 +22,12 @@
 import android.graphics.RectF;
 import android.view.View;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
+import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.allapps.search.SectionDecorationInfo;
 import com.android.launcher3.util.Themes;
 
@@ -48,6 +47,7 @@
     @Override
     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
         List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
+        SearchAdapterProvider adapterProvider = mAppsView.getSearchAdapterProvider();
         for (int i = 0; i < parent.getChildCount(); i++) {
             View view = parent.getChildAt(i);
             int position = parent.getChildAdapterPosition(view);
@@ -56,7 +56,7 @@
                 SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
                 SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
                 if (decorationHandler != null) {
-                    if (sectionInfo.isFocusedView()) {
+                    if (view.equals(adapterProvider.getHighlightedItem())) {
                         decorationHandler.onFocusDraw(c, view);
                     } else {
                         decorationHandler.onGroupDraw(c, view);
@@ -66,28 +66,6 @@
         }
     }
 
-    // Fallback logic in case non of the SearchTarget is labeled as focused item.
-    private void drawDecoration(@NonNull Canvas c,
-            @NonNull SectionDecorationHandler decorationHandler,
-            @NonNull RecyclerView parent) {
-        if (decorationHandler.mIsFullWidth) {
-            decorationHandler.mBounds.left = parent.getPaddingLeft();
-            decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight();
-        }
-        if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
-                && mAppsView.getApps().getFocusedChild() != null) {
-            int index = mAppsView.getApps().getFocusedChildIndex();
-            AppsGridLayoutManager layoutManager = (AppsGridLayoutManager)
-                    mAppsView.getActiveRecyclerView().getLayoutManager();
-            if (layoutManager.findFirstVisibleItemPosition() <= index
-                    && index < parent.getChildCount()) {
-                RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(index);
-                if (vh != null) decorationHandler.onFocusDraw(c, vh.itemView);
-            }
-        }
-        decorationHandler.reset();
-    }
-
     /**
      * Handles grouping and drawing of items in the same all apps sections.
      */
@@ -102,7 +80,7 @@
         private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         private final boolean mIsTopRound;
         private final boolean mIsBottomRound;
-        private float [] mCorners;
+        private float[] mCorners;
         private float mFillSpacing;
 
         public SectionDecorationHandler(Context context, boolean isFullWidth, int fillAlpha,
@@ -134,7 +112,6 @@
          */
         public void onGroupDraw(Canvas canvas, View view) {
             if (view == null) return;
-
             mPaint.setColor(mFillcolor);
             mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
             onDraw(canvas);
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index d3c9993..79718fb 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.search.SearchCallback;
-import com.android.launcher3.util.PackageManagerHelper;
 
 /**
  * An interface to a search box that AllApps can command.
@@ -105,30 +104,14 @@
 
     @Override
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-            if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
-                mLauncher.getStatsLogManager().logger()
-                        .log(LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
-                // selectFocusedView should return SearchTargetEvent that is passed onto onClick
-                if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
-                    return true;
-                }
-            }
-        }
 
-        // Skip if it's not the right action
-        if (actionId != EditorInfo.IME_ACTION_SEARCH) {
-            return false;
+        if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
+            mLauncher.getStatsLogManager().logger()
+                    .log(LAUNCHER_ALLAPPS_FOCUSED_ITEM_SELECTED_WITH_IME);
+            // selectFocusedView should return SearchTargetEvent that is passed onto onClick
+            return Launcher.getLauncher(mLauncher).getAppsView().launchHighlightedItem();
         }
-
-        // Skip if the query is empty
-        String query = v.getText().toString();
-        if (query.isEmpty()) {
-            return false;
-        }
-        return mLauncher.startActivitySafely(v,
-                PackageManagerHelper.getMarketSearchIntent(mLauncher, query), null
-        );
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index e268f56..ba895ed 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -15,31 +15,31 @@
  */
 package com.android.launcher3.allapps.search;
 
-import android.net.Uri;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.model.data.ItemInfo;
 
 /**
  * Provides views for local search results
  */
 public class DefaultSearchAdapterProvider extends SearchAdapterProvider {
 
+    private View mHighlightedView;
+
     public DefaultSearchAdapterProvider(BaseDraggingActivity launcher) {
         super(launcher);
     }
 
     @Override
     public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
-
-    }
-
-    @Override
-    public void onSliceStatusUpdate(Uri sliceUri) {
-
+        if (position == 0) {
+            mHighlightedView = holder.itemView;
+        }
     }
 
     @Override
@@ -54,7 +54,17 @@
     }
 
     @Override
-    public boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem, View view) {
+    public boolean launchHighlightedItem() {
+        if (mHighlightedView instanceof BubbleTextView
+                && mHighlightedView.getTag() instanceof ItemInfo) {
+            ItemInfo itemInfo = (ItemInfo) mHighlightedView.getTag();
+            return mLauncher.startActivitySafely(mHighlightedView, itemInfo.getIntent(), itemInfo);
+        }
         return false;
     }
+
+    @Override
+    public View getHighlightedItem() {
+        return mHighlightedView;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
index 0864090..a650a7d 100644
--- a/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/SearchAdapterProvider.java
@@ -43,7 +43,8 @@
     /**
      * Called from LiveSearchManager to notify slice status updates.
      */
-    public abstract void onSliceStatusUpdate(Uri sliceUri);
+    public void onSliceStatusUpdate(Uri sliceUri) {
+    }
 
     /**
      * Returns whether or not viewType can be handled by searchProvider
@@ -74,6 +75,12 @@
      * handles selection event on search adapter item. Returns false if provider can not handle
      * event
      */
-    public abstract boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem adapterItem,
-            View view);
+    public abstract boolean launchHighlightedItem();
+
+    /**
+     * Returns the current highlighted view
+     */
+    public abstract View getHighlightedItem();
+
+
 }
diff --git a/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
index 0b64fca..56dd63c 100644
--- a/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
+++ b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
@@ -21,22 +21,11 @@
  * Info class for a search section that is primarily used for decoration.
  */
 public class SectionDecorationInfo {
-
-    public static final int QUICK_LAUNCH = 1 << 0;
     public static final int GROUPING = 1 << 1;
 
     private String mSectionId;
-    private boolean mFocused;
     private SectionDecorationHandler mDecorationHandler;
 
-    public boolean isFocusedView() {
-        return mFocused;
-    }
-
-    public void setFocusedView(boolean focused) {
-        mFocused = focused;
-    }
-
     public SectionDecorationInfo() {
         this(null);
     }
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 5954efa..c67efef 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -97,7 +97,7 @@
 
         double thetaShift = 0;
         if (curNumItems == 3) {
-            thetaShift = Math.PI / 6;
+            thetaShift = Math.PI / 2;
         } else if (curNumItems == 4) {
             thetaShift = Math.PI / 4;
         }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index bcb3a54..ec7155c 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -695,6 +696,9 @@
 
         mPageIndicator.stopAllAnimations();
         startAnimation(anim);
+        // Because t=0 has the folder match the folder icon, we can skip the
+        // first frame and have the same movement one frame earlier.
+        anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
 
         // Make sure the folder picks up the last drag move even if the finger doesn't move.
         if (mDragController.isDragging()) {
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index d44d158..e954480 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -92,6 +92,10 @@
         });
     }
 
+    public void updateBackgroundColor(int color) {
+        mMainView.updateBackgroundColor(color);
+    }
+
     public void addGutter() {
         if (mGutter == null) {
             mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView);
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 9b06523..c995666 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -97,15 +97,24 @@
         super.onFinishInflate();
 
         mTextAndBackground = findViewById(R.id.text_and_background);
-        ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
-        mBackgroundColor = colorBackground.getColor();
-        RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
-                Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
-                colorBackground, null);
-        mTextAndBackground.setBackground(rippleBackground);
         mTitleView = mTextAndBackground.findViewById(R.id.title);
         mTextView = mTextAndBackground.findViewById(R.id.text);
         mIconView = findViewById(R.id.popup_item_icon);
+
+        ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
+        updateBackgroundColor(colorBackground.getColor());
+    }
+
+    public void updateBackgroundColor(int color) {
+        mBackgroundColor = color;
+        RippleDrawable rippleBackground = new RippleDrawable(ColorStateList.valueOf(
+                Themes.getAttrColor(getContext(), android.R.attr.colorControlHighlight)),
+                new ColorDrawable(color), null);
+        mTextAndBackground.setBackground(rippleBackground);
+        if (mNotificationInfo != null) {
+            mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
+                    mBackgroundColor));
+        }
     }
 
     public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 6aa9812..3736538 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -17,10 +17,12 @@
 package com.android.launcher3.popup;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -28,6 +30,8 @@
 import android.content.res.Resources;
 import android.graphics.Outline;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.util.AttributeSet;
 import android.util.Pair;
@@ -63,6 +67,9 @@
  */
 public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
 
+    // +1 for system shortcut view
+    private static final int MAX_NUM_CHILDREN = MAX_SHORTCUTS + 1;
+
     private final Rect mTempRect = new Rect();
 
     protected final LayoutInflater mInflater;
@@ -93,6 +100,9 @@
 
     private Runnable mOnCloseCallback = () -> { };
 
+    private int mArrowColor;
+    private final int[] mColors;
+
     public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mInflater = LayoutInflater.from(context);
@@ -120,16 +130,24 @@
                 R.dimen.popup_arrow_horizontal_center_offset) - (mArrowWidth / 2);
         mArrowPointRadius = resources.getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
 
+        int smallerRadius = resources.getDimensionPixelSize(R.dimen.popup_smaller_radius);
         mRoundedTop = new GradientDrawable();
-        mRoundedTop.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary));
         mRoundedTop.setCornerRadii(new float[] { mOutlineRadius, mOutlineRadius, mOutlineRadius,
-                mOutlineRadius, 0, 0, 0, 0});
+                mOutlineRadius, smallerRadius, smallerRadius, smallerRadius, smallerRadius});
 
         mRoundedBottom = new GradientDrawable();
-        mRoundedBottom.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary));
-        mRoundedBottom.setCornerRadii(new float[] { 0, 0, 0, 0, mOutlineRadius, mOutlineRadius,
-                mOutlineRadius, mOutlineRadius});
+        mRoundedBottom.setCornerRadii(new float[] { smallerRadius, smallerRadius, smallerRadius,
+                smallerRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius, mOutlineRadius});
 
+        int primaryColor = Themes.getAttrColor(context, R.attr.popupColorPrimary);
+        int secondaryColor = Themes.getAttrColor(context, R.attr.popupColorSecondary);
+        ArgbEvaluator argb = new ArgbEvaluator();
+        mColors = new int[MAX_NUM_CHILDREN];
+        // Interpolate between the two colors, exclusive.
+        float step = 1f / (MAX_NUM_CHILDREN + 1);
+        for (int i = 0; i < mColors.length; ++i) {
+            mColors[i] = (int) argb.evaluate((i + 1) * step, primaryColor, secondaryColor);
+        }
     }
 
     public ArrowPopup(Context context, AttributeSet attrs) {
@@ -187,6 +205,7 @@
 
         int numVisibleShortcut = 0;
         View lastView = null;
+        int numVisibleChild = 0;
         for (int i = 0; i < count; i++) {
             View view = getChildAt(i);
             boolean isShortcut = view instanceof DeepShortcutView;
@@ -207,16 +226,42 @@
                             view.setBackground(mRoundedTop);
                         } else if (numVisibleShortcut == (totalVisibleShortcuts - 1)) {
                             view.setBackground(mRoundedBottom);
+                        } else {
+                            view.setBackgroundResource(R.drawable.middle_item_primary);
                         }
                         numVisibleShortcut++;
                     }
                 }
+
+                int color = mColors[numVisibleChild % mColors.length];
+                setChildColor(view, color);
+
+                // Arrow color matches the first child or the last child.
+                if (!mIsAboveIcon && numVisibleChild == 0) {
+                    mArrowColor = color;
+                } else if (mIsAboveIcon) {
+                    mArrowColor = color;
+                }
+
+                numVisibleChild++;
             }
         }
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
     }
 
     /**
+     * Sets the background color of the child.
+     */
+    protected void setChildColor(View view, int color) {
+        Drawable bg = view.getBackground();
+        if (bg instanceof GradientDrawable) {
+            ((GradientDrawable) bg.mutate()).setColor(color);
+        } else if (bg instanceof ColorDrawable) {
+            ((ColorDrawable) bg.mutate()).setColor(color);
+        }
+    }
+
+    /**
      * Shows the popup at the desired location, optionally reversing the children.
      * @param viewsToFlip number of views from the top to to flip in case of reverse order
      */
@@ -228,7 +273,6 @@
         }
         onInflationComplete(reverseOrder);
         assignMarginsAndBackgrounds();
-        orientAboutObject();
         if (shouldAddArrow()) {
             addArrow();
         }
@@ -242,7 +286,6 @@
         setupForDisplay();
         onInflationComplete(false);
         assignMarginsAndBackgrounds();
-        orientAboutObject();
         if (shouldAddArrow()) {
             addArrow();
         }
@@ -293,7 +336,7 @@
                     mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(),
                     mArrowOffsetHorizontal, -mArrowOffsetVertical,
                     !mIsAboveIcon, mIsLeftAligned,
-                    Themes.getAttrColor(getContext(), R.attr.popupColorPrimary)));
+                    mArrowColor));
             mArrow.setElevation(getElevation());
         }
 
@@ -339,10 +382,18 @@
     private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
 
-        int width = getMeasuredWidth();
         int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical
                 + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
-        int height = getMeasuredHeight() + extraVerticalSpace;
+        // The margins are added after we call this method, so we need to account for them here.
+        int numVisibleChildren = 0;
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            if (getChildAt(i).getVisibility() == VISIBLE) {
+                numVisibleChildren++;
+            }
+        }
+        int childMargins = (numVisibleChildren - 1) * mMargin;
+        int height = getMeasuredHeight() + extraVerticalSpace + childMargins;
+        int width = getMeasuredWidth();
 
         getTargetObjectLocation(mTempRect);
         InsettableFrameLayout dragLayer = getPopupContainer();
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4087f49..c282ae8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.notification.NotificationMainView;
 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -170,6 +171,14 @@
         return false;
     }
 
+    @Override
+    protected void setChildColor(View v, int color) {
+        super.setChildColor(v, color);
+        if (v.getId() == R.id.notification_container && mNotificationItemView != null) {
+            mNotificationItemView.updateBackgroundColor(color);
+        }
+    }
+
     /**
      * Returns true if we can show the container.
      */
@@ -333,19 +342,11 @@
     private void updateHiddenShortcuts() {
         int allowedCount = mNotificationItemView != null
                 ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
-        int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
-        int itemHeight = mNotificationItemView != null ?
-                getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
-                : originalHeight;
-        float iconScale = ((float) itemHeight) / originalHeight;
 
         int total = mShortcuts.size();
         for (int i = 0; i < total; i++) {
             DeepShortcutView view = mShortcuts.get(i);
             view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
-            view.getLayoutParams().height = itemHeight;
-            view.getIconView().setScaleX(iconScale);
-            view.getIconView().setScaleY(iconScale);
         }
     }
 
@@ -567,6 +568,7 @@
                 // No more notifications, remove the notification views and expand all shortcuts.
                 mNotificationItemView.removeAllViews();
                 mNotificationItemView = null;
+                mNotificationContainer.setVisibility(GONE);
                 updateHiddenShortcuts();
                 assignMarginsAndBackgrounds();
             } else {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 5328041..1b0e1ce 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -74,6 +74,7 @@
     protected int mPreviewHeight;
     protected int mPresetPreviewSize;
     private int mCellSize;
+    private float mPreviewScale = 1f;
 
     private WidgetImageView mWidgetImage;
     private TextView mWidgetName;
@@ -254,8 +255,8 @@
         }
         if (drawable != null) {
             LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
-            layoutParams.width = drawable.getIntrinsicWidth();
-            layoutParams.height = drawable.getIntrinsicHeight();
+            layoutParams.width = (int) (drawable.getIntrinsicWidth() * mPreviewScale);
+            layoutParams.height = (int) (drawable.getIntrinsicHeight() * mPreviewScale);
             mWidgetImage.setLayoutParams(layoutParams);
 
             mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
@@ -305,10 +306,16 @@
 
     /** Sets the widget preview image size in number of cells. */
     public void setPreviewSize(int spanX, int spanY) {
+        setPreviewSize(spanX, spanY, 1f);
+    }
+
+    /** Sets the widget preview image size, in number of cells, and preview scale. */
+    public void setPreviewSize(int spanX, int spanY, float previewScale) {
         int padding = 2 * getResources()
                 .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
         mPreviewWidth = mDeviceProfile.cellWidthPx * spanX + padding;
         mPreviewHeight = mDeviceProfile.cellHeightPx * spanY + padding;
+        mPreviewScale = previewScale;
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index e6d54a9..2fda86c 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -119,8 +119,7 @@
 
     public void populateAndShow(ItemInfo itemInfo) {
         mOriginalItemInfo = itemInfo;
-        ((TextView) findViewById(R.id.title)).setText(getContext().getString(
-                R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
+        ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
 
         onWidgetsBound();
         attachToContainer();
@@ -140,7 +139,7 @@
 
         WidgetsTableUtils.groupWidgetItemsIntoTable(widgets, mMaxHorizontalSpan).forEach(row -> {
             TableRow tableRow = new TableRow(getContext());
-            tableRow.setGravity(Gravity.CENTER_VERTICAL);
+            tableRow.setGravity(Gravity.TOP);
             row.forEach(widgetItem -> {
                 WidgetCell widget = addItemCell(tableRow);
                 widget.setPreviewSize(widgetItem.spanX, widgetItem.spanY);
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 7eb5b83..7f84077 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -35,6 +35,7 @@
     private final SearchAndRecommendationViewHolder mViewHolder;
     private final WidgetsRecyclerView mPrimaryRecyclerView;
     private final WidgetsRecyclerView mSearchRecyclerView;
+    private final int mTabsHeight;
 
     // The following are only non null if mHasWorkProfile is true.
     @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
@@ -42,10 +43,28 @@
     @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
 
     private WidgetsRecyclerView mCurrentRecyclerView;
-    private int mMaxCollapsibleHeight = 0;
+
+    /**
+     * The vertical distance, in pixels, until the search is pinned at the top of the screen when
+     * the user scrolls down the recycler view.
+     */
+    private int mCollapsibleHeightForSearch = 0;
+    /**
+     * The vertical distance, in pixels, until the recommendation table disappears from the top of
+     * the screen when the user scrolls down the recycler view.
+     */
+    private int mCollapsibleHeightForRecommendation = 0;
+    /**
+     * The vertical distance, in pixels, until the tabs is pinned at the top of the screen when the
+     * user scrolls down the recycler view.
+     *
+     * <p>Always 0 if there is no work profile.
+     */
+    private int mCollapsibleHeightForTabs = 0;
 
     SearchAndRecommendationsScrollController(
             boolean hasWorkProfile,
+            int tabsHeight,
             SearchAndRecommendationViewHolder viewHolder,
             WidgetsRecyclerView primaryRecyclerView,
             @Nullable WidgetsRecyclerView workRecyclerView,
@@ -55,46 +74,63 @@
         mHasWorkProfile = hasWorkProfile;
         mViewHolder = viewHolder;
         mPrimaryRecyclerView = primaryRecyclerView;
+        mCurrentRecyclerView = mPrimaryRecyclerView;
         mWorkRecyclerView = workRecyclerView;
         mSearchRecyclerView = searchRecyclerView;
         mPrimaryWorkTabsView = personalWorkTabsView;
         mPrimaryWorkViewPager = primaryWorkViewPager;
         mCurrentRecyclerView = mPrimaryRecyclerView;
+        mTabsHeight = tabsHeight;
     }
 
     /** Sets the current active {@link WidgetsRecyclerView}. */
     public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
         mCurrentRecyclerView = currentRecyclerView;
+        mCurrentRecyclerView = currentRecyclerView;
+        mViewHolder.mHeaderTitle.setTranslationY(0);
+        mViewHolder.mRecommendedWidgetsTable.setTranslationY(0);
+        mViewHolder.mSearchBar.setTranslationY(0);
+
+        if (mHasWorkProfile) {
+            mPrimaryWorkTabsView.setTranslationY(0);
+        }
     }
 
     /**
      * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
+     *
+     * @return {@code true} if margins or/and padding of views in the search and recommendations
+     * container have been updated.
      */
-    public void updateMarginAndPadding() {
-        // The maximum vertical distance, in pixels, until the last collapsible element is not
-        // visible from the screen when the user scrolls down the recycler view.
-        mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
-                + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
-                + measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
+    public boolean updateMarginAndPadding() {
+        boolean hasMarginOrPaddingUpdated = false;
+        mCollapsibleHeightForSearch = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
+        mCollapsibleHeightForRecommendation =
+                measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
+                        + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
+                        + measureHeightWithVerticalMargins((View) mViewHolder.mSearchBar)
+                        + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
 
         int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
         if (mHasWorkProfile) {
+            mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
+                    + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
             // In a work profile setup, the full widget sheet contains the following views:
-            //           -------               -|
-            //           Widgets               -|---> LinearLayout for search & recommendations
-            //          Search bar             -|
-            //      Personal | Work
+            //           ------- (pinned)           -|
+            //          Widgets (collapsible)       -|---> LinearLayout for search & recommendations
+            //          Search bar (pinned)         -|
+            //  Widgets recommendation (collapsible)-|
+            //      Personal | Work (pinned)
             //           View Pager
             //
             // Views after the search & recommendations are not bound by RelativelyLayout param.
             // To position them on the expected location, padding & margin are added to these views
 
             // Tabs should have a padding of the height of the search & recommendations container.
-            mPrimaryWorkTabsView.setPadding(
-                    mPrimaryWorkTabsView.getPaddingLeft(),
-                    topContainerHeight,
-                    mPrimaryWorkTabsView.getPaddingRight(),
-                    mPrimaryWorkTabsView.getPaddingBottom());
+            RelativeLayout.LayoutParams tabsLayoutParams =
+                    (RelativeLayout.LayoutParams) mPrimaryWorkTabsView.getLayoutParams();
+            tabsLayoutParams.topMargin = topContainerHeight;
+            mPrimaryWorkTabsView.setLayoutParams(tabsLayoutParams);
 
             // Instead of setting the top offset directly, we split the top offset into two values:
             // 1. topOffsetAfterAllViewsCollapsed: this is the top offset after all collapsible
@@ -124,39 +160,52 @@
             //
             // When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
             // mMaxCollapsibleDistance should equal to the top container height.
-            int tabsViewActualHeight = measureHeightWithVerticalMargins(mPrimaryWorkTabsView)
-                    - mPrimaryWorkTabsView.getPaddingTop();
             int topOffsetAfterAllViewsCollapsed =
-                    topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
+                    topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
 
-            RelativeLayout.LayoutParams layoutParams =
+            RelativeLayout.LayoutParams viewPagerLayoutParams =
                     (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
-            layoutParams.setMargins(0, topOffsetAfterAllViewsCollapsed, 0, 0);
-            mPrimaryWorkViewPager.setLayoutParams(layoutParams);
-            mPrimaryWorkViewPager.requestLayout();
+            if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
+                viewPagerLayoutParams.topMargin = topOffsetAfterAllViewsCollapsed;
+                mPrimaryWorkViewPager.setLayoutParams(viewPagerLayoutParams);
+                hasMarginOrPaddingUpdated = true;
+            }
 
-            mPrimaryRecyclerView.setPadding(
-                    mPrimaryRecyclerView.getPaddingLeft(),
-                    mMaxCollapsibleHeight,
-                    mPrimaryRecyclerView.getPaddingRight(),
-                    mPrimaryRecyclerView.getPaddingBottom());
-            mWorkRecyclerView.setPadding(
-                    mWorkRecyclerView.getPaddingLeft(),
-                    mMaxCollapsibleHeight,
-                    mWorkRecyclerView.getPaddingRight(),
-                    mWorkRecyclerView.getPaddingBottom());
+            if (mPrimaryRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
+                mPrimaryRecyclerView.setPadding(
+                        mPrimaryRecyclerView.getPaddingLeft(),
+                        mCollapsibleHeightForTabs,
+                        mPrimaryRecyclerView.getPaddingRight(),
+                        mPrimaryRecyclerView.getPaddingBottom());
+                hasMarginOrPaddingUpdated = true;
+            }
+            if (mWorkRecyclerView.getPaddingTop() != mCollapsibleHeightForTabs) {
+                mWorkRecyclerView.setPadding(
+                        mWorkRecyclerView.getPaddingLeft(),
+                        mCollapsibleHeightForTabs,
+                        mWorkRecyclerView.getPaddingRight(),
+                        mWorkRecyclerView.getPaddingBottom());
+                hasMarginOrPaddingUpdated = true;
+            }
         } else {
-            mPrimaryRecyclerView.setPadding(
-                    mPrimaryRecyclerView.getPaddingLeft(),
-                    topContainerHeight,
-                    mPrimaryRecyclerView.getPaddingRight(),
-                    mPrimaryRecyclerView.getPaddingBottom());
+            if (mPrimaryRecyclerView.getPaddingTop() != topContainerHeight) {
+                mPrimaryRecyclerView.setPadding(
+                        mPrimaryRecyclerView.getPaddingLeft(),
+                        topContainerHeight,
+                        mPrimaryRecyclerView.getPaddingRight(),
+                        mPrimaryRecyclerView.getPaddingBottom());
+                hasMarginOrPaddingUpdated = true;
+            }
         }
-        mSearchRecyclerView.setPadding(
-                mSearchRecyclerView.getPaddingLeft(),
-                topContainerHeight,
-                mSearchRecyclerView.getPaddingRight(),
-                mSearchRecyclerView.getPaddingBottom());
+        if (mSearchRecyclerView.getPaddingTop() != topContainerHeight) {
+            mSearchRecyclerView.setPadding(
+                    mSearchRecyclerView.getPaddingLeft(),
+                    topContainerHeight,
+                    mSearchRecyclerView.getPaddingRight(),
+                    mSearchRecyclerView.getPaddingBottom());
+            hasMarginOrPaddingUpdated = true;
+        }
+        return hasMarginOrPaddingUpdated;
     }
 
     /**
@@ -168,13 +217,22 @@
         // Always use the recycler view offset because fast scroller offset has a different scale.
         int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
         if (recyclerViewYOffset < 0) return;
-        if (mMaxCollapsibleHeight > 0) {
-            int yDisplacement = Math.max(-recyclerViewYOffset, -mMaxCollapsibleHeight);
+
+        if (mCollapsibleHeightForRecommendation > 0) {
+            int yDisplacement = Math.max(-recyclerViewYOffset,
+                    -mCollapsibleHeightForRecommendation);
             mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
-            mViewHolder.mSearchBar.setTranslationY(yDisplacement);
-            if (mHasWorkProfile) {
-                mPrimaryWorkTabsView.setTranslationY(yDisplacement);
-            }
+            mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement);
+        }
+
+        if (mCollapsibleHeightForSearch > 0) {
+            int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch);
+            mViewHolder.mSearchBar.setTranslationY(searchYDisplacement);
+        }
+
+        if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) {
+            int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs);
+            mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs);
         }
     }
 
@@ -189,6 +247,9 @@
 
     /** private the height, in pixel, + the vertical margins of a given view. */
     private static int measureHeightWithVerticalMargins(View view) {
+        if (view.getVisibility() != View.VISIBLE) {
+            return 0;
+        }
         MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
         return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
                 + marginLayoutParams.topMargin;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index dc375ca..f43f712 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -33,6 +33,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
@@ -48,6 +49,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 import com.android.launcher3.widget.BaseWidgetSheet;
@@ -55,6 +57,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.search.SearchModeListener;
 import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
+import com.android.launcher3.widget.util.WidgetsTableUtils;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
@@ -68,10 +71,15 @@
 public class WidgetsFullSheet extends BaseWidgetSheet
         implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
         WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
+    private static final String TAG = WidgetsFullSheet.class.getSimpleName();
 
     private static final long DEFAULT_OPEN_DURATION = 267;
     private static final long FADE_IN_DURATION = 150;
     private static final float VERTICAL_START_POSITION = 0.3f;
+    // The widget recommendation table can easily take over the entire screen on devices with small
+    // resolution or landscape on phone. This ratio defines the max percentage of content area that
+    // the table can display.
+    private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
 
     private final Rect mInsets = new Rect();
     private final boolean mHasWorkProfile;
@@ -81,10 +89,12 @@
             mCurrentUser.equals(entry.mPkgItem.user);
     private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
             mPrimaryWidgetsFilter.negate();
+    private final int mTabsHeight;
+    private final int mWidgetCellHorizontalPadding;
 
     @Nullable private PersonalWorkPagedView mViewPager;
-    private int mInitialTabsHeight = 0;
     private boolean mIsInSearchMode;
+    private int mMaxSpansPerRow = 4;
     private View mTabsView;
     private TextView mNoWidgetsView;
     private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
@@ -96,6 +106,12 @@
         mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
         mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
         mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
+        mTabsHeight = mHasWorkProfile
+                ? getContext().getResources()
+                        .getDimensionPixelSize(R.dimen.all_apps_header_tab_height)
+                : 0;
+        mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
+                R.dimen.widget_cell_horizontal_padding);
     }
 
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
@@ -140,6 +156,7 @@
                 findViewById(R.id.search_and_recommendations_container));
         mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
                 mHasWorkProfile,
+                mTabsHeight,
                 mSearchAndRecommendationViewHolder,
                 findViewById(R.id.primary_widgets_list_view),
                 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
@@ -150,6 +167,7 @@
 
         mNoWidgetsView = findViewById(R.id.no_widgets_text);
 
+        onRecommendedWidgetsBound();
         onWidgetsBound();
 
         mSearchAndRecommendationViewHolder.mSearchBar.initialize(
@@ -257,6 +275,22 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        doMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+            doMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        if (updateMaxSpansPerRow()) {
+            doMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            if (mSearchAndRecommendationsScrollController.updateMarginAndPadding()) {
+                doMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+        }
+    }
+
+    private void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
         int widthUsed;
         if (mInsets.bottom > 0) {
@@ -272,24 +306,29 @@
                 widthUsed, heightMeasureSpec, heightUsed);
         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
                 MeasureSpec.getSize(heightMeasureSpec));
+    }
 
-        int paddingPx = 2 * getResources().getDimensionPixelOffset(
-                R.dimen.widget_cell_horizontal_padding);
-        int maxSpansPerRow = getMeasuredWidth() / (deviceProfile.cellWidthPx + paddingPx);
-        mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
-                maxSpansPerRow);
-        mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
-                maxSpansPerRow);
-        if (mHasWorkProfile) {
-            mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
-                    maxSpansPerRow);
+    /** Returns {@code true} if the max spans have been updated. */
+    private boolean updateMaxSpansPerRow() {
+        if (getMeasuredWidth() == 0) return false;
+
+        int previousMaxSpansPerRow = mMaxSpansPerRow;
+        mMaxSpansPerRow = getMeasuredWidth()
+                / (mLauncher.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
+
+        if (previousMaxSpansPerRow != mMaxSpansPerRow) {
+            mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+                    mMaxSpansPerRow);
+            mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+                    mMaxSpansPerRow);
+            if (mHasWorkProfile) {
+                mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
+                        mMaxSpansPerRow);
+            }
+            onRecommendedWidgetsBound();
+            return true;
         }
-
-        if (mInitialTabsHeight == 0 && mTabsView != null) {
-            mInitialTabsHeight = measureHeightWithVerticalMargins(mTabsView);
-        }
-
-        mSearchAndRecommendationsScrollController.updateMarginAndPadding();
+        return false;
     }
 
     @Override
@@ -346,6 +385,8 @@
             mViewPager.snapToPage(AdapterHolder.PRIMARY);
         }
         attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView);
+
+        mSearchAndRecommendationsScrollController.updateMarginAndPadding();
     }
 
     @Override
@@ -357,6 +398,8 @@
 
     private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
         mIsInSearchMode = isInSearchMode;
+        mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable
+                .setVisibility(isInSearchMode ? GONE : VISIBLE);
         if (mHasWorkProfile) {
             mViewPager.setVisibility(isInSearchMode ? GONE : VISIBLE);
             mTabsView.setVisibility(isInSearchMode ? GONE : VISIBLE);
@@ -374,6 +417,25 @@
         mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader();
     }
 
+    @Override
+    public void onRecommendedWidgetsBound() {
+        List<WidgetItem> recommendedWidgets =
+                mLauncher.getPopupDataProvider().getRecommendedWidgets();
+        WidgetsRecommendationTableLayout table =
+                mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
+        if (recommendedWidgets.size() > 0) {
+            float maxTableHeight =
+                    (mLauncher.getDeviceProfile().heightPx - mTabsHeight - getHeaderViewHeight())
+                            * RECOMMENDATION_TABLE_HEIGHT_RATIO;
+            List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
+                    WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
+                            mMaxSpansPerRow);
+            table.setRecommendedWidgets(recommendedWidgetsInTable, maxTableHeight);
+        } else {
+            table.setVisibility(GONE);
+        }
+    }
+
     private void open(boolean animate) {
         if (animate) {
             if (getPopupContainer().getInsets().bottom > 0) {
@@ -515,6 +577,7 @@
                     apps.getIconCache(),
                     /* iconClickListener= */ WidgetsFullSheet.this,
                     /* iconLongClickListener= */ WidgetsFullSheet.this);
+            mWidgetsListAdapter.setHasStableIds(true);
             switch (mAdapterType) {
                 case PRIMARY:
                     mWidgetsListAdapter.setFilter(mPrimaryWidgetsFilter);
@@ -530,24 +593,35 @@
         void setup(WidgetsRecyclerView recyclerView) {
             mWidgetsRecyclerView = recyclerView;
             mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
+            // Disables animation because it disrupts the item focus upon adapter item change.
+            mWidgetsRecyclerView.setItemAnimator(null);
             mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
             mWidgetsRecyclerView.setEdgeEffectFactory(
                     ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
             mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
+            mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
         }
     }
 
     final class SearchAndRecommendationViewHolder {
-        final View mContainer;
+        final ViewGroup mContainer;
         final View mCollapseHandle;
         final WidgetsSearchBar mSearchBar;
         final TextView mHeaderTitle;
+        final WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
 
-        SearchAndRecommendationViewHolder(View searchAndRecommendationContainer) {
+        SearchAndRecommendationViewHolder(ViewGroup searchAndRecommendationContainer) {
             mContainer = searchAndRecommendationContainer;
             mCollapseHandle = mContainer.findViewById(R.id.collapse_handle);
             mSearchBar = mContainer.findViewById(R.id.widgets_search_bar);
             mHeaderTitle = mContainer.findViewById(R.id.title);
+            mRecommendedWidgetsTable = mContainer.findViewById(R.id.recommended_widget_table);
+            mRecommendedWidgetsTable.setWidgetCellOnTouchListener((view, event) -> {
+                getRecyclerView().onTouchEvent(event);
+                return false;
+            });
+            mRecommendedWidgetsTable.setWidgetCellLongClickListener(WidgetsFullSheet.this);
+            mRecommendedWidgetsTable.setWidgetCellOnClickListener(WidgetsFullSheet.this);
         }
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index cab1e02..d841c64 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
 import java.util.function.Predicate;
@@ -216,7 +217,9 @@
 
     @Override
     public long getItemId(int pos) {
-        return pos;
+        return Arrays.hashCode(new Object[]{
+                mVisibleEntries.get(pos).mPkgItem.hashCode(),
+                getItemViewType(pos)});
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index f126321..e57f4d8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -52,7 +52,9 @@
     public void bindViewHolder(WidgetsListHeaderHolder viewHolder, WidgetsListHeaderEntry data,
             int position) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
-        if (position == 0) {
+        if (mWidgetsListAdapter.getItemCount() == 1) {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_single_item_ripple);
+        } else if (position == 0) {
             widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
         } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
             widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
index 37713e1..b98f5e1 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinder.java
@@ -53,7 +53,9 @@
     public void bindViewHolder(WidgetsListSearchHeaderHolder viewHolder,
             WidgetsListSearchHeaderEntry data, int position) {
         WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
-        if (position == 0) {
+        if (mWidgetsListAdapter.getItemCount() == 1) {
+            widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_single_item_ripple);
+        } else if (position == 0) {
             widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_top_ripple);
         } else if (position == mWidgetsListAdapter.getItemCount() - 1) {
             widgetsListHeader.setBackgroundResource(R.drawable.widgets_list_bottom_ripple);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index d0be35d..c1d64b1 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -158,7 +158,7 @@
                 tableRow = (TableRow) table.getChildAt(i);
             } else {
                 tableRow = new TableRow(table.getContext());
-                tableRow.setGravity(Gravity.CENTER_VERTICAL);
+                tableRow.setGravity(Gravity.TOP);
                 table.addView(tableRow);
             }
             if (tableRow.getChildCount() > widgetItems.size()) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
new file mode 100644
index 0000000..6569fb0
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 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.widget.picker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A {@link TableLayout} for showing recommended widgets. */
+public final class WidgetsRecommendationTableLayout extends TableLayout {
+    private static final float SCALE_DOWN_RATIO = 0.9f;
+    private final DeviceProfile mDeviceProfile;
+    private final float mWidgetCellTextViewsHeight;
+
+    private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
+    @Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
+    @Nullable private OnClickListener mWidgetCellOnClickListener;
+    @Nullable private OnTouchListener mWidgetCellOnTouchListener;
+
+    public WidgetsRecommendationTableLayout(Context context) {
+        this(context, /* attrs= */ null);
+    }
+
+    public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
+        // There are 1 row for title, 1 row for dimension and 2 rows for description.
+        mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
+    }
+
+    /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
+    public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
+        mWidgetCellOnLongClickListener = onLongClickListener;
+    }
+
+    /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
+    public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
+        mWidgetCellOnClickListener = widgetCellOnClickListener;
+    }
+
+    /** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
+    public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
+        mWidgetCellOnTouchListener = widgetCellOnTouchListener;
+    }
+
+    /**
+     * Sets a list of recommended widgets that would like to be displayed in this table within the
+     * desired {@code recommendationTableMaxHeight}.
+     *
+     * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
+     * last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
+     * row still doesn't fit, we scale down the preview image.
+     */
+    public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+            float recommendationTableMaxHeight) {
+        mRecommendationTableMaxHeight = recommendationTableMaxHeight;
+        RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
+                recommendedWidgets);
+        bindData(data);
+    }
+
+    private void bindData(RecommendationTableData data) {
+        if (data.mRecommendationTable.size() == 0) {
+            setVisibility(GONE);
+            return;
+        }
+
+        removeAllViews();
+
+        for (int i = 0; i < data.mRecommendationTable.size(); i++) {
+            List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
+            TableRow tableRow = new TableRow(getContext());
+            tableRow.setGravity(Gravity.TOP);
+
+            for (WidgetItem widgetItem : widgetItems) {
+                WidgetCell widgetCell = addItemCell(tableRow);
+                widgetCell.setPreviewSize(widgetItem.spanX, widgetItem.spanY, data.mPreviewScale);
+                widgetCell.applyFromCellItem(widgetItem,
+                        LauncherAppState.getInstance(getContext()).getWidgetCache());
+                widgetCell.ensurePreview();
+            }
+            addView(tableRow);
+        }
+        setVisibility(VISIBLE);
+    }
+
+    private WidgetCell addItemCell(ViewGroup parent) {
+        WidgetCell widget = (WidgetCell) LayoutInflater.from(
+                getContext()).inflate(R.layout.widget_cell, parent, false);
+
+        widget.setOnTouchListener(mWidgetCellOnTouchListener);
+        WidgetImageView preview = widget.findViewById(R.id.widget_preview);
+        preview.setOnClickListener(mWidgetCellOnClickListener);
+        preview.setOnLongClickListener(mWidgetCellOnLongClickListener);
+        widget.setAnimatePreview(false);
+
+        parent.addView(widget);
+        return widget;
+    }
+
+    private RecommendationTableData fitRecommendedWidgetsToTableSpace(
+            float previewScale,
+            List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
+        // A naive estimation of the widgets recommendation table height without inflation.
+        float totalHeight = 0;
+        for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
+            List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
+            float rowHeight = 0;
+            for (int j = 0; j < widgetItems.size(); j++) {
+                float previewHeight = widgetItems.get(j).spanY * mDeviceProfile.allAppsCellHeightPx
+                        * previewScale;
+                rowHeight = Math.max(rowHeight, previewHeight + mWidgetCellTextViewsHeight);
+            }
+            totalHeight += rowHeight;
+        }
+
+        if (totalHeight < mRecommendationTableMaxHeight) {
+            return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
+        }
+
+        if (recommendedWidgetsInTable.size() > 1) {
+            // We don't want to scale down widgets preview unless we really need to. Reduce the
+            // num of row by 1 to see if it fits.
+            return fitRecommendedWidgetsToTableSpace(
+                    previewScale,
+                    recommendedWidgetsInTable.subList(/* fromIndex= */0,
+                            /* toIndex= */recommendedWidgetsInTable.size() - 1));
+        }
+
+        float nextPreviewScale = previewScale * SCALE_DOWN_RATIO;
+        return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
+    }
+
+    /** Data class for the widgets recommendation table and widgets preview scaling. */
+    private class RecommendationTableData {
+        private final List<ArrayList<WidgetItem>> mRecommendationTable;
+        private final float mPreviewScale;
+
+        RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
+                float previewScale) {
+            mRecommendationTable = recommendationTable;
+            mPreviewScale = previewScale;
+        }
+    }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 8a73483..da55c28 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -11,6 +11,15 @@
 // 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_apps_Launcher3_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_apps_Launcher3_license"],
+}
+
 filegroup {
     name: "launcher3-test-src-common",
     srcs: ["src_common/**/*.java"],
diff --git a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
index c5ab030..21625c6 100644
--- a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
+++ b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
@@ -18,7 +18,7 @@
         <ImageView
             android:layout_width="24dp"
             android:layout_height="24dp"
-            android:background="@android:color/system_primary_500"/>
+            android:background="@android:color/system_neutral1_500"/>
     </LinearLayout>
     <LinearLayout
         android:orientation = "horizontal"
@@ -32,7 +32,7 @@
         <ImageView
             android:layout_width="24dp"
             android:layout_height="24dp"
-            android:background="@android:color/system_secondary_500"/>
+            android:background="@android:color/system_accent1_500"/>
     </LinearLayout>
     <LinearLayout
         android:orientation = "horizontal"
@@ -46,7 +46,7 @@
         <ImageView
             android:layout_width="24dp"
             android:layout_height="24dp"
-            android:background="@android:color/system_neutral_500"/>
+            android:background="@android:color/system_neutral2_500"/>
     </LinearLayout>
 
     </LinearLayout>
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index a7b92b7..7bfe33c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1214,7 +1214,7 @@
 
         final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
         assertTrue("injectInputEvent failed",
-                mInstrumentation.getUiAutomation().injectInputEvent(event, true));
+                mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
         event.recycle();
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 22f4d31..fe4c712 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -30,7 +30,6 @@
 import com.android.launcher3.testing.TestProtocol;
 
 import java.util.Collection;
-import java.util.List;
 
 /**
  * All widgets container.
@@ -106,7 +105,8 @@
                     fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
             final Point displaySize = mLauncher.getRealDisplaySize();
 
-            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
+            Rect headerRect = new Rect();
+            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(headerRect);
             mLauncher.assertTrue("Can't locate widgets list for the test app: "
                             + mLauncher.getLauncherPackageName(),
                     widgetsContainer != null);
@@ -132,7 +132,12 @@
 
                 mLauncher.assertTrue("Too many attempts", ++i <= 40);
                 final int scroll = getWidgetsScroll();
-                mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, tableRows, 0);
+                mLauncher.scroll(
+                        fullWidgetsPicker,
+                        Direction.DOWN,
+                        headerRect,
+                        10,
+                        true);
                 final int newScroll = getWidgetsScroll();
                 mLauncher.assertTrue(
                         "Scrolled in a wrong direction in Widgets: from " + scroll + " to "
@@ -143,7 +148,7 @@
     }
 
     /** Finds the widgets list of this test app from the collapsed full widgets picker. */
-    private UiObject2 findTestAppWidgetsTableContainer() {
+    private UiObject2 findTestAppWidgetsTableContainer(Rect outHeaderRect) {
         final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
                 "widgets_list_header");
         final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
@@ -156,6 +161,7 @@
             UiObject2 fullWidgetsPicker = verifyActiveContainer();
 
             UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
+            outHeaderRect.set(0, 0, 0, header.getVisibleBounds().height());
             mLauncher.assertTrue("Can't find a widget header", header != null);
 
             // Look for a header that has the test app name.
@@ -179,11 +185,14 @@
                 if (widgetsContainer != null) {
                     return widgetsContainer;
                 }
-                mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
-            } else {
-                mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
-                        0);
+
             }
+            mLauncher.scroll(
+                    fullWidgetsPicker,
+                    Direction.DOWN,
+                    outHeaderRect,
+                    /* steps= */ 10,
+                    /* slowDown= */ true);
         }
 
         return null;