Merge "Disable double haptic when dragging from taskbar hotseat on home" into sc-dev
diff --git a/OWNERS b/OWNERS
index 1d6ad8c..daad057 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,6 +4,13 @@
 # People who can approve changes for submission
 #
 
+petrcermak@google.com
+pbdr@google.com
+kideckel@google.com
+stevenckng@google.com
+ydixit@google.com
+boadway@google.com
+alinazaidi@google.com
 adamcohen@google.com
 hyunyoungs@google.com
 mrcasey@google.com
diff --git a/quickstep/res/drawable/task_menu_bg.xml b/quickstep/res/drawable/task_menu_bg.xml
index 7334d98..a60defc 100644
--- a/quickstep/res/drawable/task_menu_bg.xml
+++ b/quickstep/res/drawable/task_menu_bg.xml
@@ -14,25 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:gravity="bottom">
-        <!-- Shadow -->
-        <shape>
-            <gradient android:angle="270"
-                android:endColor="@android:color/transparent"
-                android:startColor="#26000000" />
-            <size android:height="@dimen/task_card_menu_shadow_height" />
-        </shape>
-    </item>
-    <item android:bottom="@dimen/task_card_menu_shadow_height">
-        <!-- Background -->
-        <shape>
-            <corners
-                android:topLeftRadius="?android:attr/dialogCornerRadius"
-                android:topRightRadius="?android:attr/dialogCornerRadius"
-                android:bottomLeftRadius="0dp"
-                android:bottomRightRadius="0dp" />
-            <solid android:color="?attr/popupColorPrimary" />
-        </shape>
-    </item>
-</layer-list>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="?attr/popupColorPrimary" />
+</shape>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 544835c..fb7d85f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -246,6 +246,11 @@
      * Removes the Taskbar from the screen, and removes any obsolete listeners etc.
      */
     public void cleanup() {
+        if (mAnimator != null) {
+            // End this first, in case it relies on properties that are about to be cleaned up.
+            mAnimator.end();
+        }
+
         mTaskbarView.cleanup();
         mTaskbarContainerView.cleanup();
         removeFromWindowManager();
@@ -253,10 +258,6 @@
         mTaskbarVisibilityController.cleanup();
         mHotseatController.cleanup();
         mRecentsController.cleanup();
-
-        if (mAnimator != null) {
-            mAnimator.end();
-        }
     }
 
     private void removeFromWindowManager() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 2315147..fe7ece2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -23,12 +23,14 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -41,10 +43,10 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.TaskCornerRadius;
 
 /**
  * Contains options for a recent task when long-pressing its icon.
@@ -72,6 +74,7 @@
 
         mActivity = BaseDraggingActivity.fromContext(context);
         mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+        setClipToOutline(true);
     }
 
     @Override
@@ -108,6 +111,17 @@
         return (type & TYPE_TASK_MENU) != 0;
     }
 
+    @Override
+    public ViewOutlineProvider getOutlineProvider() {
+        return new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
+                        TaskCornerRadius.get(view.getContext()));
+            }
+        };
+    }
+
     public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
         float adjustedY = y + mThumbnailTopMargin;
         // Changing pivot to make computations easier
@@ -260,7 +274,7 @@
     }
 
     private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
-        float radius = Themes.getDialogCornerRadius(getContext());
+        float radius = TaskCornerRadius.get(mContext);
         Rect fromRect = new Rect(0, 0, getWidth(), 0);
         Rect toRect = new Rect(0, 0, getWidth(), getHeight());
         return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
diff --git a/res/values/config.xml b/res/values/config.xml
index 89415b8..8184c98 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -90,6 +90,7 @@
     <string name="wallpaper_picker_package" translatable="false"></string>
     <string name="calendar_component_name" translatable="false"></string>
     <string name="clock_component_name" translatable="false"></string>
+    <string name="local_colors_extraction_class" translatable="false"></string>
 
     <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
@@ -188,4 +189,8 @@
     </string-array>
 
     <string-array name="filtered_components" ></string-array>
+
+    <!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
+    <string name="color_generator_class" translatable="false"/>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6fb72b1..351182d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,9 +40,9 @@
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
-    <string name="long_press_widget_to_add">Touch &amp; hold to pick up a widget.</string>
+    <string name="long_press_widget_to_add">Touch &amp; hold to move a widget.</string>
     <!-- Accessibility spoken hint message in widget picker, which allows user to add a widget. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=100] -->
-    <string name="long_accessible_way_to_add">Double-tap &amp; hold to pick up a widget or use custom actions.</string>
+    <string name="long_accessible_way_to_add">Double-tap &amp; hold to move a widget or use custom actions.</string>
     <!-- The format string for the dimensions of a widget in the drawer -->
     <!-- There is a special version of this format string for Farsi -->
     <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
@@ -104,9 +104,9 @@
 
     <!-- Drag and drop -->
     <!-- Message to tell the user to press and hold on a shortcut to add it [CHAR_LIMIT=50] -->
-    <string name="long_press_shortcut_to_add">Touch &amp; hold to pick up a shortcut.</string>
+    <string name="long_press_shortcut_to_add">Touch &amp; hold to move a shortcut.</string>
     <!-- Accessibility spoken hint message in deep shortcut menu, which allows user to add a shortcut. Custom action is the label for additional accessibility actions available in this mode [CHAR_LIMIT=200] -->
-    <string name="long_accessible_way_to_add_shortcut">Double-tap &amp; hold to pick up a shortcut or use custom actions.</string>
+    <string name="long_accessible_way_to_add_shortcut">Double-tap &amp; hold to move a shortcut or use custom actions.</string>
 
     <skip />
     <!-- Error message when user has filled a home screen -->
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
new file mode 100644
index 0000000..c2bf1ae
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchAlgorithmTest {
+
+    private SimpleWidgetsSearchAlgorithm mSimpleWidgetsSearchAlgorithm;
+    @Mock
+    private WidgetsPickerSearchPipeline mSearchPipeline;
+    @Mock
+    private SearchCallback<WidgetsListBaseEntry> mSearchCallback;
+    @Captor
+    private ArgumentCaptor<Consumer<List<WidgetsListBaseEntry>>> mConsumerCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSimpleWidgetsSearchAlgorithm = new SimpleWidgetsSearchAlgorithm(mSearchPipeline);
+    }
+
+    @Test
+    public void doSearch_shouldQueryPipeline() {
+        mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
+
+        verify(mSearchPipeline).query(eq("abc"), any());
+    }
+
+    @Test
+    public void doSearch_shouldInformSearchCallbackOnQueryResult() {
+        ArrayList<WidgetsListBaseEntry> baseEntries = new ArrayList<>();
+
+        mSimpleWidgetsSearchAlgorithm.doSearch("abc", mSearchCallback);
+
+        verify(mSearchPipeline).query(eq("abc"), mConsumerCaptor.capture());
+        mConsumerCaptor.getValue().accept(baseEntries);
+        shadowOf(getMainLooper()).idle();
+        // Verify SearchCallback#onSearchResult receives a query token along with the search
+        // results. The query token is the original query string concatenated with the query
+        // timestamp.
+        verify(mSearchCallback).onSearchResult(matches("abc\t\\d*"), eq(baseEntries));
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java
new file mode 100644
index 0000000..8aebf12
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipelineTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.search;
+
+import static android.os.Looper.getMainLooper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
+import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class SimpleWidgetsSearchPipelineTest {
+    private static final SimpleWidgetsSearchPipeline.StringMatcher MATCHER =
+            SimpleWidgetsSearchPipeline.StringMatcher.getInstance();
+
+    @Mock private IconCache mIconCache;
+
+    private InvariantDeviceProfile mTestProfile;
+    private WidgetsListHeaderEntry mCalendarHeaderEntry;
+    private WidgetsListContentEntry mCalendarContentEntry;
+    private WidgetsListHeaderEntry mCameraHeaderEntry;
+    private WidgetsListContentEntry mCameraContentEntry;
+    private WidgetsListHeaderEntry mClockHeaderEntry;
+    private WidgetsListContentEntry mClockContentEntry;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
+                .getComponent().getPackageName())
+                .when(mIconCache).getTitleNoCache(any());
+        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile.numRows = 5;
+        mTestProfile.numColumns = 5;
+        mContext = RuntimeEnvironment.application;
+
+        mCalendarHeaderEntry =
+                createWidgetsHeaderEntry("com.example.android.Calendar", "Calendar", 2);
+        mCalendarContentEntry =
+                createWidgetsContentEntry("com.example.android.Calendar", "Calendar", 2);
+        mCameraHeaderEntry = createWidgetsHeaderEntry("com.example.android.Camera", "Camera", 5);
+        mCameraContentEntry = createWidgetsContentEntry("com.example.android.Camera", "Camera", 5);
+        mClockHeaderEntry = createWidgetsHeaderEntry("com.example.android.Clock", "Clock", 3);
+        mClockContentEntry = createWidgetsContentEntry("com.example.android.Clock", "Clock", 3);
+    }
+
+    @Test
+    public void query_shouldInformCallbackWithResultsMatchedOnAppName() {
+        SimpleWidgetsSearchPipeline pipeline = new SimpleWidgetsSearchPipeline(
+                List.of(mCalendarHeaderEntry, mCalendarContentEntry, mCameraHeaderEntry,
+                        mCameraContentEntry, mClockHeaderEntry, mClockContentEntry));
+
+        pipeline.query("Ca", results ->
+                assertEquals(results, List.of(mCalendarHeaderEntry, mCalendarContentEntry,
+                        mCameraHeaderEntry, mCameraContentEntry)));
+        shadowOf(getMainLooper()).idle();
+    }
+
+    @Test
+    public void testMatches() {
+        assertTrue(MATCHER.matches("q", "Q"));
+        assertTrue(MATCHER.matches("q", "  Q"));
+        assertTrue(MATCHER.matches("e", "elephant"));
+        assertTrue(MATCHER.matches("eL", "Elephant"));
+        assertTrue(MATCHER.matches("elephant ", "elephant"));
+        assertTrue(MATCHER.matches("whitec", "white cow"));
+        assertTrue(MATCHER.matches("white  c", "white cow"));
+        assertTrue(MATCHER.matches("white ", "white cow"));
+        assertTrue(MATCHER.matches("white c", "white cow"));
+        assertTrue(MATCHER.matches("电", "电子邮件"));
+        assertTrue(MATCHER.matches("电子", "电子邮件"));
+        assertTrue(MATCHER.matches("다", "다운로드"));
+        assertTrue(MATCHER.matches("드", "드라이브"));
+        assertTrue(MATCHER.matches("åbç", "abc"));
+        assertTrue(MATCHER.matches("ål", "Alpha"));
+
+        assertFalse(MATCHER.matches("phant", "elephant"));
+        assertFalse(MATCHER.matches("elephants", "elephant"));
+        assertFalse(MATCHER.matches("cow", "white cow"));
+        assertFalse(MATCHER.matches("cow", "whiteCow"));
+        assertFalse(MATCHER.matches("dog", "cats&Dogs"));
+        assertFalse(MATCHER.matches("ba", "Bot"));
+        assertFalse(MATCHER.matches("ba", "bot"));
+        assertFalse(MATCHER.matches("子", "电子邮件"));
+        assertFalse(MATCHER.matches("邮件", "电子邮件"));
+        assertFalse(MATCHER.matches("ㄷ", "다운로드 드라이브"));
+        assertFalse(MATCHER.matches("ㄷㄷ", "다운로드 드라이브"));
+        assertFalse(MATCHER.matches("åç", "abc"));
+    }
+
+    private WidgetsListHeaderEntry createWidgetsHeaderEntry(String packageName, String appName,
+            int numOfWidgets) {
+        List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+        PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+                widgetItems.get(0).user);
+
+        return new WidgetsListHeaderEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+    }
+
+    private WidgetsListContentEntry createWidgetsContentEntry(String packageName, String appName,
+            int numOfWidgets) {
+        List<WidgetItem> widgetItems = generateWidgetItems(packageName, numOfWidgets);
+        PackageItemInfo pInfo = createPackageItemInfo(packageName, appName,
+                widgetItems.get(0).user);
+
+        return new WidgetsListContentEntry(pInfo, /* titleSectionName= */ "", widgetItems);
+    }
+
+    private PackageItemInfo createPackageItemInfo(String packageName, String appName,
+            UserHandle userHandle) {
+        PackageItemInfo pInfo = new PackageItemInfo(packageName);
+        pInfo.title = appName;
+        pInfo.user = userHandle;
+        pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0);
+        return pInfo;
+    }
+
+    private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
+        ShadowPackageManager packageManager = shadowOf(mContext.getPackageManager());
+        ArrayList<WidgetItem> widgetItems = new ArrayList<>();
+        for (int i = 0; i < numOfWidgets; i++) {
+            ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
+            AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
+            widgetInfo.provider = cn;
+            ReflectionHelpers.setField(widgetInfo, "providerInfo",
+                    packageManager.addReceiverIfNotPresent(cn));
+
+            WidgetItem widgetItem = new WidgetItem(
+                    LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
+                    mTestProfile, mIconCache);
+            widgetItems.add(widgetItem);
+        }
+        return widgetItems;
+    }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 87fb6fb..0cc965c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -186,7 +186,6 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
-    private final Rect mTempRect = new Rect();
     private final int[] mTempXY = new int[2];
     private final float[] mTempFXY = new float[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
@@ -367,10 +366,19 @@
     }
 
     public float getWallpaperOffsetForCenterPage() {
-        int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen());
+        return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen());
+    }
+
+    private float getWallpaperOffsetForPage(int page) {
+        int pageScroll = getScrollForPage(page);
         return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll);
     }
 
