Merge "Scale task view in TaskViewSimulator before translating it" into ub-launcher3-master
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 810f4e3..f92b3e3 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -63,7 +63,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /**
  * Data model for digital wellbeing status of apps.
@@ -222,9 +221,8 @@
             reloadLauncherInNormalMode(context);
             return;
         }
-        runWithMinimalDeviceConfigs((bundle) -> {
-            if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE)
-                    == IN_MINIMAL_DEVICE) {
+        mWorkerHandler.post(() -> {
+            if (isInMinimalDeviceMode()) {
                 reloadLauncherInMinimalMode(context);
             } else {
                 reloadLauncherInNormalMode(context);
@@ -253,31 +251,30 @@
                 .authority(mWellbeingProviderPkg + ".api");
     }
 
-    /**
-     * Fetch most up-to-date minimal device config.
-     */
     @WorkerThread
-    private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
+    private boolean isInMinimalDeviceMode() {
         if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
-            return;
+            return false;
         }
         if (DEBUG || mIsInTest) {
-            Log.d(TAG, "runWithMinimalDeviceConfigs() called");
+            Log.d(TAG, "isInMinimalDeviceMode() called");
         }
         Preconditions.assertNonUiThread();
 
         final Uri contentUri = apiBuilder().build();
-        final Bundle remoteBundle;
         try (ContentProviderClient client = mContext.getContentResolver()
                 .acquireUnstableContentProviderClient(contentUri)) {
-            remoteBundle = client.call(
+            final Bundle remoteBundle = client == null ? null : client.call(
                     METHOD_GET_MINIMAL_DEVICE_CONFIG, null /* args */, null /* extras */);
-            consumer.accept(remoteBundle);
+            return remoteBundle != null
+                    && remoteBundle.getInt(EXTRA_MINIMAL_DEVICE_STATE,
+                    UNKNOWN_MINIMAL_DEVICE_STATE) == IN_MINIMAL_DEVICE;
         } catch (Exception e) {
             Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
             if (mIsInTest) throw new RuntimeException(e);
         }
-        if (DEBUG || mIsInTest) Log.i(TAG, "runWithMinimalDeviceConfigs(): finished");
+        if (DEBUG || mIsInTest) Log.i(TAG, "isInMinimalDeviceMode(): finished");
+        return false;
     }
 
     private boolean updateActions(String... packageNames) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 26ad377..22e6755 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -434,9 +434,13 @@
                 mOnDeferredActivityLaunch);
 
         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
-                () -> mDeviceState.getRotationTouchHelper().
-                        onEndTargetCalculated(mGestureState.getEndTarget(),
-                                mActivityInterface));
+                () -> {
+                    mDeviceState.getRotationTouchHelper()
+                            .onEndTargetCalculated(mGestureState.getEndTarget(),
+                                    mActivityInterface);
+
+                    mRecentsView.onGestureEndTargetCalculated(mGestureState.getEndTarget());
+                });
 
         notifyGestureStartedAsync();
     }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index f5f5259..8f2356c 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
 
@@ -27,6 +28,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.quickstep.FallbackActivityInterface;
+import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
@@ -74,14 +76,14 @@
     }
 
     /**
-     * When the gesture ends and recents view become interactive, we also remove the temporary
+     * When the gesture ends and we're going to recents view, we also remove the temporary
      * invisible tile added for the home task. This also pushes the remaining tiles back
      * to the center.
      */
     @Override
