Merge "Prevent crash when display is already removed by the time we get displayAdded" into ub-launcher3-master
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f84a82e..f36439d 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -46,6 +46,12 @@
             tools:node="replace" >
         </activity>
 
+        <service
+            android:name="com.android.launcher3.notification.NotificationListener"
+            android:label="@string/notification_dots_service_title"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+            android:enabled="false"
+            tools:node="replace" />
     </application>
 
 </manifest>
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 3b3dc01..89b3831 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -41,6 +41,7 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = true;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
 
     private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
 
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index cc7b712..810f4e3 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -43,9 +43,13 @@
 import android.util.Log;
 
 import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
@@ -74,6 +78,9 @@
     private static final int MSG_PACKAGE_REMOVED = 2;
     private static final int MSG_FULL_REFRESH = 3;
 
+    private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
+    private static final int IN_MINIMAL_DEVICE = 2;
+
     // Welbeing contract
     private static final String PATH_ACTIONS = "actions";
     private static final String PATH_MINIMAL_DEVICE = "minimal_device";
@@ -84,6 +91,8 @@
     private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
     private static final String EXTRA_PACKAGES = "packages";
     private static final String EXTRA_SUCCESS = "success";
+    private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state";
+    private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db";
 
     public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
             new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -121,11 +130,12 @@
                     updateWellbeingData();
                 } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
                     // Wellbeing reports that minimal device state or config is changed.
-                    updateLauncherModel();
+                    updateLauncherModel(context);
                 }
             }
         };
-        FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, this::updateLauncherModel);
+        FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
+                updateLauncherModel(context));
 
         if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
             context.registerReceiver(
@@ -170,7 +180,6 @@
             Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
             if (mIsInTest) throw new RuntimeException(e);
         }
-
         updateWellbeingData();
     }
 
@@ -208,10 +217,34 @@
         mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
     }
 
-    private void updateLauncherModel() {
-        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) return;
+    private void updateLauncherModel(@NonNull final Context context) {
+        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
+            reloadLauncherInNormalMode(context);
+            return;
+        }
+        runWithMinimalDeviceConfigs((bundle) -> {
+            if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE)
+                    == IN_MINIMAL_DEVICE) {
+                reloadLauncherInMinimalMode(context);
+            } else {
+                reloadLauncherInNormalMode(context);
+            }
+        });
+    }
 
-        // TODO: init Launcher in minimal device / normal mode
+    private void reloadLauncherInNormalMode(@NonNull final Context context) {
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
+                InvariantDeviceProfile.INSTANCE.get(context).dbFile);
+    }
+
+    private void reloadLauncherInMinimalMode(@NonNull final Context context) {
+        final Bundle extras = new Bundle();
+        extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
+                mWellbeingProviderPkg + ".api");
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
+                DB_NAME_MINIMAL_DEVICE, extras);
     }
 
     private Uri.Builder apiBuilder() {
@@ -225,6 +258,9 @@
      */
     @WorkerThread
     private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
+        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
+            return;
+        }
         if (DEBUG || mIsInTest) {
             Log.d(TAG, "runWithMinimalDeviceConfigs() called");
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 1b8e244..aad7e17 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
@@ -70,6 +71,7 @@
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
+        SCRIM_MULTIPLIER.set(scrim, 1f);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
     }
 
@@ -108,6 +110,8 @@
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
                 config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+        setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f,
+                config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
 
         setter.setFloat(
                 mRecentsView, getTaskModalnessProperty(),
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 57fd11a..da3c485 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -66,8 +66,8 @@
         SingleAxisSwipeDetector.Listener {
 
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
-    // How much of the overview scrim we can remove during the transition.
-    private static final float OVERVIEW_TO_HOME_SCRIM_PROGRESS = 0.5f;
+    // The min amount of overview scrim we keep during the transition.
+    private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
 
     private final Launcher mLauncher;
     private final SingleAxisSwipeDetector mSwipeDetector;
@@ -163,11 +163,11 @@
             RecentsView recentsView = mLauncher.getOverviewPanel();
             AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
                     builder);
-            float endScrimAlpha = Utilities.mapRange(OVERVIEW_TO_HOME_SCRIM_PROGRESS,
-                    mStartState.getOverviewScrimAlpha(mLauncher),
-                    mEndState.getOverviewScrimAlpha(mLauncher));
+
             builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(),
-                    OverviewScrim.SCRIM_PROGRESS, endScrimAlpha, PULLBACK_INTERPOLATOR);
+                    OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
+                    PULLBACK_INTERPOLATOR);
+
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
                 builder.addOnFrameCallback(recentsView::redrawLiveTile);
             }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 3586b4f..df6194d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -82,7 +82,15 @@
         mDetector = new SingleAxisSwipeDetector(activity, this, dir);
     }
 
-    private boolean canInterceptTouch() {
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0) {
+            // Don't intercept swipes on the nav bar, as user might be trying to go home
+            // during a task dismiss animation.
+            if (mCurrentAnimation != null) {
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+            return false;
+        }
         if (mCurrentAnimation != null) {
             mCurrentAnimation.forceFinishIfCloseToEnd();
         }
@@ -118,7 +126,7 @@
             clearState();
         }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = !canInterceptTouch();
+            mNoIntercept = !canInterceptTouch(ev);
             if (mNoIntercept) {
                 return false;
             }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 5ec6377..ba0a81f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -659,17 +659,17 @@
                     runningComponent != null && runningComponent.equals(homeComponent);
         }
 