+    /** Returns the number of pages used for the wallpaper parallax. */
+    public int getNumPagesForWallpaperParallax() {
+        return mWallpaperOffset.getNumPagesForWallpaperParallax();
+    }
+
     public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) {
         Rect r = new Rect();
         cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index c9fb956..8d676c9 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -88,7 +88,6 @@
     private PreferenceCategory mPluginsCategory;
     private FlagTogglerPrefUi mFlagTogglerPrefUi;
 
-
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 2ad80cf..b8554e4 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -145,14 +145,17 @@
         msg.sendToTarget();
     }
 
-    private void updateOffset() {
-        int numPagesForWallpaperParallax;
+    /** Returns the number of pages used for the wallpaper parallax. */
+    public int getNumPagesForWallpaperParallax() {
         if (mWallpaperIsLiveWallpaper) {
-            numPagesForWallpaperParallax = mNumScreens;
+            return mNumScreens;
         } else {
-            numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
+            return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
         }
-        Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
+    }
+
+    private void updateOffset() {
+        Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0,
                 mWindowToken).sendToTarget();
     }
 
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 03c58bb..4fe631a 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -74,7 +74,18 @@
 
     @Override
     public final void onClick(View v) {
-        mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+        Object tag = null;
+        if (v instanceof WidgetCell) {
+            tag = v.getTag();
+        } else if (v.getParent() instanceof WidgetCell) {
+            tag = ((WidgetCell) v.getParent()).getTag();
+        }
+        if (tag instanceof PendingAddShortcutInfo) {
+            mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
+        } else {
+            mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
+        }
+
     }
 
     @Override