-    public void onGestureAnimationEnd() {
-        super.onGestureAnimationEnd();
-        if (mHomeTaskInfo != null) {
+    public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
+        super.onGestureEndTargetCalculated(endTarget);
+        if (mHomeTaskInfo != null && endTarget == RECENTS) {
             TaskView tv = getTaskView(mHomeTaskInfo.taskId);
             if (tv != null) {
                 PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0362377..b617817 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -118,6 +118,7 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
@@ -1183,7 +1184,14 @@
     }
 
     /**
-     * Called when a gesture from an app has finished.
+     * Called when a gesture from an app has finished, and an end target has been determined.
+     */
+    public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
+
+    }
+
+    /**
+     * Called when a gesture from an app has finished, and the animation to the target has ended.
      */
     public void onGestureAnimationEnd() {
         if (mOrientationState.setGestureActive(false)) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 656d59e..d47eba6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -246,7 +246,7 @@
         setScaleX(taskView.getScaleX());
         setScaleY(taskView.getScaleY());
         boolean canActivityRotate = taskView.getRecentsView()
-            .mOrientationState.canRecentsActivityRotate();
+            .mOrientationState.isRecentsActivityRotationAllowed();
         mOptionLayout.setOrientation(orientationHandler
                 .getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout));
         setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index ccfa3fc..9a053f2 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -3,6 +3,8 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -11,12 +13,12 @@
 import android.app.PendingIntent;
 import android.app.usage.UsageStatsManager;
 import android.content.Intent;
-import android.os.Build;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.views.DigitalWellBeingToast;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -33,10 +35,9 @@
             resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
 
     @Test
+    // b/150303529
+    @TestStabilityRule.Stability(flavors = UNBUNDLED_POSTSUBMIT | PLATFORM_POSTSUBMIT)
     public void testToast() throws Exception {
-        // b/150303529
-        if (Build.MODEL.contains("Cuttlefish")) return;
-
         startAppFast(CALCULATOR_PACKAGE);
 
         final UsageStatsManager usageStatsManager =
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index ecd4e2b..c5863c1 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -201,6 +201,12 @@
     @PortraitLandscape
     public void testOverviewActions() throws Exception {
         if (mLauncher.getNavigationModel() != NavigationModel.TWO_BUTTON) {
+            // Experimenting for b/165029151:
+            final Overview overview = mLauncher.pressHome().switchToOverview();
+            if (overview.hasTasks()) overview.dismissAllTasks();
+            mLauncher.pressHome();
+            //
+
             startTestAppsWithCheck();
             OverviewActions actionsView =
                     mLauncher.pressHome().switchToOverview().getOverviewActions();
diff --git a/res/layout/search_result_thumbnail.xml b/res/layout/search_result_thumbnail.xml
new file mode 100644
index 0000000..0cc5a29
--- /dev/null
+++ b/res/layout/search_result_thumbnail.xml
@@ -0,0 +1,19 @@
+<?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.ThumbnailSearchResultView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="125dp"
+    android:layout_height="125dp"/>
\ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 90566f3..1cf2e89 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
@@ -70,6 +71,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -88,9 +90,10 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
+import android.widget.ImageView;
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
@@ -117,6 +120,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -269,6 +273,8 @@
     private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1;
     private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0;
 
+    private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
+
     private LauncherAppTransitionManager mAppTransitionManager;
     private Configuration mOldConfig;
 
@@ -400,6 +406,7 @@
 
         inflateRootView(R.layout.launcher);
         setupViews();
+        crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
         mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
@@ -476,7 +483,7 @@
                 () -> getStateManager().goToState(NORMAL));
 
         if (Utilities.ATLEAST_R) {
-            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+            getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
         }
 
         mLifecycleRegistry = new LifecycleRegistry(this);
@@ -1364,6 +1371,18 @@
         closeContextMenu();
     }
 
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        int width = mDragLayer.getWidth();
+        int height = mDragLayer.getHeight();
+
+        if (width <= 0 || height <= 0) {
+            return null;
+        }
+
+        return BitmapRenderer.createHardwareBitmap(width, height, mDragLayer::draw);
+    }
+
     public AllAppsTransitionController getAllAppsController() {
         return mAllAppsController;
     }
@@ -2758,4 +2777,40 @@
 
         void onLauncherResume();
     }
+
+    /**
+     * Cross-fades the launcher's updated appearance with its previous appearance.
+     *
+     * This method is used to cross-fade UI updates on activity creation, specifically dark mode
+     * updates.
+     */
+    private void crossFadeWithPreviousAppearance() {
+        Bitmap previousAppearanceBitmap = (Bitmap) getLastNonConfigurationInstance();
+
+        if (previousAppearanceBitmap == null) {
+            return;
+        }
+
+        ImageView crossFadeHelper = new ImageView(this);
+
+        crossFadeHelper.setImageBitmap(previousAppearanceBitmap);
+        crossFadeHelper.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+
+        InsettableFrameLayout.LayoutParams layoutParams = new InsettableFrameLayout.LayoutParams(
+                InsettableFrameLayout.LayoutParams.MATCH_PARENT,
+                InsettableFrameLayout.LayoutParams.MATCH_PARENT);
+
+        layoutParams.ignoreInsets = true;
+
+        crossFadeHelper.setLayoutParams(layoutParams);
+
+        getRootView().addView(crossFadeHelper);
+
+        crossFadeHelper
+                .animate()
+                .setDuration(THEME_CROSS_FADE_ANIMATION_DURATION)
+                .alpha(0f)
+                .withEndAction(() -> getRootView().removeView(crossFadeHelper))
+                .start();
+    }
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index bfe327e..a4181c5 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -105,7 +105,7 @@
         new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context));
 
         mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
-                .registerInstallTracker(mModel, MODEL_EXECUTOR);
+                .registerInstallTracker(mModel);
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
         mNotificationDotsObserver =
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 007e5f5..1bbbb2b 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -25,8 +25,11 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.util.Executors;
 
 /**
  * BroadcastReceiver to handle session commit intent.
@@ -38,6 +41,11 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        Executors.MODEL_EXECUTOR.execute(() -> processIntent(context, intent));
+    }
+
+    @WorkerThread
+    private static void processIntent(Context context, Intent intent) {
         if (!isEnabled(context)) {
             // User has decided to not add icons on homescreen.
             return;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index dea2a8d..43ccb79 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -133,6 +133,10 @@
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
 
+    // An intent extra to indicate the launch source by launcher.
+    public static final String EXTRA_WALLPAPER_LAUNCH_SOURCE =
+            "com.android.wallpaper.LAUNCH_SOURCE";
+
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
 
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index d1340fa..f01f90b 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -96,6 +96,8 @@
 
     public static final int VIEW_TYPE_SEARCH_PEOPLE = 1 << 11;
 
+    public static final int VIEW_TYPE_SEARCH_THUMBNAIL = 1 << 12;
+
     // 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;
@@ -186,6 +188,7 @@
                     || viewType == VIEW_TYPE_SEARCH_SLICE
                     || viewType == VIEW_TYPE_SEARCH_ROW
                     || viewType == VIEW_TYPE_SEARCH_PEOPLE
+                    || viewType == VIEW_TYPE_SEARCH_THUMBNAIL
                     || viewType == VIEW_TYPE_SEARCH_SHORTCUT;
         }
     }
@@ -197,6 +200,7 @@
      */
     public static class AdapterItemWithPayload<T> extends AdapterItem {
         private T mPayload;
+        private String mSearchSessionId;
         private AllAppsSearchPlugin mPlugin;
         private IntConsumer mSelectionHandler;
 
@@ -218,6 +222,14 @@
             mSelectionHandler = runnable;
         }
 