-        if (gestureState.getRunningTask() == null) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+                && gestureState.getActivityInterface().isInLiveTileMode()) {
+            return createOverviewInputConsumer(
+                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
+        } else if (gestureState.getRunningTask() == null) {
             return mResetGestureInputConsumer;
         } else if (previousGestureState.isRunningAnimationToLauncher()
                 || gestureState.getActivityInterface().isResumed()
                 || forceOverviewInputConsumer) {
             return createOverviewInputConsumer(
                     previousGestureState, gestureState, event, forceOverviewInputConsumer);
-        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode()) {
-            return createOverviewInputConsumer(
-                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
         } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
             return mResetGestureInputConsumer;
         } else {
diff --git a/res/layout/search_result_play_item.xml b/res/layout/search_result_play_item.xml
new file mode 100644
index 0000000..4e82eaf
--- /dev/null
+++ b/res/layout/search_result_play_item.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<com.android.launcher3.views.SearchResultPlayItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    android:orientation="horizontal">
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size"
+        android:layout_gravity="start|center_vertical"
+        android:background="@drawable/ic_deepshortcut_placeholder" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/title_view"
+            style="@style/TextHeadline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="16sp" />
+
+        <TextView
+            android:id="@+id/detail_0"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <TextView
+            android:id="@+id/detail_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:visibility="gone" />
+
+        <TextView
+            android:id="@+id/detail_2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:visibility="gone" />
+    </LinearLayout>
+    <Button
+        android:id="@+id/try_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:background="?android:attr/selectableItemBackground"
+        android:text="@string/search_action_try_now">
+    </Button>
+
+
+</com.android.launcher3.views.SearchResultPlayItem>
diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml
index c39a641..9419015 100644
--- a/res/layout/search_section_title.xml
+++ b/res/layout/search_section_title.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:id="@+id/section_title"
-          android:textSize="14sp"
-          android:fontFamily="@style/TextHeadline"
-          android:layout_width="wrap_content"
-          android:textColor="?android:attr/textColorPrimary"
-          android:padding="4dp"
-          android:layout_height="wrap_content"/>
\ No newline at end of file
+<com.android.launcher3.views.SearchSectionHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/section_title"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:fontFamily="@style/TextHeadline"
+    android:padding="4dp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:textSize="14sp" />
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ef47eef..ad3e2b7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -70,6 +70,8 @@
     <!--All apps Search-->
     <!-- Section title for apps [CHAR_LIMIT=50] -->
     <string name="search_corpus_apps">Apps</string>
+    <!-- try instant app action for play search result [CHAR_LIMIT=50 -->
+    <string name="search_action_try_now">Try Now</string>
 
     <!-- Popup items -->
     <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index d64967b..02c6162 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -24,6 +24,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.UiThreadHelper;
 
 
@@ -130,6 +131,10 @@
     public void reset() {
         if (!TextUtils.isEmpty(getText())) {
             setText("");
+        } else {
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                return;
+            }
         }
         if (isFocused()) {
             View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fdbbf4e..2973cf7 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -100,10 +100,12 @@
     public static final int SCHEMA_VERSION = 28;
 
     public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
+    public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
 
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
     protected DatabaseHelper mOpenHelper;
+    protected String mProviderAuthority;
 
     private long mLastRestoreTimestamp = 0L;
 
@@ -367,7 +369,8 @@
             case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
                 Bundle result = new Bundle();
                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false));
+                        Utilities.getPrefs(getContext()).getBoolean(
+                                mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false));
                 return result;
             }
             case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
@@ -437,6 +440,7 @@
                                             getContext(), true /* forMigration */)));
                     return result;
                 }
+                return null;
             }
             case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
                 if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
@@ -450,6 +454,23 @@
                                     () -> mOpenHelper));
                     return result;
                 }
+                return null;
+            }
+            case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
+                if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
+                final DatabaseHelper helper = mOpenHelper;
+                if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
+                    mProviderAuthority = null;
+                } else {
+                    mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
+                }
+                mOpenHelper = DatabaseHelper.createDatabaseHelper(
+                        getContext(), arg, false /* forMigration */);
+                helper.close();
+                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+                if (app == null) return null;
+                app.getModel().forceReload();
+                return null;
             }
         }
         return null;
@@ -492,7 +513,8 @@
     }
 
     private void clearFlagEmptyDbCreated() {
-        Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
+        Utilities.getPrefs(getContext()).edit()
+                .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
     }
 
     /**
@@ -505,7 +527,7 @@
     synchronized private void loadDefaultFavoritesIfNecessary() {
         SharedPreferences sp = Utilities.getPrefs(getContext());
 
-        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
+        if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
             Log.d(TAG, "loading default workspace");
 
             AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
@@ -553,8 +575,13 @@
      */
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
         Context ctx = getContext();
-        String authority = Settings.Secure.getString(ctx.getContentResolver(),
-                "launcher3.layout.provider");
+        final String authority;
+        if (!TextUtils.isEmpty(mProviderAuthority)) {
+            authority = mProviderAuthority;
+        } else {
+            authority = Settings.Secure.getString(ctx.getContentResolver(),
+                    "launcher3.layout.provider");
+        }
         if (TextUtils.isEmpty(authority)) {
             return null;
         }
@@ -694,11 +721,25 @@
         }
 
         /**
+         * Re-composite given key in respect to database. If the current db is
+         * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
+         * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+         * string will be "EMPTY_DATABASE_CREATED@minimal.db".
+         */
+        String getKey(final String key) {
+            if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
+                return key;
+            }
+            return key + "@" + getDatabaseName();
+        }
+
+        /**
          * Overriden in tests.
          */
         protected void onEmptyDbCreated() {
             // Set the flag for empty DB
-            Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
+            Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
+                    .commit();
         }
 
         public long getSerialNumberForUser(UserHandle user) {
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 5512654..58a418e 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -354,14 +354,20 @@
 
         public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
 
+        public static final String METHOD_SWITCH_DATABASE = "switch_database";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
-            return call(cr, method, null);
+            return call(cr, method, null /* arg */);
         }
 
         public static Bundle call(ContentResolver cr, String method, String arg) {
-            return cr.call(CONTENT_URI, method, arg, null);
+            return call(cr, method, arg, null /* extras */);
+        }
+
+        public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
+            return cr.call(CONTENT_URI, method, arg, extras);
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index af3722a..0684fe0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -42,6 +44,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.core.os.BuildCompat;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -108,7 +111,7 @@
 
     private final MultiValueAlpha mMultiValueAlpha;
 