@@ -158,4 +169,21 @@
         toast.show();
         return toast;
     }
+
+    /**
+     * Show shortcut tap toast prompting user to drag instead.
+     */
+    private static Toast showShortcutToast(Context context, Toast toast) {
+        // Let the user know that they have to long press to add a widget
+        if (toast != null) {
+            toast.cancel();
+        }
+
+        CharSequence msg = Utilities.wrapForTts(
+                context.getText(R.string.long_press_shortcut_to_add),
+                context.getString(R.string.long_accessible_way_to_add_shortcut));
+        toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
+        toast.show();
+        return toast;
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 3285c18..df01295 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -16,12 +16,17 @@
 
 package com.android.launcher3.widget;
 
+import android.app.WallpaperManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.util.Log;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -32,10 +37,13 @@
 import android.widget.Advanceable;
 import android.widget.RemoteViews;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -43,11 +51,16 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
 
+import java.util.List;
+
 /**
  * {@inheritDoc}
  */
 public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
-        implements TouchCompleteListener, View.OnLongClickListener {
+        implements TouchCompleteListener, View.OnLongClickListener,
+        LocalColorExtractor.Listener {
+
+    private static final String LOG_TAG = "LauncherAppWidgetHostView";
 
     // Related to the auto-advancing of widgets
     private static final long ADVANCE_INTERVAL = 20000;
@@ -60,18 +73,29 @@
 
     private final CheckLongPressHelper mLongPressHelper;
     protected final Launcher mLauncher;
+    private final Workspace mWorkspace;
+    private final WallpaperManager mWallpaperManager;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mReinflateOnConfigChange;
 
+    // Maintain the color manager.
+    private final LocalColorExtractor mColorExtractor;
+
     private boolean mIsScrollable;
     private boolean mIsAttachedToWindow;
     private boolean mIsAutoAdvanceRegistered;
     private Runnable mAutoAdvanceRunnable;
+    private RectF mLastLocationRegistered = null;
+    // Used to store the widget size during onLayout.
+    private final Rect mCurrentWidgetSize = new Rect();
+    private final RectF mTempRectF = new RectF();
+    private final boolean mIsRtl;
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mLauncher = Launcher.getLauncher(context);
+        mWorkspace = mLauncher.getWorkspace();
         mLongPressHelper = new CheckLongPressHelper(this, this);
         mInflater = LayoutInflater.from(context);
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
@@ -81,6 +105,19 @@
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
         }
+        mIsRtl = Utilities.isRtl(context.getResources());
+        mWallpaperManager = WallpaperManager.getInstance(getContext());
+        mColorExtractor = LocalColorExtractor.newInstance(getContext());
+        mColorExtractor.setListener(this);
+    }
+
+    @Override
+    public void setColorResources(@Nullable SparseIntArray colors) {
+        if (colors == null) {
+            resetColorResources();
+        } else {
+            super.setColorResources(colors);
+        }
     }
 
     @Override
@@ -167,6 +204,7 @@
         // state is updated. So isAttachedToWindow() will return true until next frame.
         mIsAttachedToWindow = false;
         checkIfAutoAdvance();
+        mColorExtractor.removeLocations();
     }
 
     @Override