+        public void setSearchSessionId(String searchSessionId) {
+            mSearchSessionId = searchSessionId;
+        }
+
+        public String getSearchSessionId() {
+            return mSearchSessionId;
+        }
+
         public IntConsumer getSelectionHandler() {
             return mSelectionHandler;
         }
@@ -225,6 +237,8 @@
         public T getPayload() {
             return mPayload;
         }
+
+
     }
 
     /**
@@ -307,15 +321,21 @@
 
         @Override
         public int getSpanSize(int position) {
-            if (isIconViewType(mApps.getAdapterItems().get(position).viewType)) {
-                return 1;
+            int viewType = mApps.getAdapterItems().get(position).viewType;
+            if (isIconViewType(viewType)) {
+                return 1 * SPAN_MULTIPLIER;
+            } else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL) {
+                return mAppsPerRow;
             } else {
                 // Section breaks span the full width
-                return mAppsPerRow;
+                return mAppsPerRow * SPAN_MULTIPLIER;
             }
         }
     }
 
+    // multiplier to support adapter item column count that is not mAppsPerRow.
+    private static final int SPAN_MULTIPLIER = 3;
+
     private final BaseDraggingActivity mLauncher;
     private final LayoutInflater mLayoutInflater;
     private final AlphabeticalAppsList mApps;
@@ -352,7 +372,7 @@
 
     public void setAppsPerRow(int appsPerRow) {
         mAppsPerRow = appsPerRow;
-        mGridLayoutMgr.setSpanCount(mAppsPerRow);
+        mGridLayoutMgr.setSpanCount(mAppsPerRow * SPAN_MULTIPLIER);
     }
 
     /**
@@ -444,6 +464,9 @@
             case VIEW_TYPE_SEARCH_PEOPLE:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.search_result_people_item, parent, false));
+            case VIEW_TYPE_SEARCH_THUMBNAIL:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_thumbnail, parent, false));
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -464,24 +487,27 @@
                 //TODO: replace with custom TopHitBubbleTextView with support for both shortcut
                 // and apps
                 if (adapterItem instanceof AdapterItemWithPayload) {
-                    AdapterItemWithPayload withPayload = (AdapterItemWithPayload) adapterItem;
-                    IntConsumer selectionHandler = type -> {
+                    AdapterItemWithPayload item = (AdapterItemWithPayload) adapterItem;
+                    item.setSelectionHandler(type -> {
                         SearchTargetEvent e = new SearchTargetEvent(SearchTarget.ItemType.APP,
-                                type);
+                                type, item.position, item.getSearchSessionId());
                         e.bundle = HeroSearchResultView.getAppBundle(info);
-                        if (withPayload.getPlugin() != null) {
-                            withPayload.getPlugin().notifySearchTargetEvent(e);
+                        if (item.getPlugin() != null) {
+                            item.getPlugin().notifySearchTargetEvent(e);
                         }
-                    };
+                    });
                     icon.setOnClickListener(view -> {
-                        selectionHandler.accept(SearchTargetEvent.SELECT);
+                        item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
                         mOnIconClickListener.onClick(view);
                     });
                     icon.setOnLongClickListener(view -> {
-                        selectionHandler.accept(SearchTargetEvent.LONG_PRESS);
+                        item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
                         return mOnIconLongClickListener.onLongClick(view);
                     });
-                    withPayload.setSelectionHandler(selectionHandler);
+                }
+                else {
+                    icon.setOnClickListener(mOnIconClickListener);
+                    icon.setOnLongClickListener(mOnIconLongClickListener);
                 }
                 break;
             case VIEW_TYPE_EMPTY_SEARCH:
@@ -500,20 +526,22 @@
                 break;
             case VIEW_TYPE_SEARCH_SLICE:
                 SliceView sliceView = (SliceView) holder.itemView;
-                AdapterItemWithPayload<Uri> item =
+                AdapterItemWithPayload<Uri> slicePayload =
                         (AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position);
                 sliceView.setOnSliceActionListener((info1, s) -> {
-                    if (item.getPlugin() != null) {
+                    if (slicePayload.getPlugin() != null) {
                         SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
                                 SearchTarget.ItemType.SETTINGS_SLICE,
-                                SearchTargetEvent.CHILD_SELECT);
+                                SearchTargetEvent.CHILD_SELECT, slicePayload.position,
+                                slicePayload.getSearchSessionId());
                         searchTargetEvent.bundle = new Bundle();
-                        searchTargetEvent.bundle.putParcelable("uri", item.getPayload());
-                        item.getPlugin().notifySearchTargetEvent(searchTargetEvent);
+                        searchTargetEvent.bundle.putParcelable("uri", slicePayload.getPayload());
+                        slicePayload.getPlugin().notifySearchTargetEvent(searchTargetEvent);
                     }
                 });
                 try {
-                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher, item.getPayload());
+                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher,
+                            slicePayload.getPayload());
                     liveData.observe((Launcher) mLauncher, sliceView);
                     sliceView.setTag(liveData);
                 } catch (Exception ignored) {
@@ -525,9 +553,11 @@
             case VIEW_TYPE_SEARCH_ROW:
             case VIEW_TYPE_SEARCH_SHORTCUT:
             case VIEW_TYPE_SEARCH_PEOPLE:
+            case VIEW_TYPE_SEARCH_THUMBNAIL:
+                AdapterItemWithPayload item =
+                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position);
                 PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
-                payloadResultView.applyAdapterInfo(
-                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position));
+                payloadResultView.setup(item);
                 break;
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
@@ -553,7 +583,6 @@
         }
     }
 
-
     @Override
     public boolean onFailedToRecycleView(ViewHolder holder) {
         // Always recycle and we will reset the view when it is bound
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 3320189..d7fa5bc 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -35,6 +35,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -213,6 +215,44 @@
         /**
          * Updates View using Adapter's payload
          */