-    Rect mInsets = new Rect();
+    private Rect mInsets = new Rect();
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -204,6 +207,17 @@
         mAH[AdapterHolder.WORK].applyPadding();
     }
 
+    private void hideInput() {
+        if (!BuildCompat.isAtLeastR() || !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
+
+        WindowInsets insets = getRootWindowInsets();
+        if (insets == null) return;
+
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            getWindowInsetsController().hide(WindowInsets.Type.ime());
+        }
+    }
+
     /**
      * Returns whether the view itself will handle the touch event or not.
      */
@@ -219,9 +233,14 @@
         }
         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
+            hideInput();
             return false;
         }
-        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        if (shouldScroll) {
+            hideInput();
+        }
+        return shouldScroll;
     }
 
     @Override
@@ -528,6 +547,25 @@
     }
 
     /**
+     * Handles selection on focused view and returns success
+     */
+    public boolean selectFocusedView(View v) {
+        ItemInfo itemInfo = getHighlightedItemInfo();
+        if (itemInfo != null) {
+            return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
+        }
+        AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
+        if (focusedItem instanceof AdapterItemWithPayload) {
+            Runnable onSelection = ((AdapterItemWithPayload) focusedItem).getSelectionHandler();
+            if (onSelection != null) {
+                onSelection.run();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns the ItemInfo of a view that is in focus, ready to be launched by an IME.
      */
     public ItemInfo getHighlightedItemInfo() {
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 2cec797..c61f01f 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -40,10 +40,10 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.views.HeroSearchResultView;
 
 import java.util.List;
 
@@ -71,6 +71,8 @@
 
     public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
 
+    public static final int DETAIL_ROW_WITH_BUTTON = 1 << 7;
+
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
@@ -86,6 +88,108 @@
     }
 
     /**
+     * Info about a particular adapter item (can be either section or app)
+     */
+    public static class AdapterItem {
+        /** Common properties */
+        // The index of this adapter item in the list
+        public int position;
+        // The type of this item
+        public int viewType;
+
+        /** App-only properties */
+        // The section name of this app.  Note that there can be multiple items with different
+        // sectionNames in the same section
+        public String sectionName = null;
+        // The row that this item shows up on
+        public int rowIndex;
+        // The index of this app in the row
+        public int rowAppIndex;
+        // The associated AppInfo for the app
+        public AppInfo appInfo = null;
+        // The index of this app not including sections
+        public int appIndex = -1;
+        // Search section associated to result
+        public SearchSectionInfo searchSectionInfo = null;
+
+        /**
+         * Factory method for AppIcon AdapterItem
+         */
+        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+                int appIndex) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_ICON;
+            item.position = pos;
+            item.sectionName = sectionName;
+            item.appInfo = appInfo;
+            item.appIndex = appIndex;
+            return item;
+        }
+
+        /**
+         * Factory method for empty search results view
+         */
+        public static AdapterItem asEmptySearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_EMPTY_SEARCH;
+            item.position = pos;
+            return item;
+        }
+
+        /**
+         * Factory method for a dividerView in AllAppsSearch
+         */
+        public static AdapterItem asAllAppsDivider(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
+            item.position = pos;
+            return item;
+        }
+
+        /**
+         * Factory method for a market search button
+         */
+        public static AdapterItem asMarketSearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_SEARCH_MARKET;
+            item.position = pos;
+            return item;
+        }
+
+        boolean isCountedForAccessibility() {
+            return viewType == VIEW_TYPE_ICON
+                    || viewType == VIEW_TYPE_SEARCH_HERO_APP
+                    || viewType == DETAIL_ROW_WITH_BUTTON;
+        }
+    }
+
+    /**
+     * Extension of AdapterItem that contains an extra payload specific to item
+     * @param <T> Play load Type
+     */
+    public static class AdapterItemWithPayload<T> extends AdapterItem {
+        private T mPayload;
+        private Runnable mSelectionHandler;
+
+        public AdapterItemWithPayload(T payload, int type) {
+            mPayload = payload;
+            viewType = type;
+        }
+
+        public void setSelectionHandler(Runnable runnable) {
+            mSelectionHandler = runnable;
+        }
+
+        public Runnable getSelectionHandler() {
+            return mSelectionHandler;
+        }
+
+        public T getPayload() {
+            return mPayload;
+        }
+    }
+
+    /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
     public class AppsGridLayoutManager extends GridLayoutManager {
@@ -286,6 +390,9 @@
             case VIEW_TYPE_SEARCH_HERO_APP:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.search_result_hero_app, parent, false));
+            case DETAIL_ROW_WITH_BUTTON:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_play_item, parent, false));
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -315,15 +422,11 @@
                 }
                 break;
             case VIEW_TYPE_SEARCH_CORPUS_TITLE:
-                TextView titleView = (TextView) holder.itemView;
-                titleView.setText(mApps.getAdapterItems().get(position).searchSectionInfo.getTitle(
-                        titleView.getContext()));
-                break;
+            case DETAIL_ROW_WITH_BUTTON:
             case VIEW_TYPE_SEARCH_HERO_APP:
-                HeroSearchResultView heroView = (HeroSearchResultView) holder.itemView;
-                heroView.prepareUsingAdapterItem(
-                        (AlphabeticalAppsList.HeroAppAdapterItem) mApps.getAdapterItems().get(
-                                position));
+                PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
+                payloadResultView.applyAdapterInfo(
+                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position));
                 break;
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
@@ -344,7 +447,7 @@
 
     @Override
     public int getItemViewType(int position) {
-        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+        AdapterItem item = mApps.getAdapterItems().get(position);
         return item.viewType;
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 640ef01..13a93ff 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -278,7 +278,7 @@
         if (mApps == null) {
             return;
         }
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
@@ -352,7 +352,7 @@
     @Override
     public int getCurrentScrollY() {
         // Return early if there are no items or we haven't been measured
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
             return -1;
         }
@@ -368,14 +368,14 @@
     }
 
     public int getCurrentScrollY(int position, int offset) {
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        AlphabeticalAppsList.AdapterItem posItem = position < items.size() ?
-                items.get(position) : null;
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
+        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
+                ? items.get(position) : null;
         int y = mCachedScrollPositions.get(position, -1);
         if (y < 0) {
             y = 0;
             for (int i = 0; i < position; i++) {
-                AlphabeticalAppsList.AdapterItem item = items.get(i);
+                AllAppsGridAdapter.AdapterItem item = items.get(i);
                 if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
                     // Break once we reach the desired row
                     if (posItem != null && posItem.viewType == item.viewType &&
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index a168c06..6f29e11 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -47,13 +47,13 @@
         // Since views in the same section will follow each other, we can skip to a last view in
         // a section to get the bounds of the section without having to iterate on every item.
         int itemCount = parent.getChildCount();
-        List<AlphabeticalAppsList.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
         SectionDecorationHandler lastDecorationHandler = null;
         int i = 0;
         while (i < itemCount) {
             View view = parent.getChildAt(i);
             int position = parent.getChildAdapterPosition(view);
-            AlphabeticalAppsList.AdapterItem adapterItem = adapterItems.get(position);
+            AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
             if (adapterItem.searchSectionInfo != null) {
                 SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
                 int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 7379dbed..8c059d5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -19,10 +19,10 @@
 import android.content.Context;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
 import com.android.launcher3.allapps.search.SearchSectionInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
 
@@ -62,101 +62,6 @@
         }
     }
 
-    /**
-     * Info about a particular adapter item (can be either section or app)
-     */
-    public static class AdapterItem {
-        /** Common properties */
-        // The index of this adapter item in the list
-        public int position;
-        // The type of this item
-        public int viewType;
-
-        /** App-only properties */
-        // The section name of this app.  Note that there can be multiple items with different
-        // sectionNames in the same section
-        public String sectionName = null;
-        // The row that this item shows up on
-        public int rowIndex;
-        // The index of this app in the row
-        public int rowAppIndex;
-        // The associated AppInfo for the app
-        public AppInfo appInfo = null;
-        // The index of this app not including sections
-        public int appIndex = -1;
-        // Search section associated to result
-        public SearchSectionInfo searchSectionInfo = null;
-
-        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
-                int appIndex) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
-            item.position = pos;
-            item.sectionName = sectionName;
-            item.appInfo = appInfo;
-            item.appIndex = appIndex;
-            return item;
-        }
-
-        public static AdapterItem asEmptySearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
-            item.position = pos;
-            return item;
-        }
-
-        public static AdapterItem asAllAppsDivider(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
-            item.position = pos;
-            return item;
-        }
-
-        public static AdapterItem asMarketSearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
-            item.position = pos;
-            return item;
-        }
-
-        /**
-         * Factory method for search section title AdapterItem
-         */
-        public static AdapterItem asSearchTitle(SearchSectionInfo sectionInfo, int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE;
-            item.position = pos;
-            item.searchSectionInfo = sectionInfo;
-            return item;
-        }
-
-        boolean isCountedForAccessibility() {
-            return viewType == AllAppsGridAdapter.VIEW_TYPE_ICON
-                    || viewType == AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
-        }
-    }
-
-    /**
-     * Extension of AdapterItem that contains shortcut workspace items
-     */
-    public static class HeroAppAdapterItem extends AdapterItem {
-        private ArrayList<WorkspaceItemInfo> mShortcutInfos;
-
-        public HeroAppAdapterItem(AppInfo info, ArrayList<WorkspaceItemInfo> shortcutInfos) {
-            viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
-            mShortcutInfos = shortcutInfos;
-            appInfo = info;
-        }
-
-        /**
-         * Returns list of shortcuts for appInfo
-         */
-        public ArrayList<WorkspaceItemInfo> getShortcutInfos() {
-            return mShortcutInfos;
-        }
-
-    }
-
 
     private final BaseDraggingActivity mLauncher;
 
@@ -396,7 +301,9 @@
                 adapterItem.position = i;
                 mAdapterItems.add(adapterItem);
                 if (adapterItem.searchSectionInfo != lastSection) {
-                    adapterItem.searchSectionInfo.setPosStart(i);
+                    if (adapterItem.searchSectionInfo != null) {
+                        adapterItem.searchSectionInfo.setPosStart(i);
+                    }
                     if (lastSection != null) {
                         lastSection.setPosEnd(i - 1);
                     }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 0d87481..2e5ed3e 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,35 +15,26 @@
  */
 package com.android.launcher3.allapps.search;
 
-import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnFocusChangeListener;
 import android.view.inputmethod.EditorInfo;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
-import android.widget.Toast;
-
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.systemui.plugins.AllAppsSearchPlugin;
-import com.android.systemui.plugins.PluginListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -54,17 +45,14 @@
  */
 public class AllAppsSearchBarController
         implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
-        OnFocusChangeListener, PluginListener<AllAppsSearchPlugin> {
+        OnFocusChangeListener {
 
-    private static final String TAG = "AllAppsSearchBarContoller";
     protected BaseDraggingActivity mLauncher;
     protected Callbacks mCb;
     protected ExtendedEditText mInput;
     protected String mQuery;
 
     protected SearchAlgorithm mSearchAlgorithm;
-    private AllAppsSearchPlugin mPlugin;
-    private Consumer mPlubinCb;
 
     public void setVisibility(int visibility) {
         mInput.setVisibility(visibility);
@@ -85,18 +73,13 @@
         mInput.setOnBackKeyListener(this);
         mInput.setOnFocusChangeListener(this);
         mSearchAlgorithm = searchAlgorithm;
-
-        PluginManagerWrapper.INSTANCE.get(launcher).addPluginListener(this,
-                AllAppsSearchPlugin.class);
-        mPlubinCb = secondaryCb;
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        if (mPlugin != null) {
-            if (s.length() == 0) {
-                mPlugin.startedTyping();
-            }
+    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+        if (mSearchAlgorithm instanceof PluginWrapper) {
+            ((PluginWrapper) mSearchAlgorithm).runOnPluginIfConnected(
+                    AllAppsSearchPlugin::startedTyping);
         }
     }
 
@@ -114,9 +97,6 @@
         } else {
             mSearchAlgorithm.cancel(false);
             mSearchAlgorithm.doSearch(mQuery, mCb);
-            if (mPlugin != null) {
-                mPlugin.performSearch(mQuery, mPlubinCb);
-            }
         }
     }
 
@@ -133,10 +113,8 @@
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
         if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
             if (actionId == EditorInfo.IME_ACTION_SEARCH) {
-                ItemInfo info = Launcher.getLauncher(mLauncher).getAppsView()
-                        .getHighlightedItemInfo();
-                if (info != null) {
-                    return mLauncher.startActivitySafely(v, info.getIntent(), info);
+                if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
+                    return true;
                 }
             }
         }
@@ -197,43 +175,14 @@
         return mInput.isFocused();
     }
 