@@ -213,6 +251,78 @@
         }
 
         mIsScrollable = checkScrollableRecursively(this);
+
+        mCurrentWidgetSize.left = left;
+        mCurrentWidgetSize.top = top;
+        mCurrentWidgetSize.right = right;
+        mCurrentWidgetSize.bottom = bottom;
+        updateColorExtraction(mCurrentWidgetSize);
+    }
+
+    private void updateColorExtraction(Rect widgetLocation) {
+        // If the widget hasn't been measured and laid out, we cannot do this.
+        if (widgetLocation.isEmpty()) {
+            return;
+        }
+        LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+        if (info != null) {
+            int screenWidth = mLauncher.getDeviceProfile().widthPx;
+            int screenHeight = mLauncher.getDeviceProfile().heightPx;
+            int numScreens = mWorkspace.getNumPagesForWallpaperParallax();
+            int screenId = mIsRtl ? numScreens - info.screenId : info.screenId;
+            float relativeScreenWidth = 1f / numScreens;
+            float absoluteTop = widgetLocation.top;
+            float absoluteBottom = widgetLocation.bottom;
+            for (View v = (View) getParent();
+                    v != null && v.getId() != R.id.launcher;
+                    v = (View) v.getParent()) {
+                absoluteBottom += v.getTop();
+                absoluteTop += v.getTop();
+            }
+            float xOffset = 0;
+            View parentView = (View) getParent();
+            // The layout depends on the orientation.
+            if (getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_LANDSCAPE) {
+                xOffset = screenHeight - mWorkspace.getPaddingRight()
+                        - parentView.getWidth();
+            } else {
+                xOffset = mWorkspace.getPaddingLeft() + parentView.getPaddingLeft();
+            }
+            // This is the position of the widget relative to the wallpaper, as expected by the
+            // local color extraction of the WallpaperManager.
+            // The coordinate system is such that, on the horizontal axis, each screen has a
+            // distinct range on the [0,1] segment. So if there are 3 screens, they will have the
+            // ranges [0, 1/3], [1/3, 2/3] and [2/3, 1]. The position on the subrange should be
+            // the position of the widget relative to the screen. For the vertical axis, this is
+            // simply the location of the widget relative to the screen.
+            mTempRectF.left = ((widgetLocation.left + xOffset) / screenWidth + screenId)
+                    * relativeScreenWidth;
+            mTempRectF.right = ((widgetLocation.right + xOffset) / screenWidth + screenId)
+                    * relativeScreenWidth;
+            mTempRectF.top = absoluteTop / screenHeight;
+            mTempRectF.bottom = absoluteBottom / screenHeight;
+            if (mTempRectF.left < 0 || mTempRectF.right > 1 || mTempRectF.top < 0
+                    || mTempRectF.bottom > 1) {
+                Log.e(LOG_TAG, "   Error, invalid relative position");
+                return;
+            }
+            if (!mTempRectF.equals(mLastLocationRegistered)) {
+                if (mLastLocationRegistered != null) {
+                    mColorExtractor.removeLocations();
+                }
+                mLastLocationRegistered = new RectF(mTempRectF);
+                mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+            }
+        } else {
+            mColorExtractor.removeLocations();
+        }
+    }
+
+    @Override
+    public void onColorsChanged(RectF rectF, SparseIntArray colors) {
+        // setColorResources will reapply the view, which must happen in the UI thread.
+        post(() -> setColorResources(colors));
     }
 
     @Override