+
+        default void setup(AdapterItemWithPayload<T> adapterItemWithPayload) {
+            Object[] targetInfo = getTargetInfo();
+            if (targetInfo != null) {
+                targetInfo[0] = adapterItemWithPayload.getSearchSessionId();
+                targetInfo[1] = adapterItemWithPayload.position;
+            }
+            applyAdapterInfo(adapterItemWithPayload);
+        }
+
         void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
+
+        /**
+         * Gets object created by {@link PayloadResultHandler#createTargetInfo()}
+         */
+        Object[] getTargetInfo();
+
+        /**
+         * Creates a wrapper object to hold searchSessionId and item position
+         */
+        default Object[] createTargetInfo() {
+            return new Object[2];
+        }
+
+        /**
+         * Generates a SearchTargetEvent object for a PayloadHandlerView
+         */
+        default SearchTargetEvent getSearchTargetEvent(SearchTarget.ItemType itemType,
+                int eventType) {
+            Object[] targetInfo = getTargetInfo();
+            if (targetInfo == null) return null;
+
+            String searchSessionId = (String) targetInfo[0];
+            int position = (int) targetInfo[1];
+            return new SearchTargetEvent(itemType, eventType,
+                    position, searchSessionId);
+        }
     }
+
+
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2627149..4175280 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -177,8 +177,8 @@
             "SEPARATE_RECENTS_ACTIVITY", false,
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
-    public static final BooleanFlag ENABLE_MINIMAL_DEVICE = new DeviceFlag(
-            "ENABLE_MINIMAL_DEVICE", false,
+    public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(
+            "ENABLE_MINIMAL_DEVICE", true,
             "Allow user to toggle minimal device mode in launcher.");
 
     public static void initialize(Context context) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c841b7b..281598a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -58,6 +58,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -216,6 +217,8 @@
 
     private StatsLogManager mStatsLogManager;
 
+    @Nullable private FolderWindowInsetsAnimationCallback mFolderWindowInsetsAnimationCallback;
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -262,10 +265,10 @@
         mFooterHeight = mFooter.getMeasuredHeight();
 
         if (Utilities.ATLEAST_R) {
-            WindowInsetsAnimation.Callback cb = new FolderWindowInsetsAnimationCallback(
-                    DISPATCH_MODE_STOP, this);
+            mFolderWindowInsetsAnimationCallback =
+                    new FolderWindowInsetsAnimationCallback(DISPATCH_MODE_STOP, this);
 
-            setWindowInsetsAnimationCallback(cb);
+            setWindowInsetsAnimationCallback(mFolderWindowInsetsAnimationCallback);
         }
     }
 
@@ -755,11 +758,17 @@
         a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
+                if (Utilities.ATLEAST_R) {
+                    setWindowInsetsAnimationCallback(null);
+                }
                 mIsAnimatingClosed = true;
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (Utilities.ATLEAST_R && mFolderWindowInsetsAnimationCallback != null) {
+                    setWindowInsetsAnimationCallback(mFolderWindowInsetsAnimationCallback);
+                }
                 closeComplete(true);
                 announceAccessibilityChanges();
                 mIsAnimatingClosed = false;
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 753a6dd..fa25114 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.Utilities.getPrefs;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -31,6 +32,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.SessionCommitReceiver;
@@ -39,10 +41,10 @@
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -65,27 +67,27 @@
 
     private final LauncherApps mLauncherApps;
     private final Context mAppContext;
-    private final IntSet mPromiseIconIds;
 
     private final PackageInstaller mInstaller;
     private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
 
+    private IntSet mPromiseIconIds;
+
     public InstallSessionHelper(Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
         mAppContext = context.getApplicationContext();
         mLauncherApps = context.getSystemService(LauncherApps.class);
+    }
 
+    @WorkerThread
+    private IntSet getPromiseIconIds() {
+        Preconditions.assertWorkerThread();
+        if (mPromiseIconIds != null) {
+            return mPromiseIconIds;
+        }
         mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
-                getPrefs(context).getString(PROMISE_ICON_IDS, "")));
+                getPrefs(mAppContext).getString(PROMISE_ICON_IDS, "")));
 