-    @Override
-    public void onPluginConnected(AllAppsSearchPlugin allAppsSearchPlugin, Context context) {
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-            mPlugin = allAppsSearchPlugin;
-            checkCallPermission();
-        }
-    }
-
     /**
-     * Check call permissions.
+     * A wrapper setup for running essential calls to plugin from search controller
      */
-    public void checkCallPermission() {
-        final String[] permission = {"android.permission.CALL_PHONE",
-                "android.permission.READ_CONTACTS"};
-        boolean request = false;
-        for (String p : permission) {
-            int permissionCheck = ContextCompat.checkSelfPermission(mLauncher, p);
-            if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
-                request = true;
-            }
-        }
-
-        if (!request) return;
-        boolean rationale = false;
-        for (String p : permission) {
-            if (mLauncher.shouldShowRequestPermissionRationale(p)) {
-                rationale = true;
-            }
-            if (rationale) {
-                Log.e(TAG, p + " Show rationale");
-                Toast.makeText(mLauncher, "Requesting Permissions", Toast.LENGTH_SHORT).show();
-            } else {
-                ActivityCompat.requestPermissions(mLauncher,  permission,  123);
-                Log.e(TAG, p + " request permission");
-            }
-        }
-
+    public interface PluginWrapper {
+        /**
+         * executes call if plugin is connected
+         */
+        void runOnPluginIfConnected(Consumer<AllAppsSearchPlugin> plugin);
     }
 
     /**
@@ -246,11 +195,23 @@
          *
          * @param items sorted list of search result adapter items.
          */
-        void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items);
+        void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
 
         /**
          * Called when the search results should be cleared.
          */
         void clearSearchResult();
     }
+
+    /**
+     * An interface for supporting dynamic search results
+     *
+     * @param <T> Type of payload
+     */
+    public interface PayloadResultHandler<T> {
+        /**
+         * Updates View using Adapter's payload
+         */
+        void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index e8a0d7a..6f183ee 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
@@ -172,7 +173,7 @@
     }
 
     @Override