@@ -225,6 +335,14 @@
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
         maybeRegisterAutoAdvance();
+
+        if (visibility == View.VISIBLE) {
+            if (mLastLocationRegistered != null) {
+                mColorExtractor.addLocation(List.of(mLastLocationRegistered));
+            }
+        } else {
+            mColorExtractor.removeLocations();
+        }
     }
 
     private void checkIfAutoAdvance() {
diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java
new file mode 100644
index 0000000..097158b
--- /dev/null
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009 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;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.graphics.RectF;
+import android.util.SparseIntArray;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.List;
+
+/** Extracts the colors we need from the wallpaper at given locations. */
+public class LocalColorExtractor implements ResourceBasedOverride {
+
+    /** Listener for color changes on a screen location. */
+    public interface Listener {
+        /**
+         * Method called when the colors on a registered location has changed.
+         *
+         * {@code extractedColors} maps the color resources {@code android.R.colors.system_*} to
+         * their value, in a format that can be passed directly to
+         * {@link AppWidgetHostView#setColorResources(SparseIntArray)}.
+         */
+        void onColorsChanged(RectF rect, SparseIntArray extractedColors);
+    }
+
+    static LocalColorExtractor newInstance(Context context) {
+        return Overrides.getObject(LocalColorExtractor.class, context.getApplicationContext(),
+                R.string.local_colors_extraction_class);
+    }
+
+    /** Sets the object that will receive the color changes. */
+    public void setListener(@Nullable Listener listener) {
+        // no-op
+    }
+
+    /** Adds a list of locations to track with this listener. */
+    public void addLocation(List<RectF> locations) {
+        // no-op
+    }
+
+    /** Stops tracking any locations. */
+    public void removeLocations() {
+        // no-op
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
new file mode 100644
index 0000000..15d2454
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithm.java
@@ -0,0 +1,63 @@
+/*
+ * 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.search;
+
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link SearchAlgorithm} that posts a task to query on the main thread.
+ */
+public final class SimpleWidgetsSearchAlgorithm implements SearchAlgorithm<WidgetsListBaseEntry> {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SimpleWidgetsSearchAlgo";
+    private static final String DELIM = "\t";
+
+    private final Handler mResultHandler;
+    private final WidgetsPickerSearchPipeline mSearchPipeline;
+
+    public SimpleWidgetsSearchAlgorithm(WidgetsPickerSearchPipeline searchPipeline) {
+        mResultHandler = new Handler();
+        mSearchPipeline = searchPipeline;
+    }
+
+    @Override
+    public void doSearch(String query, SearchCallback<WidgetsListBaseEntry> callback) {
+        long startTime = System.currentTimeMillis();
+        String queryToken = query + DELIM + startTime;
+        if (DEBUG) {
+            Log.d(TAG, "doSearch queryToken:" + queryToken);
+        }
+        mSearchPipeline.query(query,
+                results -> mResultHandler.post(
+                        () -> callback.onSearchResult(queryToken, new ArrayList(results))));
+    }
+
+    @Override
+    public void cancel(boolean interruptActiveRequests) {
+        if (interruptActiveRequests) {
+            mResultHandler.removeCallbacksAndMessages(/*token= */null);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java
new file mode 100644
index 0000000..9911495
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchPipeline.java
@@ -0,0 +1,88 @@
+/*
+ * 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.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of {@link WidgetsPickerSearchPipeline} that performs search by prefix matching on
+ * app names and widget labels.
+ */
+public final class SimpleWidgetsSearchPipeline implements WidgetsPickerSearchPipeline {
+
+    private final List<WidgetsListBaseEntry> mAllEntries;
+
+    public SimpleWidgetsSearchPipeline(List<WidgetsListBaseEntry> allEntries) {
+        mAllEntries = allEntries;
+    }
+
+    @Override
+    public void query(String input, Consumer<List<WidgetsListBaseEntry>> callback) {
+        StringMatcher matcher =  StringMatcher.getInstance();
+        ArrayList<WidgetsListBaseEntry> results = new ArrayList<>();
+        // TODO(b/157286785): Filter entries based on query prefix matching on widget labels also.
+        for (WidgetsListBaseEntry e : mAllEntries) {
+            if (matcher.matches(input, e.mPkgItem.title.toString())) {
+                results.add(e);
+            }
+        }
+        callback.accept(results);
+    }
+
+    /**
+     * Performs locale sensitive string comparison using {@link Collator}.
+     */
+    public static class StringMatcher {
+
+        private static final char MAX_UNICODE = '\uFFFF';
+
+        private final Collator mCollator;
+
+        StringMatcher() {
+            mCollator = Collator.getInstance();
+            mCollator.setStrength(Collator.PRIMARY);
+            mCollator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+        }
+
+        /**
+         * Returns true if {@param query} is a prefix of {@param target}.
+         */
+        public boolean matches(String query, String target) {
+            switch (mCollator.compare(query, target)) {
+                case 0:
+                    return true;
+                case -1:
+                    // The target string can contain a modifier which would make it larger than
+                    // the query string (even though the length is same). If the query becomes
+                    // larger after appending a unicode character, it was originally a prefix of
+                    // the target string and hence should match.
+                    return mCollator.compare(query + MAX_UNICODE, target) > -1;
+                default:
+                    return false;
+            }
+        }
+
+        public static StringMatcher getInstance() {
+            return new StringMatcher();
+        }
+    }
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 744dee0..7f6c8f8 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -60,6 +60,17 @@
                        android:resource="@xml/appwidget_with_config"/>
         </receiver>
 
+        <receiver
+            android:name="com.android.launcher3.testcomponent.AppWidgetDynamicColors"
+            android:exported="true"
+            android:label="Dynamic Colors">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                android:resource="@xml/appwidget_dynamic_colors"/>
+        </receiver>
+
         <activity
             android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
             android:exported="true">
diff --git a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..c5ab030
--- /dev/null
+++ b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:background="?android:attr/colorBackground"
+    android:padding="8dp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:orientation = "horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="prim"/>
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:background="@android:color/system_primary_500"/>
+    </LinearLayout>
+    <LinearLayout
+        android:orientation = "horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="second"/>
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:background="@android:color/system_secondary_500"/>
+    </LinearLayout>
+    <LinearLayout
+        android:orientation = "horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="neutral"/>
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:background="@android:color/system_neutral_500"/>
+    </LinearLayout>
+
+    </LinearLayout>
\ No newline at end of file
diff --git a/tests/res/xml/appwidget_dynamic_colors.xml b/tests/res/xml/appwidget_dynamic_colors.xml
new file mode 100644
index 0000000..f6b9a04
--- /dev/null
+++ b/tests/res/xml/appwidget_dynamic_colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="1dp"
+    android:minHeight="1dp"
+    android:updatePeriodMillis="0"
+    android:initialLayout="@layout/test_layout_appwidget_dynamic_colors"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
new file mode 100644
index 0000000..5fb3454
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 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.testcomponent;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple app widget showing a primary, secondary and neutral color.
+ */
+public class AppWidgetDynamicColors extends AppWidgetProvider {
+}