-        cleanUpPromiseIconIds();
-    }
-
-    public static UserHandle getUserHandle(SessionInfo info) {
-        return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
-    }
-
-    protected void cleanUpPromiseIconIds() {
         IntArray existingIds = new IntArray();
         for (SessionInfo info : getActiveSessions().values()) {
             existingIds.add(info.getSessionId());
@@ -100,6 +102,7 @@
         for (int i = idsToRemove.size() - 1; i >= 0; --i) {
             mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
         }
+        return mPromiseIconIds;
     }
 
     public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
@@ -126,7 +129,7 @@
 
     private void updatePromiseIconPrefs() {
         getPrefs(mAppContext).edit()
-                .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
+                .putString(PROMISE_ICON_IDS, getPromiseIconIds().getArray().toConcatString())
                 .apply();
     }
 
@@ -184,13 +187,15 @@
         return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE;
     }
 
+    @WorkerThread
     public boolean promiseIconAddedForId(int sessionId) {
-        return mPromiseIconIds.contains(sessionId);
+        return getPromiseIconIds().contains(sessionId);
     }
 
+    @WorkerThread
     public void removePromiseIconId(int sessionId) {
-        if (mPromiseIconIds.contains(sessionId)) {
-            mPromiseIconIds.getArray().removeValue(sessionId);
+        if (promiseIconAddedForId(sessionId)) {
+            getPromiseIconIds().getArray().removeValue(sessionId);
             updatePromiseIconPrefs();
         }
     }
@@ -203,6 +208,7 @@
      * - The app is not already installed
      * - A promise icon for the session has not already been created
      */
+    @WorkerThread
     void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
         if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
                 && SessionCommitReceiver.isEnabled(mAppContext)
@@ -210,25 +216,24 @@
                 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
                 && sessionInfo.getAppIcon() != null
                 && !TextUtils.isEmpty(sessionInfo.getAppLabel())
-                && !mPromiseIconIds.contains(sessionInfo.getSessionId())
+                && !promiseIconAddedForId(sessionInfo.getSessionId())
                 && new PackageManagerHelper(mAppContext).getApplicationInfo(
                         sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
             ItemInstallQueue.INSTANCE.get(mAppContext)
                     .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
 
-            mPromiseIconIds.add(sessionInfo.getSessionId());
+            getPromiseIconIds().add(sessionInfo.getSessionId());
             updatePromiseIconPrefs();
         }
     }
 
-    public InstallSessionTracker registerInstallTracker(
-            InstallSessionTracker.Callback callback, LooperExecutor executor) {
+    public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
         InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-            mInstaller.registerSessionCallback(tracker, executor.getHandler());
+            mInstaller.registerSessionCallback(tracker, MODEL_EXECUTOR.getHandler());
         } else {
-            mLauncherApps.registerPackageInstallerSessionCallback(executor, tracker);
+            mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, tracker);
         }
         return tracker;
     }
@@ -240,4 +245,8 @@
             mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
         }
     }
+
+    public static UserHandle getUserHandle(SessionInfo info) {
+        return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
+    }
 }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index eb3ca73..b0b907a 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -24,8 +24,11 @@
 import android.os.UserHandle;
 import android.util.SparseArray;
 
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.util.PackageUserKey;
 
+@WorkerThread
 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
 
     // Lazily initialized
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 2d7d6b0..5ade22b 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -60,6 +60,9 @@
     private void onUsersChanged(Intent intent) {
         enableAndResetCache();
         mUserChangeListeners.forEach(Runnable::run);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.WORK_PROFILE_REMOVED, "profile changed", new Exception());
+        }
     }
 
     /**
@@ -104,9 +107,6 @@
                 mUsers = null;
                 mUserToSerialMap = null;
             }
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work profile removed", new Exception());
-            }
         }
     }
 
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
index a8e1c6b..94b9ca1 100644
--- a/src/com/android/launcher3/views/HeroSearchResultView.java
+++ b/src/com/android/launcher3/views/HeroSearchResultView.java
@@ -16,12 +16,16 @@
 package com.android.launcher3.views;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.graphics.Point;
 import android.os.Bundle;
 import android.util.AttributeSet;
+import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
@@ -31,9 +35,10 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -53,9 +58,10 @@
  * A view representing a high confidence app search result that includes shortcuts
  */
 public class HeroSearchResultView extends LinearLayout implements DragSource,