-    public void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items) {
+    public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
         if (items != null) {
             mApps.setSearchResults(items);
             notifyResultChanged();
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
index e67e897..fb3d953 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.allapps.search;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP;
 
 import android.content.Context;
 import android.content.pm.ShortcutInfo;
@@ -23,9 +24,9 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
 import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
-import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
-import com.android.launcher3.allapps.AlphabeticalAppsList.HeroAppAdapterItem;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
@@ -82,7 +83,7 @@
 
     /**
      * Returns MAX_SHORTCUTS_COUNT shortcuts from local cache
-     * TODO: Shortcuts should be ranked based on relevancy 
+     * TODO: Shortcuts should be ranked based on relevancy
      */
     private ArrayList<WorkspaceItemInfo> getShortcutInfos(Context context, AppInfo appInfo) {
         List<ShortcutInfo> shortcuts = new ShortcutRequest(context, appInfo.user)
@@ -126,7 +127,9 @@
             //hero app
             AppInfo appInfo = apps.get(i);
             ArrayList<WorkspaceItemInfo> shortcuts = getShortcutInfos(context, appInfo);
-            AdapterItem adapterItem = new HeroAppAdapterItem(appInfo, shortcuts);
+            AdapterItem adapterItem = new AllAppsGridAdapter.AdapterItemWithPayload(shortcuts,
+                    VIEW_TYPE_SEARCH_HERO_APP);
+            adapterItem.appInfo = appInfo;
             adapterItem.searchSectionInfo = mSearchSectionInfo;
             adapterItems.add(adapterItem);
         }
diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java
index 3216740..545f0e3 100644
--- a/src/com/android/launcher3/allapps/search/SearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.allapps.search;
 
-import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -28,5 +28,5 @@
     /**
      * Perform query
      */
-    void performSearch(String query, Consumer<ArrayList<AlphabeticalAppsList.AdapterItem>> cb);
+    void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
 }
diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
index dee0ffd..e026e84 100644
--- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
+++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.allapps.search;
 
-import android.content.Context;
-
 import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
 
 /**
@@ -24,7 +22,7 @@
  */
 public class SearchSectionInfo {
 
-    private final int mTitleResId;
+    private String mTitle;
     private SectionDecorationHandler mDecorationHandler;
 
     public int getPosStart() {
@@ -47,11 +45,11 @@
     private int mPosEnd;
 
     public SearchSectionInfo() {
-        this(-1);
+        this(null);
     }
 
-    public SearchSectionInfo(int titleResId) {
-        mTitleResId = titleResId;
+    public SearchSectionInfo(String title) {
+        mTitle = title;
     }
 
     public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
@@ -66,10 +64,7 @@
     /**
      * Returns the section's title
      */
-    public String getTitle(Context context) {
-        if (mTitleResId == -1) {
-            return "";
-        }
-        return context.getString(mTitleResId);
+    public String getTitle() {
+        return mTitle == null ? "" : mTitle;
     }
 }
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index 94acbfd..c0c3e5e 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -33,10 +34,25 @@
  */
 public class OverviewScrim extends Scrim {
 
+    public static final FloatProperty<OverviewScrim> SCRIM_MULTIPLIER =
+            new FloatProperty<OverviewScrim>("scrimMultiplier") {
+                @Override
+                public Float get(OverviewScrim scrim) {
+                    return scrim.mScrimMultiplier;
+                }
+
+                @Override
+                public void setValue(OverviewScrim scrim, float v) {
+                    scrim.setScrimMultiplier(v);
+                }
+            };
+
     private @NonNull View mStableScrimmedView;
     // Might be higher up if mStableScrimmedView is invisible.
     private @Nullable View mCurrentScrimmedView;
 
+    private float mScrimMultiplier = 1f;
+
     public OverviewScrim(View view) {
         super(view);
         mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
@@ -68,4 +84,16 @@
     public @Nullable View getScrimmedView() {
         return mCurrentScrimmedView;
     }
+
+    private void setScrimMultiplier(float scrimMultiplier) {
+        if (Float.compare(mScrimMultiplier, scrimMultiplier) != 0) {
+            mScrimMultiplier = scrimMultiplier;
+            invalidate();
+        }
+    }
+
+    @Override
+    protected int getScrimAlpha() {
+        return Math.round(super.getScrimAlpha() * mScrimMultiplier);
+    }
 }
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
index f90962d..a151cba 100644
--- a/src/com/android/launcher3/graphics/Scrim.java
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -61,7 +61,11 @@
     }
 
     public void draw(Canvas canvas) {
-        canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha));
+        canvas.drawColor(setColorAlphaBound(mScrimColor, getScrimAlpha()));
+    }
+
+    protected int getScrimAlpha() {
+        return mScrimAlpha;
     }
 
     private void setScrimProgress(float progress) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 7e45021..cf32514 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -297,8 +297,7 @@
                         .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                         .map(ShortcutKey::fromItemInfo),
                     // Pending shortcuts
-                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts()
-                        .stream().filter(si -> si.user.equals(user)))
+                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
                 .collect(groupingBy(ShortcutKey::getPackageName,
                         mapping(ShortcutKey::getId, Collectors.toSet())));
 
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 6238db3..5e48a0f 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
 import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -30,11 +29,9 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
-import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
@@ -47,27 +44,19 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PersistedItemArray;
 import com.android.launcher3.util.Preconditions;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Class to maintain a queue of pending items to be added to the workspace.
@@ -79,7 +68,6 @@
     public static final int FLAG_DRAG_AND_DROP = 4;
 
     private static final String TAG = "InstallShortcutReceiver";
-    private static final boolean DBG = false;
 
     // The set of shortcuts that are pending install
     private static final String APPS_PENDING_INSTALL = "apps_to_install";
@@ -90,24 +78,34 @@
     public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
             new MainThreadInitializedObject<>(ItemInstallQueue::new);
 
+    private final PersistedItemArray<PendingInstallShortcutInfo> mStorage =
+            new PersistedItemArray<>(APPS_PENDING_INSTALL);
     private final Context mContext;
 
     // Determines whether to defer installing shortcuts immediately until
     // processAllPendingInstalls() is called.
     private int mInstallQueueDisabledFlags = 0;
 
+    // Only accessed on worker thread
+    private List<PendingInstallShortcutInfo> mItems;
+
     private ItemInstallQueue(Context context) {
         mContext = context;
     }
 
     @WorkerThread
+    private void ensureQueueLoaded() {
+        Preconditions.assertWorkerThread();
+        if (mItems == null) {
+            mItems = mStorage.read(mContext, this::decode);
+        }
+    }
+
+    @WorkerThread
     private void addToQueue(PendingInstallShortcutInfo info) {
-        String encoded = info.encodeToString(mContext);
-        SharedPreferences prefs = Utilities.getPrefs(mContext);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
-        strings.add(encoded);
-        prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+        ensureQueueLoaded();
+        mItems.add(info);
+        mStorage.write(mContext, mItems);
     }
 
     @WorkerThread
@@ -117,28 +115,21 @@
             // Launcher not loaded
             return;
         }
-
-        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-        SharedPreferences prefs = Utilities.getPrefs(mContext);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-        if (strings == null) {
+        ensureQueueLoaded();
+        if (mItems.isEmpty()) {
             return;
         }
 
-        for (String encoded : strings) {
-            PendingInstallShortcutInfo info = decode(encoded, mContext);
-            if (info == null) {
-                continue;
-            }
+        List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
+                .map(info -> info.getItemInfo(mContext))
+                .collect(Collectors.toList());
 
-            // Generate a shortcut info to add into the model
-            installQueue.add(info.getItemInfo(mContext));
-        }
-        prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+        // Add the items and clear queue
         if (!installQueue.isEmpty()) {
             launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
         }
+        mItems.clear();
+        mStorage.getFile(mContext).delete();
     }
 
     /**
@@ -149,33 +140,11 @@
         if (packageNames.isEmpty()) {
             return;
         }
-        Preconditions.assertWorkerThread();
-
-        SharedPreferences sp = Utilities.getPrefs(mContext);
-        Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) {
-            Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
-                    + ", removing packages: " + packageNames);
+        ensureQueueLoaded();
+        if (mItems.removeIf(item ->
+                item.user.equals(user) && packageNames.contains(getIntentPackage(item.intent)))) {
+            mStorage.write(mContext, mItems);
         }
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            return;
-        }
-        Set<String> newStrings = new HashSet<>(strings);
-        Iterator<String> newStringsIter = newStrings.iterator();
-        while (newStringsIter.hasNext()) {
-            String encoded = newStringsIter.next();
-            try {
-                Decoder decoder = new Decoder(encoded, mContext);
-                if (packageNames.contains(getIntentPackage(decoder.intent))
-                        && user.equals(decoder.user)) {
-                    newStringsIter.remove();
-                }
-            } catch (JSONException | URISyntaxException e) {
-                Log.d(TAG, "Exception reading shortcut to add: " + e);
-                newStringsIter.remove();
-            }
-        }
-        sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
     }
 
     /**
@@ -200,28 +169,14 @@
     }
 
     /**
-     * Returns all pending shorts in the queue
+     * Returns a stream of all pending shortcuts in the queue
      */
     @WorkerThread
-    public HashSet<ShortcutKey> getPendingShortcuts() {
-        HashSet<ShortcutKey> result = new HashSet<>();
-
-        Set<String> strings = Utilities.getPrefs(mContext).getStringSet(APPS_PENDING_INSTALL, null);
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            return result;
-        }
-
-        for (String encoded : strings) {
-            try {
-                Decoder decoder = new Decoder(encoded, mContext);
-                if (decoder.optInt(Favorites.ITEM_TYPE, -1) == ITEM_TYPE_DEEP_SHORTCUT) {
-                    result.add(ShortcutKey.fromIntent(decoder.intent, decoder.user));
-                }
-            } catch (JSONException | URISyntaxException e) {
-                Log.d(TAG, "Exception reading shortcut to add: " + e);
-            }
-        }
-        return result;
+    public Stream<ShortcutKey> getPendingShortcuts(UserHandle user) {
+        ensureQueueLoaded();
+        return mItems.stream()
+                .filter(item -> item.itemType == ITEM_TYPE_DEEP_SHORTCUT && user.equals(item.user))
+                .map(item -> ShortcutKey.fromIntent(item.intent, user));
     }
 
     private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
@@ -293,19 +248,9 @@
             providerInfo = info;
         }
 
-        public String encodeToString(Context context) {
-            try {
-                return new JSONStringer()
-                        .object()
-                        .key(Favorites.ITEM_TYPE).value(itemType)
-                        .key(Favorites.INTENT).value(intent.toUri(0))
-                        .key(PROFILE_ID).value(
-                                UserCache.INSTANCE.get(context).getSerialNumberForUser(user))
-                        .endObject().toString();
-            } catch (JSONException e) {
-                Log.d(TAG, "Exception when adding shortcut: " + e);
-                return null;
-            }
+        @Override
+        public Intent getIntent() {
+            return intent;
         }
 
         public Pair<ItemInfo, Object> getItemInfo(Context context) {
@@ -365,55 +310,33 @@
                 ? intent.getPackage() : intent.getComponent().getPackageName();
     }
 
-    private static PendingInstallShortcutInfo decode(String encoded, Context context) {
-        try {
-            Decoder decoder = new Decoder(encoded, context);
-            switch (decoder.optInt(Favorites.ITEM_TYPE, -1)) {
-                case Favorites.ITEM_TYPE_APPLICATION:
-                    return new PendingInstallShortcutInfo(
-                            decoder.intent.getPackage(), decoder.user);
-                case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
-                    List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.intent, decoder.user)
-                            .buildRequest(context)
-                            .query(ShortcutRequest.ALL);
-                    if (si.isEmpty()) {
-                        return null;
-                    } else {
-                        return new PendingInstallShortcutInfo(si.get(0));
-                    }
+    private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
+        switch (itemType) {
+            case Favorites.ITEM_TYPE_APPLICATION:
+                return new PendingInstallShortcutInfo(intent.getPackage(), user);
+            case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                List<ShortcutInfo> si = ShortcutKey.fromIntent(intent, user)
+                        .buildRequest(mContext)
+                        .query(ShortcutRequest.ALL);
+                if (si.isEmpty()) {
+                    return null;
+                } else {
+                    return new PendingInstallShortcutInfo(si.get(0));
                 }
-                case Favorites.ITEM_TYPE_APPWIDGET: {
-                    int widgetId = decoder.intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
-                    AppWidgetProviderInfo info =
-                            AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId);
-                    if (info == null || !info.provider.equals(decoder.intent.getComponent())
-                            || !info.getProfile().equals(decoder.user)) {
-                        return null;
-                    }
-                    return new PendingInstallShortcutInfo(info, widgetId);
-                }
-                default:
-                    Log.e(TAG, "Unknown item type");
             }
-        } catch (JSONException | URISyntaxException e) {
-            Log.d(TAG, "Exception reading shortcut to add: " + e);
+            case Favorites.ITEM_TYPE_APPWIDGET: {
+                int widgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info =
+                        AppWidgetManager.getInstance(mContext).getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(intent.getComponent())
+                        || !info.getProfile().equals(user)) {
+                    return null;
+                }
+                return new PendingInstallShortcutInfo(info, widgetId);
+            }
+            default:
+                Log.e(TAG, "Unknown item type");
         }
         return null;
     }