-        AllAppsSearchBarController.PayloadResultHandler<List<ItemInfoWithIcon>> {
+        PayloadResultHandler<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> {
 
     public static final int MAX_SHORTCUTS_COUNT = 2;
+    private final Object[] mTargetInfo = createTargetInfo();
     BubbleTextView mBubbleTextView;
     View mIconView;
     BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
@@ -102,7 +108,7 @@
                             grid.allAppsIconSizePx));
             bubbleTextView.setOnClickListener(view -> {
                 WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
-                SearchTargetEvent event = new SearchTargetEvent(
+                SearchTargetEvent event = getSearchTargetEvent(
                         SearchTarget.ItemType.APP_HERO,
                         SearchTargetEvent.CHILD_SELECT);
                 event.bundle = getAppBundle(itemInfo);
@@ -119,15 +125,25 @@
      * Apply {@link ItemInfo} for appIcon and shortcut Icons
      */
     @Override
-    public void applyAdapterInfo(AdapterItemWithPayload<List<ItemInfoWithIcon>> adapterItem) {
+    public void applyAdapterInfo(
+            AdapterItemWithPayload<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> adapterItem) {
         mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
         mIconView.setBackground(mBubbleTextView.getIcon());
         mIconView.setTag(adapterItem.appInfo);
-        List<ItemInfoWithIcon> shorcutInfos = adapterItem.getPayload();
+        List<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcutDetails = adapterItem.getPayload();
+        LauncherAppState appState = LauncherAppState.getInstance(getContext());
         for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
-            mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
-            if (i < shorcutInfos.size()) {
-                mDeepShortcutTextViews[i].applyFromItemInfoWithIcon(shorcutInfos.get(i));
+            BubbleTextView shortcutView = mDeepShortcutTextViews[i];
+            mDeepShortcutTextViews[i].setVisibility(shortcutDetails.size() > i ? VISIBLE : GONE);
+            if (i < shortcutDetails.size()) {
+                Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcutDetails.get(i);
+                //apply ItemInfo and prepare view
+                shortcutView.applyFromItemInfoWithIcon(p.second);
+                MODEL_EXECUTOR.execute(() -> {
+                    // load unbadged shortcut in background and update view when icon ready
+                    appState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first);
+                    MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second));
+                });
             }
         }
         mPlugin = adapterItem.getPlugin();
@@ -135,6 +151,11 @@
     }
 
     @Override
+    public Object[] getTargetInfo() {
+        return mTargetInfo;
+    }
+
+    @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
         mBubbleTextView.setVisibility(VISIBLE);
         mBubbleTextView.setIconVisible(true);
@@ -169,7 +190,7 @@
             mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
                     draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
 
-            SearchTargetEvent event = new SearchTargetEvent(
+            SearchTargetEvent event = mContainer.getSearchTargetEvent(
                     SearchTarget.ItemType.APP_HERO, SearchTargetEvent.LONG_PRESS);
             event.bundle = getAppBundle(itemInfo);
             if (mContainer.mPlugin != null) {
@@ -186,7 +207,7 @@
         Launcher launcher = Launcher.getLauncher(getContext());
         launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo);
 
-        SearchTargetEvent event = new SearchTargetEvent(
+        SearchTargetEvent event = getSearchTargetEvent(
                 SearchTarget.ItemType.APP_HERO, eventType);
         event.bundle = getAppBundle(itemInfo);
         if (mPlugin != null) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 3ec20d5..a8caa9e 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.views;
 
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
+import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_LAUNCH_SOURCE;
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
@@ -211,7 +212,8 @@
         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                 .putExtra(EXTRA_WALLPAPER_OFFSET,
-                        launcher.getWorkspace().getWallpaperOffsetForCenterPage());
+                        launcher.getWorkspace().getWallpaperOffsetForCenterPage())
+                .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher");
         if (!Utilities.existsStyleWallpapers(launcher)) {
             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
         } else {
diff --git a/src/com/android/launcher3/views/SearchResultPeopleView.java b/src/com/android/launcher3/views/SearchResultPeopleView.java
index 6e45e88..eacc095 100644
--- a/src/com/android/launcher3/views/SearchResultPeopleView.java
+++ b/src/com/android/launcher3/views/SearchResultPeopleView.java
@@ -67,7 +67,7 @@
     private ImageButton[] mProviderButtons = new ImageButton[3];
     private AllAppsSearchPlugin mPlugin;
     private Uri mContactUri;
-
+    private final Object[] mTargetInfo = createTargetInfo();
 
     public SearchResultPeopleView(Context context) {
         this(context, null, 0);
@@ -129,14 +129,14 @@
                     Bundle provider = providers.get(i);
                     Intent intent = Intent.parseUri(provider.getString("intent_uri_str"),
                             URI_ANDROID_APP_SCHEME | URI_ALLOW_UNSAFE);
-                    setupProviderButton(button, provider, intent);
+                    setupProviderButton(button, provider, intent, adapterItemWithPayload);
                     String pkg = provider.getString("package_name");
                     UI_HELPER_EXECUTOR.post(() -> {
                         try {
                             ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
                                     pkg, 0);
                             Drawable appIcon = applicationInfo.loadIcon(mPackageManager);
-                            MAIN_EXECUTOR.post(()-> button.setImageDrawable(appIcon));
+                            MAIN_EXECUTOR.post(() -> button.setImageDrawable(appIcon));
                         } catch (PackageManager.NameNotFoundException ignored) {
                         }
 
@@ -151,11 +151,17 @@
         adapterItemWithPayload.setSelectionHandler(this::handleSelection);
     }
 
-    private void setupProviderButton(ImageButton button, Bundle provider, Intent intent) {
+    @Override
+    public Object[] getTargetInfo() {
+        return mTargetInfo;
+    }
+
+    private void setupProviderButton(ImageButton button, Bundle provider, Intent intent,
+            AllAppsGridAdapter.AdapterItem adapterItem) {
         Launcher launcher = Launcher.getLauncher(getContext());
         button.setOnClickListener(b -> {
             launcher.startActivitySafely(b, intent, null);
-            SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+            SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
                     SearchTarget.ItemType.PEOPLE,
                     SearchTargetEvent.CHILD_SELECT);
             searchTargetEvent.bundle = new Bundle();
@@ -173,8 +179,8 @@
             Launcher launcher = Launcher.getLauncher(getContext());
             launcher.startActivitySafely(this, new Intent(Intent.ACTION_VIEW, mContactUri).setFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK), null);
-            SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
-                    SearchTarget.ItemType.PEOPLE, eventType);
+            SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.PEOPLE,
+                    eventType);
             searchTargetEvent.bundle = new Bundle();
             searchTargetEvent.bundle.putParcelable("contact_uri", mContactUri);
             if (mPlugin != null) {
diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java
index 8624609..ff3ecc8 100644
--- a/src/com/android/launcher3/views/SearchResultPlayItem.java
+++ b/src/com/android/launcher3/views/SearchResultPlayItem.java
@@ -58,6 +58,8 @@
     private String mPackageName;
     private boolean mIsInstantGame;
     private AllAppsSearchPlugin mPlugin;
+    private final Object[] mTargetInfo = createTargetInfo();
+
 
     public SearchResultPlayItem(Context context) {
         this(context, null, 0);
@@ -125,6 +127,11 @@
         });
     }
 
+    @Override
+    public Object[] getTargetInfo() {
+        return mTargetInfo;
+    }
+
     private void showIfNecessary(TextView textView, @Nullable String string) {
         if (string == null || string.isEmpty()) {
             textView.setVisibility(GONE);
@@ -160,7 +167,7 @@
     }
 
     private void logSearchEvent(int eventType) {
-        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
                 SearchTarget.ItemType.PLAY_RESULTS, eventType);
         searchTargetEvent.bundle = new Bundle();
         searchTargetEvent.bundle.putString("package_name", mPackageName);
diff --git a/src/com/android/launcher3/views/SearchResultShortcut.java b/src/com/android/launcher3/views/SearchResultShortcut.java
index 307cf34..a409f08 100644
--- a/src/com/android/launcher3/views/SearchResultShortcut.java
+++ b/src/com/android/launcher3/views/SearchResultShortcut.java
@@ -50,6 +50,8 @@
     private View mIconView;
     private ShortcutInfo mShortcutInfo;
     private AllAppsSearchPlugin mPlugin;
+    private final Object[] mTargetInfo = createTargetInfo();
+
 
     public SearchResultShortcut(@NonNull Context context) {
         super(context);
@@ -96,12 +98,17 @@
         adapterItemWithPayload.setSelectionHandler(this::handleSelection);
     }
 
+    @Override
+    public Object[] getTargetInfo() {
+        return mTargetInfo;
+    }
+
     private void handleSelection(int eventType) {
         WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) mBubbleTextView.getTag();
         ItemClickHandler.onClickAppShortcut(this, itemInfo, Launcher.getLauncher(getContext()));
 
-        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
-                SearchTarget.ItemType.SHORTCUT, eventType);
+        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.SHORTCUT,
+                eventType);
         searchTargetEvent.shortcut = mShortcutInfo;
         if (mPlugin != null) {
             mPlugin.notifySearchTargetEvent(searchTargetEvent);
diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java
index d439ee3..0fe0a43 100644
--- a/src/com/android/launcher3/views/SearchSectionHeaderView.java
+++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java
@@ -52,4 +52,9 @@
             setVisibility(INVISIBLE);
         }
     }
+
+    @Override
+    public Object[] getTargetInfo() {
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java
index 93bcee2..a1a0172 100644
--- a/src/com/android/launcher3/views/SearchSettingsRowView.java
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -48,6 +48,8 @@
     private TextView mBreadcrumbsView;
     private Intent mIntent;
     private AllAppsSearchPlugin mPlugin;
+    private final Object[] mTargetInfo = createTargetInfo();
+
 
     public SearchSettingsRowView(@NonNull Context context) {
         super(context);
@@ -87,6 +89,11 @@
         adapterItemWithPayload.setSelectionHandler(this::handleSelection);
     }
 
+    @Override
+    public Object[] getTargetInfo() {
+        return mTargetInfo;
+    }
+
     private void showIfAvailable(TextView view, @Nullable String string) {
         if (TextUtils.isEmpty(string)) {
             view.setVisibility(GONE);
@@ -108,7 +115,7 @@
         Launcher launcher = Launcher.getLauncher(getContext());
         launcher.startActivityForResult(mIntent, 0);
 
-        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
                 SearchTarget.ItemType.SETTINGS_ROW, eventType);
         searchTargetEvent.bundle = new Bundle();
         searchTargetEvent.bundle.putParcelable("intent", mIntent);
diff --git a/src/com/android/launcher3/views/ThumbnailSearchResultView.java b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
new file mode 100644
index 0000000..1d58a06
--- /dev/null
+++ b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
@@ -0,0 +1,89 @@
+/*
+ * 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.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.util.Themes;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * A view representing a high confidence app search result that includes shortcuts
+ */
+public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppCompatImageView
+        implements AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+
+    private final Object[] mTargetInfo = createTargetInfo();
+    Intent mIntent;
+    AllAppsSearchPlugin mPlugin;
+    int mPosition;
+
+    public ThumbnailSearchResultView(Context context) {
+        super(context);
+    }
+
+    public ThumbnailSearchResultView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ThumbnailSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    private void handleSelection(int eventType) {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        launcher.startActivitySafely(this, mIntent, null);
+
+        SearchTargetEvent event = getSearchTargetEvent(
+                SearchTarget.ItemType.SCREENSHOT, eventType);
+        if (mPlugin != null) {
+            mPlugin.notifySearchTargetEvent(event);
+        }
+    }
+
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItem) {
+        mPosition = adapterItem.position;
+        RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(null,
+                (Bitmap) adapterItem.getPayload().getParcelable("bitmap"));
+        drawable.setCornerRadius(Themes.getDialogCornerRadius(getContext()));
+        setImageDrawable(drawable);
+        mIntent = new Intent(Intent.ACTION_VIEW)
+                .setData(Uri.parse(adapterItem.getPayload().getString("uri")))
+                .setType("image/*")
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mPlugin = adapterItem.getPlugin();
+        adapterItem.setSelectionHandler(this::handleSelection);
+    }
+
+    @Override
+    public Object[] getTargetInfo() {
+        return mTargetInfo;
+    }
+}
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
index 437cf3c..aa3ab8f 100644
--- a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.plugins;
 
+import android.app.Activity;
+import android.view.View;
+
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
@@ -29,22 +32,26 @@
 @ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION)
 public interface AllAppsSearchPlugin extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS";
-    int VERSION = 5;
+    int VERSION = 7;
 
+    void setup(Activity activity, View view);
 
     /**
-     * Send signal when user enters all apps.
+     * Send launcher state related signals.
      */
-    void startAllAppsSession();
+    void onStateTransitionStart(int fromState, int toState);
+    void onStateTransitionComplete(int state);
 
     /**
-     * Send signal when user starts typing.
+     * Send launcher window focus and visibility changed signals.
+     */
+    void onWindowFocusChanged(boolean hasFocus);
+    void onWindowVisibilityChanged(int visibility);
+
+    /**
+     * Send signal when user starts typing, perform search, when search ends
      */
     void startedSearchSession();
-
-    /**
-     * Send over the query and get the search results.
-     */
     void performSearch(String query, Consumer<List<SearchTarget>> results);
 
     /**
@@ -53,7 +60,8 @@
     void notifySearchTargetEvent(SearchTargetEvent event);
 
     /**
-     * Send signal when user exits all apps.
+     * Launcher activity lifecycle callbacks
      */
-    void endAllAppsSession();
+    void onResume(int state);
+    void onStop(int state);
 }
\ No newline at end of file
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
index c6b8300..fb8dd72 100644
--- a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
@@ -26,14 +26,52 @@
 public class SearchTarget implements Comparable<SearchTarget> {
 
     public enum ViewType {
+
+        /**
+         * Consists of N number of icons. (N: launcher column count)
+         */
         TOP_HIT(0),
+
+        /**
+         * Consists of 1 icon and two subsidiary icons.
+         */
         HERO(1),
+
+        /**
+         * Main/sub/breadcrumb texts are rendered.
+         */
         DETAIL(2),
+
+        /**
+         * Consists of an icon, three detail strings.
+         */
         ROW(3),
+
+        /**
+         * Consists of an icon, three detail strings and a button.
+         */
         ROW_WITH_BUTTON(4),
+
+        /**
+         * Consists of a single slice view
+         */
         SLICE(5),
+
+        /**
+         * Similar to hero section.
+         */
         SHORTCUT(6),
-        PEOPLE(7);
+
+        /**
+         * Person icon and handling app icons are rendered.
+         */
+        PEOPLE(7),
+
+        /**
+         * N number of 1x1 ratio thumbnail is rendered.
+         * (current N = 3)
+         */
+        THUMBNAIL(8);
 
         private final int mId;
         ViewType(int id) {
@@ -52,9 +90,12 @@
         APP(3, "", ViewType.TOP_HIT),
         APP_HERO(4, "", ViewType.HERO),
         SHORTCUT(5, "Shortcuts", ViewType.SHORTCUT),
-        PEOPLE(6, "People", ViewType.PEOPLE);
+        PEOPLE(6, "People", ViewType.PEOPLE),
+        SCREENSHOT(7, "Screenshots", ViewType.THUMBNAIL);
 
         private final int mId;
+
+        /** Used to render section title. */
         private final String mTitle;
         private final ViewType mViewType;
 
@@ -81,6 +122,7 @@
     public List<ShortcutInfo> shortcuts;
     public Bundle bundle;
     public float score;
+    public String mSessionId;
 
     /**
      * Constructor to create the search target. Bundle is currently temporary to hold
@@ -89,11 +131,12 @@
      *
      */
     public SearchTarget(ItemType itemType, List<ShortcutInfo> shortcuts,
-            Bundle bundle, float score) {
+            Bundle bundle, float score, String sessionId) {
         this.type = itemType;
         this.shortcuts = shortcuts;
         this.bundle = bundle;
         this.score = score;
+        this.mSessionId = sessionId;
     }
 
     @Override
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
index ac4bc33..00a78de 100644
--- a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
@@ -31,10 +31,14 @@
     public ShortcutInfo shortcut;
     public int eventType;
     public Bundle bundle;
-    public float score;
+    public int index;
+    public String sessionIdentifier;
 
-    public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType) {
+    public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType, int index,
+            String sessionId) {
         this.type = itemType;
         this.eventType = eventType;
+        this.index = index;
+        this.sessionIdentifier = sessionId;
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index f5f93c4..488e763 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -136,7 +136,8 @@
         });
 
         executeOnLauncher(launcher -> Log.d(TestProtocol.WORK_PROFILE_REMOVED,
-                "Work profile status: " + launcher.getAppsView().isPersonalTabVisible()));
+                "work profile status (" + mProfileUserId + ") :"
+                        + launcher.getAppsView().isWorkTabVisible()));
 
         // verify work edu is seen next
         waitForLauncherCondition("Launcher did not show the next edu screen", l ->