-
-    private static class Decoder extends JSONObject {
-        public final Intent intent;
-        public final UserHandle user;
-
-        private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
-            super(encoded);
-            intent = Intent.parseUri(getString(Favorites.INTENT), 0);
-            user = has(PROFILE_ID)
-                    ? UserCache.INSTANCE.get(context).getUserForSerialNumber(getLong(PROFILE_ID))
-                    : Process.myUserHandle();
-            if (user == null || intent == null) {
-                throw new JSONException("Invalid data");
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index ec3a467..922425f 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
@@ -171,6 +172,10 @@
         protected boolean initPreference(Preference preference) {
             switch (preference.getKey()) {
                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
+                    if (WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS) {
+                        return false;
+                    }
+
                     // Listen to system notification dot settings while this UI is active.
                     mNotificationDotsObserver = newNotificationSettingsObserver(
                             getActivity(), (NotificationDotsPreference) preference);
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
index ae20638..7ff2abb 100644
--- a/src/com/android/launcher3/util/PersistedItemArray.java
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -68,8 +68,7 @@
      */
     @WorkerThread
     public void write(Context context, List<T> items) {
-        AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
-
+        AtomicFile file = getFile(context);
         FileOutputStream fos;
         try {
             fos = file.startWrite();
@@ -124,9 +123,7 @@
     @WorkerThread
     public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
         List<T> result = new ArrayList<>();
-        AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
-
-        try (FileInputStream fis = file.openRead()) {
+        try (FileInputStream fis = getFile(context).openRead()) {
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
 
@@ -167,6 +164,13 @@
     }
 
     /**
+     * Returns the underlying file used for persisting data
+     */
+    public AtomicFile getFile(Context context) {
+        return new AtomicFile(context.getFileStreamPath(mFileName));
+    }
+
+    /**
      * Interface to create an ItemInfo during parsing
      */
     public interface ItemFactory<T extends ItemInfo> {
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
index c2a02bc..10f3c41 100644
--- a/src/com/android/launcher3/views/HeroSearchResultView.java
+++ b/src/com/android/launcher3/views/HeroSearchResultView.java
@@ -30,7 +30,8 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -47,7 +48,8 @@
 /**
  * A view representing a high confidence app search result that includes shortcuts
  */
-public class HeroSearchResultView extends LinearLayout implements DragSource {
+public class HeroSearchResultView extends LinearLayout implements DragSource,
+        AllAppsSearchBarController.PayloadResultHandler<List<WorkspaceItemInfo>> {
 
     BubbleTextView mBubbleTextView;
     View mIconView;
@@ -96,18 +98,18 @@
     /**
      * Apply {@link ItemInfo} for appIcon and shortcut Icons
      */
-    public void prepareUsingAdapterItem(AlphabeticalAppsList.HeroAppAdapterItem adapterItem) {
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<List<WorkspaceItemInfo>> adapterItem) {
         mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
         mIconView.setBackground(mBubbleTextView.getIcon());
         mIconView.setTag(adapterItem.appInfo);
-        List<WorkspaceItemInfo> shorcutInfos = adapterItem.getShortcutInfos();
+        List<WorkspaceItemInfo> shorcutInfos = adapterItem.getPayload();
         for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
             mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
             if (i < shorcutInfos.size()) {
                 mDeepShortcutTextViews[i].applyFromWorkspaceItem(shorcutInfos.get(i));
             }
         }
-
     }
 
     @Override
@@ -126,7 +128,6 @@
         mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
     }
 
-
     /**
      * Drag and drop handler for popup items in Launcher activity
      */
diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java
new file mode 100644
index 0000000..19a4c5d
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultPlayItem.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 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.views;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * A View representing a PlayStore item.
+ */
+public class SearchResultPlayItem extends LinearLayout implements
+        AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+    private final DeviceProfile mDeviceProfile;
+    private View mIconView;
+    private TextView mTitleView;
+    private TextView[] mDetailViews = new TextView[3];
+    private Button mPreviewButton;
+    private String mPackageName;
+    private boolean mIsInstantGame;
+
+    public SearchResultPlayItem(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultPlayItem(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
+        mTitleView = findViewById(R.id.title_view);
+        mPreviewButton = findViewById(R.id.try_button);
+        mPreviewButton.setOnClickListener(view -> launchInstantGame());
+        mDetailViews[0] = findViewById(R.id.detail_0);
+        mDetailViews[1] = findViewById(R.id.detail_1);
+        mDetailViews[2] = findViewById(R.id.detail_2);
+
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = mDeviceProfile.allAppsIconSizePx;
+        iconParams.width = mDeviceProfile.allAppsIconSizePx;
+        setOnClickListener(view -> handleSelection());
+
+    }
+
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
+        Bundle bundle = adapterItemWithPayload.getPayload();
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+        if (bundle.getString("package", "").equals(mPackageName)) {
+            return;
+        }
+        mIsInstantGame = bundle.getBoolean("instant_game", false);
+        mPackageName = bundle.getString("package");
+        mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
+        mTitleView.setText(bundle.getString("title"));
+//        TODO: Should use a generic type to get values b/165320033
+        showIfNecessary(mDetailViews[0], bundle.getString("price"));
+        showIfNecessary(mDetailViews[1], bundle.getString("rating"));
+        showIfNecessary(mDetailViews[2], bundle.getString("category"));
+
+        mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            try {
+//                TODO: Handle caching
+                URL url = new URL(bundle.getString("icon_url"));
+                Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
+                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
+                        Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
+                                mDeviceProfile.allAppsIconSizePx, false));
+                mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    private void showIfNecessary(TextView textView, @Nullable String string) {
+        if (string == null || string.isEmpty()) {
+            textView.setVisibility(GONE);
+        } else {
+            textView.setText(string);
+            textView.setVisibility(VISIBLE);
+        }
+    }
+
+    private void handleSelection() {
+        if (mPackageName == null) return;
+        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
+                "https://play.google.com/store/apps/details?id="
+                        + mPackageName));
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(i);
+    }
+
+    private void launchInstantGame() {
+        if (!mIsInstantGame) return;
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        String referrer = "Pixel_Launcher";
+        String id = mPackageName;
+        String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
+        intent.setPackage("com.android.vending");
+        intent.setData(Uri.parse(deepLinkUrl));
+        intent.putExtra("overlay", true);
+        intent.putExtra("callerId", getContext().getPackageName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java
new file mode 100644
index 0000000..d439ee3
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+
+/**
+ * Header text view that shows a title for a given section in All apps search
+ */
+public class SearchSectionHeaderView extends TextView implements
+        AllAppsSearchBarController.PayloadResultHandler<String> {
+    public SearchSectionHeaderView(Context context) {
+        super(context);
+    }
+
+    public SearchSectionHeaderView(Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
+        super(context, attrs, styleAttr);
+    }
+
+    @Override
+    public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
+        String title = adapterItem.getPayload();
+        if (title == null || !title.isEmpty()) {
+            setText(title);
+            setVisibility(VISIBLE);
+        } else {
+            setVisibility(INVISIBLE);
+        }
+    }
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 9d87788..a64df62 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -51,6 +51,7 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = false;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
 
     private static final String TAG = "WidgetsModel";
     private static final boolean DEBUG = false;