Merge "Log Launcher transitions to WW" into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index e103f66..7334d80 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -30,10 +30,8 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.content.ComponentName;
-import android.content.pm.ShortcutInfo;
 import android.os.Process;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
 import android.view.ViewGroup;
@@ -67,7 +65,6 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -382,24 +379,16 @@
             mRestoreHelper.restoreBackup();
         }
         StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
-        ArrayList<ComponentKey> componentKeysToBeCached = new ArrayList<>();
-        SparseArray<ShortcutRequest> requests = new SparseArray<>();
+        ArrayList<ComponentKey> componentKeys = new ArrayList<>();
         for (AppTarget appTarget : appTargets) {
             ComponentKey key;
-            ShortcutRequest request = requests.get(appTarget.getUser().getIdentifier());
-            if (request == null) {
-                request = new ShortcutRequest(mLauncher, appTarget.getUser());
-                requests.put(appTarget.getUser().getIdentifier(), request);
-            }
-            List<ShortcutInfo> shortcutInfos = request.forPackage(appTarget.getPackageName(),
-                    appTarget.getClassName()).query(ShortcutRequest.ALL);
-            if (!shortcutInfos.isEmpty()) {
-                key = ShortcutKey.fromInfo(shortcutInfos.get(0));
+            if (appTarget.getShortcutInfo() != null) {
+                key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
             } else {
                 key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
                         appTarget.getClassName()), appTarget.getUser());
-                componentKeysToBeCached.add(key);
             }
+            componentKeys.add(key);
             predictionLog.append(key.toString());
             predictionLog.append(",rank:");
             predictionLog.append(appTarget.getRank());
@@ -412,7 +401,7 @@
         }
         updateDependencies();
         fillGapsWithPrediction();
-        cachePredictionComponentKeysIfNecessary(componentKeysToBeCached);
+        cachePredictionComponentKeysIfNecessary(componentKeys);
     }
 
     private void cachePredictionComponentKeysIfNecessary(ArrayList<ComponentKey> componentKeys) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 51d53fd..597c17b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.graphics.IconPalette;
@@ -91,9 +90,8 @@
     public void onDraw(Canvas canvas) {
         int count = canvas.save();
         if (!mIsPinned) {
-            ItemInfo info = (ItemInfo) getTag();
-            boolean isBadged = info != null && (!Process.myUserHandle().equals(info.user)
-                    || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
+            boolean isBadged = getTag() instanceof WorkspaceItemInfo
+                    && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
             drawEffect(canvas, isBadged);
             canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
             canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 256cd4f..413a813 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -282,8 +282,7 @@
         if (mActivity == activity) {
             return true;
         }
-        mTaskViewSimulator.setLayoutRotation(mDeviceState.getCurrentActiveRotation(),
-                mDeviceState.getDisplayRotation());
+
         if (mActivity != null) {
             // The launcher may have been recreated as a result of device rotation.
             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
@@ -336,6 +335,7 @@
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return;
         }
+        mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration());
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index ed07062..01936e4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -66,7 +66,7 @@
             }
         }
         RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState();
-        boolean canLauncherRotate = orientedState.canLauncherRotate();
+        boolean canLauncherRotate = orientedState.canRecentsActivityRotate();
         boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
 
         // Add overview actions to the menu when in in-place rotate landscape mode.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 37314ea..bfdddbd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -471,7 +471,6 @@
                 Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
             }
             mDeviceState.setOrientationTransformIfNeeded(event);
-            GestureState newGestureState;
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
                 if (TestProtocol.sDebugTracing) {
@@ -481,35 +480,30 @@
                 // Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
                 // onConsumerInactive and wipe the previous gesture state
                 GestureState prevGestureState = new GestureState(mGestureState);
-                newGestureState = createGestureState();
+                mGestureState = createGestureState(mGestureState);
                 mConsumer.onConsumerAboutToBeSwitched();
-                mConsumer = newConsumer(prevGestureState, newGestureState, event);
+                mConsumer = newConsumer(prevGestureState, mGestureState, event);
 
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName());
                 mUncheckedConsumer = mConsumer;
             } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()) {
-                newGestureState = createGestureState();
-                ActivityManager.RunningTaskInfo runningTask = newGestureState.getRunningTask();
+                mGestureState = createGestureState(mGestureState);
+                ActivityManager.RunningTaskInfo runningTask = mGestureState.getRunningTask();
                 if (mDeviceState.canTriggerAssistantAction(event, runningTask)) {
                     // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
                     // should not interrupt it. QuickSwitch assumes that interruption can only
                     // happen if the next gesture is also quick switch.
                     mUncheckedConsumer = new AssistantInputConsumer(
                             this,
-                            newGestureState,
+                            mGestureState,
                             InputConsumer.NO_OP, mInputMonitorCompat,
                             mOverviewComponentObserver.assistantGestureIsConstrained());
                 } else {
-                    newGestureState = DEFAULT_STATE;
                     mUncheckedConsumer = InputConsumer.NO_OP;
                 }
             } else {
-                newGestureState = DEFAULT_STATE;
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
-
-            // Save the current gesture state
-            mGestureState = newGestureState;
         } else {
             // Other events
             if (mUncheckedConsumer != InputConsumer.NO_OP) {
@@ -519,7 +513,17 @@
         }
 
         if (mUncheckedConsumer != InputConsumer.NO_OP) {
-            ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+            switch (event.getActionMasked()) {
+                case ACTION_DOWN:
+                case ACTION_UP:
+                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent("
+                            + (int) event.getRawX() + ", " + (int) event.getRawY() + ")",
+                            event.getActionMasked());
+                    break;
+                default:
+                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+                    break;
+            }
         }
 
         boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL)
@@ -533,14 +537,14 @@
         TraceHelper.INSTANCE.endFlagsOverride(traceToken);
     }
 
-    private GestureState createGestureState() {
+    private GestureState createGestureState(GestureState previousGestureState) {
         GestureState gestureState = new GestureState(mOverviewComponentObserver,
                 ActiveGestureLog.INSTANCE.generateAndSetLogId());
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
-            gestureState.updateRunningTask(mGestureState.getRunningTask());
-            gestureState.updateLastStartedTaskId(mGestureState.getLastStartedTaskId());
+            gestureState.updateRunningTask(previousGestureState.getRunningTask());
+            gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
             gestureState.updatePreviouslyAppearedTaskIds(
-                    mGestureState.getPreviouslyAppearedTaskIds());
+                    previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
             gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
                     () -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
@@ -743,7 +747,7 @@
 
     private void reset() {
         mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
-        mGestureState = new GestureState();
+        mGestureState = DEFAULT_STATE;
     }
 
     private void preloadOverview(boolean fromInit) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index a9f138e..4e967cf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -23,6 +23,8 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import static com.android.launcher3.PagedView.ACTION_MOVE_ALLOW_EASY_FLING;
+import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
@@ -38,6 +40,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
@@ -76,7 +79,8 @@
     private static final String UP_EVT = "OtherActivityInputConsumer.UP";
 
     // TODO: Move to quickstep contract
-    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
 
     private final RecentsAnimationDeviceState mDeviceState;
     private final NavBarPosition mNavBarPosition;
@@ -150,10 +154,12 @@
 
         boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
-        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
 
-        float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
-        mSquaredTouchSlop = slop * slop;
+        float slopMultiplier = mDeviceState.isFullyGesturalNavMode()
+                ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
+                : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
+        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+        mSquaredTouchSlop = slopMultiplier * mTouchSlop * mTouchSlop;
 
         mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
@@ -187,6 +193,10 @@
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler
                     .getRecentsViewDispatcher(mNavBarPosition.getRotation()));
+            int action = ev.getAction();
+            ev.setAction(ACTION_MOVE_ALLOW_EASY_FLING);
+            mRecentsViewDispatcher.dispatchEvent(ev);
+            ev.setAction(action);
         }
         int edgeFlags = ev.getEdgeFlags();
         ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -317,6 +327,13 @@
             }
             case ACTION_CANCEL:
             case ACTION_UP: {
+                if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) {
+                    float displacementX = mLastPos.x - mDownPos.x;
+                    float displacementY = mLastPos.y - mDownPos.y;
+                    Log.d("Quickswitch", "mPassedWindowMoveSlop=false"
+                            + " disp=" + squaredHypot(displacementX, displacementY)
+                            + " slop=" + mSquaredTouchSlop);
+                }
                 finishTouchTracking(ev);
                 break;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 32da52e..46013d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -22,6 +22,7 @@
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -125,6 +126,14 @@
     }
 
     /**
+     * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration)
+     */
+    public void setRecentsConfiguration(Configuration configuration) {
+        mOrientationState.setActivityConfiguration(configuration);
+        mLayoutValid = false;
+    }
+
+    /**
      * @see com.android.quickstep.views.RecentsView#FULLSCREEN_PROGRESS
      */
     public float getFullScreenScale() {
@@ -205,7 +214,8 @@
     public void applyWindowToHomeRotation(Matrix matrix) {
         mMatrix.postTranslate(mDp.windowX, mDp.windowY);
         postDisplayRotation(deltaRotation(
-                mOrientationState.getLauncherRotation(), mOrientationState.getDisplayRotation()),
+                mOrientationState.getRecentsActivityRotation(),
+                mOrientationState.getDisplayRotation()),
                 mDp.widthPx, mDp.heightPx, matrix);
         matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y);
     }
@@ -226,7 +236,7 @@
             mPositionHelper.updateThumbnailMatrix(
                     mThumbnailPosition, mThumbnailData,
                     mTaskRect.width(), mTaskRect.height(),
-                    mDp, mOrientationState.getLauncherRotation());
+                    mDp, mOrientationState.getRecentsActivityRotation());
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
 
             PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index a19026b..846b944 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -31,6 +31,7 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -42,6 +43,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -52,6 +54,7 @@
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * {@link RecentsView} used in Launcher activity
@@ -167,14 +170,21 @@
     }
 
     @Override
-    protected void onTaskLaunched(boolean success) {
+    protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             mActivity.getStateManager().goToState(NORMAL, false /* animate */);
         } else {
             LauncherState state = mActivity.getStateManager().getState();
             mActivity.getAllAppsController().setState(state);
         }
-        super.onTaskLaunched(success);
+        super.onTaskLaunchAnimationEnd(success);
+    }
+
+    @Override
+    public void onTaskLaunched(Task task) {
+        UserHandle user =  UserHandle.of(task.key.userId);
+        AppLaunchTracker.INSTANCE.get(getContext()).onStartApp(task.getTopComponent(), user,
+                AppLaunchTracker.CONTAINER_OVERVIEW);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index cb22570..68b8975 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -59,6 +59,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -393,6 +394,7 @@
         mActivity = BaseActivity.fromContext(context);
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
+        mOrientationState.setActivityConfiguration(context.getResources().getConfiguration());
 
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
@@ -1063,7 +1065,7 @@
     }
 
     private void animateRecentsRotationInPlace(int newRotation) {
-        if (mOrientationState.canLauncherRotate()) {
+        if (mOrientationState.canRecentsActivityRotate()) {
             // Let system take care of the rotation
             return;
         }
@@ -1646,29 +1648,41 @@
         }
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mOrientationState.setActivityConfiguration(newConfig)) {
+            updateOrientationHandler();
+        }
+    }
+
     public void setLayoutRotation(int touchRotation, int displayRotation) {
         if (mOrientationState.update(touchRotation, displayRotation)) {
-            mOrientationHandler = mOrientationState.getOrientationHandler();
-            mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
-            setLayoutDirection(mIsRtl
-                    ? View.LAYOUT_DIRECTION_RTL
-                    : View.LAYOUT_DIRECTION_LTR);
-            mClearAllButton.setLayoutDirection(mIsRtl
-                    ? View.LAYOUT_DIRECTION_LTR
-                    : View.LAYOUT_DIRECTION_RTL);
-            mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
-            mActivity.getDragLayer().recreateControllers();
-            boolean isInLandscape = mOrientationState.getTouchRotation() != 0
-                    || mOrientationState.getLauncherRotation() != ROTATION_0;
-            mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
-                    !mOrientationState.canLauncherRotate() && isInLandscape);
-            resetPaddingFromTaskSize();
-            requestLayout();
-            // Reapply the current page to update page scrolls.
-            setCurrentPage(mCurrentPage);
+            updateOrientationHandler();
         }
     }
 
+    private void updateOrientationHandler() {
+        mOrientationHandler = mOrientationState.getOrientationHandler();
+        mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+        setLayoutDirection(mIsRtl
+                ? View.LAYOUT_DIRECTION_RTL
+                : View.LAYOUT_DIRECTION_LTR);
+        mClearAllButton.setLayoutDirection(mIsRtl
+                ? View.LAYOUT_DIRECTION_LTR
+                : View.LAYOUT_DIRECTION_RTL);
+        mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
+        mActivity.getDragLayer().recreateControllers();
+        boolean isInLandscape = mOrientationState.getTouchRotation() != 0
+                || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
+        mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
+                !mOrientationState.canRecentsActivityRotate() && isInLandscape);
+        resetPaddingFromTaskSize();
+        requestLayout();
+        // Reapply the current page to update page scrolls.
+        setCurrentPage(mCurrentPage);
+    }
+
     public RecentsOrientedState getPagedViewOrientedState() {
         return mOrientationState;
     }
@@ -1952,7 +1966,7 @@
         mPendingAnimation.addEndListener((endState) -> {
             if (endState.isSuccess) {
                 Consumer<Boolean> onLaunchResult = (result) -> {
-                    onTaskLaunched(result);
+                    onTaskLaunchAnimationEnd(result);
                     if (!result) {
                         tv.notifyTaskLaunchFailed(TAG);
                     }
@@ -1967,7 +1981,7 @@
                             .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
                 }
             } else {
-                onTaskLaunched(false);
+                onTaskLaunchAnimationEnd(false);
             }
             mPendingAnimation = null;
         });
@@ -1979,12 +1993,17 @@
 
     public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
 
-    protected void onTaskLaunched(boolean success) {
+    protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             resetTaskVisuals();
         }
     }
 
+    /**
+     * Called when task activity is launched
+     */
+    public void onTaskLaunched(Task task){ }
+
     @Override
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
@@ -2234,7 +2253,7 @@
             getCurrentPageTaskView().setModalness(modalness);
         }
         // Only show actions view when it's modal for in-place landscape mode.
-        boolean inPlaceLandscape = !mOrientationState.canLauncherRotate()
+        boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate()
                 && mOrientationState.getTouchRotation() != ROTATION_0;
         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 82fabac..222f6e6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -30,7 +30,8 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
+        .LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
 
 import android.animation.Animator;
@@ -384,6 +385,7 @@
                             }
                         }, resultCallbackHandler);
             }
+            getRecentsView().onTaskLaunched(mTask);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index d2e0339..47ce320 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -83,6 +83,7 @@
         super.onCreate(savedInstanceState);
 
         SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
+        addMultiWindowModeChangedListener(mDepthController);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 2b08dcd..fe8f0c6 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -20,11 +20,15 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.os.IBinder;
 import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
@@ -41,7 +45,8 @@
 /**
  * Controls blur and wallpaper zoom, for the Launcher surface only.
  */
-public class DepthController implements StateHandler<LauncherState> {
+public class DepthController implements StateHandler<LauncherState>,
+        BaseActivity.MultiWindowModeChangedListener {
 
     public static final FloatProperty<DepthController> DEPTH =
             new FloatProperty<DepthController>("depth") {
@@ -104,6 +109,9 @@
      */
     private float mDepth;
 
+    // Workaround for animating the depth when multiwindow mode changes.
+    private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
+
     private View.OnAttachStateChangeListener mOnAttachListener;
 
     public DepthController(Launcher l) {
@@ -171,7 +179,7 @@
 
     @Override
     public void setState(LauncherState toState) {
-        if (mSurface == null) {
+        if (mSurface == null || mIgnoreStateChangesDuringMultiWindowAnimation) {
             return;
         }
 
@@ -186,7 +194,8 @@
             PendingAnimation animation) {
         if (mSurface == null
                 || config.onlyPlayAtomicComponent()
-                || config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)) {
+                || config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)
+                || mIgnoreStateChangesDuringMultiWindowAnimation) {
             return;
         }
 
@@ -231,4 +240,21 @@
                     .apply();
         }
     }
+
+    @Override
+    public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        mIgnoreStateChangesDuringMultiWindowAnimation = true;
+
+        ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(this, DEPTH,
+                mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode))
+                .setDuration(300);
+        mwAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIgnoreStateChangesDuringMultiWindowAnimation = false;
+            }
+        });
+        mwAnimation.setAutoCancel(true);
+        mwAnimation.start();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index e93b3dd..2034801 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -46,6 +46,7 @@
 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.util.Executors;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.LogConfig;
 import com.android.systemui.shared.system.SysUiStatsLog;
@@ -123,11 +124,14 @@
                 writeSnapshot(atomInfo, mInstanceId);
             }
             for (FolderInfo fInfo : folders) {
-                ArrayList<WorkspaceItemInfo> folderContents = (ArrayList) fInfo.contents.clone();
-                for (ItemInfo info : folderContents) {
-                    LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
-                    writeSnapshot(atomInfo, mInstanceId);
-                }
+                try {
+                    ArrayList<WorkspaceItemInfo> folderContents =
+                            (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get();
+                    for (ItemInfo info : folderContents) {
+                        LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
+                        writeSnapshot(atomInfo, mInstanceId);
+                    }
+                } catch (Exception e) { }
             }
             for (ItemInfo info : appWidgets) {
                 LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index f302fdd..e998e9a 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
 
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.content.ClipData;
@@ -55,6 +56,9 @@
 public class ImageActionUtils {
 
     private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".overview.fileprovider";
+    private static final long FILE_LIFE = 1000L /*ms*/ * 60L /*s*/ * 60L /*m*/ * 24L /*h*/;
+    private static final String SUB_FOLDER = "Overview";
+    private static final String BASE_NAME = "overview_image_";
 
     /**
      * Saves screenshot to location determine by SystemUiProxy
@@ -110,10 +114,13 @@
      */
     @WorkerThread
     public static Uri getImageUri(Bitmap bitmap, Rect crop, Context context, String tag) {
+        clearOldCacheFiles(context);
         Bitmap croppedBitmap = cropBitmap(bitmap, crop);
         int cropHash = crop == null ? 0 : crop.hashCode();
-        String baseName = "image_" + bitmap.hashCode() + "_" + cropHash + ".png";
-        File file = new File(context.getCacheDir(), baseName);
+        String baseName = BASE_NAME + bitmap.hashCode() + "_" + cropHash + ".png";
+        File parent = new File(context.getCacheDir(), SUB_FOLDER);
+        parent.mkdir();
+        File file = new File(parent, baseName);
 
         try (FileOutputStream fos = new FileOutputStream(file)) {
             croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
@@ -175,4 +182,19 @@
                 .setClipData(clipdata);
         return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
     }
+
+    private static void clearOldCacheFiles(Context context) {
+        THREAD_POOL_EXECUTOR.execute(() -> {
+            File parent = new File(context.getCacheDir(), SUB_FOLDER);
+            File[] files = parent.listFiles((File f, String s) -> s.startsWith(BASE_NAME));
+            if (files != null) {
+                for (File file: files) {
+                    if (file.lastModified() + FILE_LIFE < System.currentTimeMillis()) {
+                        file.delete();
+                    }
+                }
+            }
+        });
+
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index cefab1b..ae19d73 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -58,21 +58,6 @@
     }
 
     /**
-     * Gets the scale that should be applied to the TaskView so that it matches the target
-     * TODO: Remove this method
-     */
-    public static float getTaskScale(RecentsOrientedState orientedState,
-            float srcWidth, float srcHeight, float targetWidth, float targetHeight) {
-        if (orientedState == null
-                || orientedState.isHomeRotationAllowed()
-                || orientedState.isDisplayPhoneNatural()) {
-            return srcWidth / targetWidth;
-        } else {
-            return srcHeight / targetHeight;
-        }
-    }
-
-    /**
      * Recursively sets view and all children enabled/disabled.
      * @param view Top most parent view to change.
      * @param enabled True = enable, False = disable.
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 90ee18f..b9953cb 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -33,6 +33,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Matrix;
@@ -47,6 +48,7 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -56,6 +58,7 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.systemui.shared.system.ConfigurationCompat;
 
 import java.lang.annotation.Retention;
 import java.util.function.IntConsumer;
@@ -87,7 +90,7 @@
 
     private @SurfaceRotation int mTouchRotation = ROTATION_0;
     private @SurfaceRotation int mDisplayRotation = ROTATION_0;
-    private @SurfaceRotation int mLauncherRotation = ROTATION_0;
+    private @SurfaceRotation int mRecentsActivityRotation = ROTATION_0;
 
     // Launcher activity supports multiple orientation, but fallback activity does not
     private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
@@ -130,6 +133,8 @@
     private int mFlags;
     private int mPreviousRotation = ROTATION_0;
 
+    @Nullable private Configuration mActivityConfiguration;
+
     /**
      * @param rotationChangeListener Callback for receiving rotation events when rotation watcher
      *                              is enabled
@@ -165,6 +170,15 @@
     }
 
     /**
+     * Sets the configuration for the recents activity, which could affect the activity's rotation
+     * @see #update(int, int)
+     */
+    public boolean setActivityConfiguration(Configuration activityConfiguration) {
+        mActivityConfiguration = activityConfiguration;
+        return update(mTouchRotation, mDisplayRotation);
+    }
+
+    /**
      * Sets if the host is in multi-window mode
      */
     public void setMultiWindowMode(boolean isMultiWindow) {
@@ -188,23 +202,19 @@
      */
     public boolean update(
             @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
-        if (!isMultipleOrientationSupportedByDevice()) {
-            return false;
-        }
-
-        int launcherRotation = inferLauncherRotation(displayRotation);
+        int recentsActivityRotation = inferRecentsActivityRotation(displayRotation);
         if (mDisplayRotation == displayRotation
                 && mTouchRotation == touchRotation
-                && mLauncherRotation == launcherRotation) {
+                && mRecentsActivityRotation == recentsActivityRotation) {
             return false;
         }
 
-        mLauncherRotation = launcherRotation;
+        mRecentsActivityRotation = recentsActivityRotation;
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         mPreviousRotation = touchRotation;
 
-        if (mLauncherRotation == mTouchRotation || canLauncherRotate()) {
+        if (mRecentsActivityRotation == mTouchRotation || canRecentsActivityRotate()) {
             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
             if (DEBUG) {
                 Log.d(TAG, "current RecentsOrientedState: " + this);
@@ -226,9 +236,11 @@
     }
 
     @SurfaceRotation
-    private int inferLauncherRotation(@SurfaceRotation int displayRotation) {
-        if (!isMultipleOrientationSupportedByDevice() || isHomeRotationAllowed()) {
-            return displayRotation;
+    private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
+        if (isRecentsActivityRotationAllowed()) {
+            return mActivityConfiguration == null
+                    ? displayRotation
+                    : ConfigurationCompat.getWindowConfigurationRotation(mActivityConfiguration);
         } else {
             return ROTATION_0;
         }
@@ -236,7 +248,8 @@
 
     private void setFlag(int mask, boolean enabled) {
         boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
-                && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED;
+                && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
+                && !canRecentsActivityRotate();
         if (enabled) {
             mFlags |= mask;
         } else {
@@ -244,7 +257,8 @@
         }
 
         boolean isRotationEnabled = !TestProtocol.sDisableSensorRotation
-                && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED;
+                && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
+                && !canRecentsActivityRotate();
         if (wasRotationEnabled != isRotationEnabled) {
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (isRotationEnabled) {
@@ -324,8 +338,8 @@
     }
 
     @SurfaceRotation
-    public int getLauncherRotation() {
-        return mLauncherRotation;
+    public int getRecentsActivityRotation() {
+        return mRecentsActivityRotation;
     }
 
     public boolean isMultipleOrientationSupportedByDevice() {
@@ -333,14 +347,19 @@
                 == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE;
     }
 
-    public boolean isHomeRotationAllowed() {
-        return (mFlags & (FLAG_HOME_ROTATION_ALLOWED_IN_PREFS | FLAG_MULTIWINDOW_ROTATION_ALLOWED))
-                != 0 ||
-                (mFlags & FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING) != 0;
+    public boolean isRecentsActivityRotationAllowed() {
+        return ((mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+                == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
+                || (mFlags & (FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
+                        | FLAG_MULTIWINDOW_ROTATION_ALLOWED
+                        | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
     }
 
-    public boolean canLauncherRotate() {
-        return (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0 && isHomeRotationAllowed();
+    /**
+     * Returns true if the activity can rotate, if allowed by system rotation settings
+     */
+    public boolean canRecentsActivityRotate() {
+        return (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0 && isRecentsActivityRotationAllowed();
     }
 
     /**
@@ -508,8 +527,8 @@
                     extractObjectNameAndAddress(mOrientationHandler.toString())
                 + " mDisplayRotation=" + mDisplayRotation
                 + " mTouchRotation=" + mTouchRotation
-                + " mLauncherRotation=" + mLauncherRotation
-                + " mHomeRotation=" + isHomeRotationAllowed()
+                + " mRecentsActivityRotation=" + mRecentsActivityRotation
+                + " isRecentsActivityRotationAllowed=" + isRecentsActivityRotationAllowed()
                 + " mSystemRotation=" + systemRotationOn
                 + " mFlags=" + mFlags
                 + "]";
@@ -521,7 +540,8 @@
     public DeviceProfile getLauncherDeviceProfile() {
         InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
         // TODO also check the natural orientation is landscape or portrait
-        return  (mLauncherRotation == ROTATION_90 || mLauncherRotation == ROTATION_270)
+        return  (mRecentsActivityRotation == ROTATION_90
+                || mRecentsActivityRotation == ROTATION_270)
                 ? idp.landscapeProfile
                 : idp.portraitProfile;
     }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index bd8ab08..b9e0f62 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -131,6 +131,10 @@
                     TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
                     getString("result"));
         }
+        // b/143488140
+        mDevice.pressHome();
+        mDevice.waitForIdle();
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
     }
 
     // b/143488140
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index d927ffc..b7ba106 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.folder;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -61,16 +62,16 @@
         ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
         list.add(mItem1);
         list.add(mItem2);
-        FolderNameInfo[] nameInfos =
-                new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+        FolderNameInfos nameInfos = new FolderNameInfos();
         new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
-        assertEquals("Work", nameInfos[0].getLabel());
+        assertEquals("Work", nameInfos.getLabels()[0]);
 
-        nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
-        nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
-        nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+        nameInfos.setLabel(0, "candidate1", 1.0f);
+        nameInfos.setLabel(1, "candidate2", 1.0f);
+        nameInfos.setLabel(2, "candidate3", 1.0f);
         new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
-        assertEquals("Work", nameInfos[3].getLabel());
-
+        assertEquals("Work", nameInfos.getLabels()[3]);
+        assertTrue(nameInfos.hasSuggestions());
+        assertTrue(nameInfos.hasPrimary());
     }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 31bfc09..c78df62 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -226,7 +226,16 @@
      * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
      */
     public final float getDepth(Context context) {
-        if (BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode) {
+        return getDepth(context,
+                BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
+    }
+
+    /**
+     * Returns the amount of blur and wallpaper zoom for this state with {@param isMultiWindowMode}.
+     * @see #getDepth(Context).
+     */
+    public final float getDepth(Context context, boolean isMultiWindowMode) {
+        if (isMultiWindowMode) {
             return 0;
         }
         return getDepthUnchecked(context);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index dc1ff66..e29faac 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -71,7 +71,9 @@
 public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
+    public static final boolean DEBUG_FAILED_QUICKSWITCH = false;
 
+    public static final int ACTION_MOVE_ALLOW_EASY_FLING = MotionEvent.ACTION_MASK - 1;
     public static final int INVALID_PAGE = -1;
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
@@ -89,14 +91,16 @@
     // The following constants need to be scaled based on density. The scaled versions will be
     // assigned to the corresponding member variables below.
     private static final int FLING_THRESHOLD_VELOCITY = 500;
+    private static final int EASY_FLING_THRESHOLD_VELOCITY = 400;
     private static final int MIN_SNAP_VELOCITY = 1500;
     private static final int MIN_FLING_VELOCITY = 250;
 
     private boolean mFreeScroll = false;
 
-    protected int mFlingThresholdVelocity;
-    protected int mMinFlingVelocity;
-    protected int mMinSnapVelocity;
+    protected final int mFlingThresholdVelocity;
+    protected final int mEasyFlingThresholdVelocity;
+    protected final int mMinFlingVelocity;
+    protected final int mMinSnapVelocity;
 
     protected boolean mFirstLayout = true;
 
@@ -118,12 +122,17 @@
     private float mLastMotion;
     private float mLastMotionRemainder;
     private float mTotalMotion;
+    // Used in special cases where the fling checks can be relaxed for an intentional gesture
+    private boolean mAllowEasyFling;
     protected PagedOrientationHandler mOrientationHandler = PagedOrientationHandler.PORTRAIT;
 
     protected int[] mPageScrolls;
     private boolean mIsBeingDragged;
 
+    // The amount of movement to begin scrolling
     protected int mTouchSlop;
+    // The amount of movement to begin paging
+    protected int mPageSlop;
     private int mMaximumVelocity;
     protected boolean mAllowOverScroll = true;
 
@@ -170,24 +179,19 @@
 
         setHapticFeedbackEnabled(false);
         mIsRtl = Utilities.isRtl(getResources());
-        init();
-    }
 
-    /**
-     * Initializes various states for this workspace.
-     */
-    protected void init() {
-        Context context = getContext();
         mScroller = new OverScroller(context);
         setDefaultInterpolator(Interpolators.SCROLL);
         mCurrentPage = 0;
 
         final ViewConfiguration configuration = ViewConfiguration.get(context);
-        mTouchSlop = configuration.getScaledPagingTouchSlop();
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mPageSlop = configuration.getScaledPagingTouchSlop();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 
         float density = getResources().getDisplayMetrics().density;
         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
+        mEasyFlingThresholdVelocity = (int) (EASY_FLING_THRESHOLD_VELOCITY * density);
         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
 
@@ -913,6 +917,7 @@
                 mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
                 mLastMotionRemainder = 0;
                 mTotalMotion = 0;
+                mAllowEasyFling = false;
                 mActivePointerId = ev.getPointerId(0);
 
                 updateIsBeingDraggedOnTouchDown();
@@ -944,7 +949,7 @@
     private void updateIsBeingDraggedOnTouchDown() {
         // mScroller.isFinished should be false when being flinged.
         final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
-        final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
+        final boolean finishedScrolling = (mScroller.isFinished() || xDist < mPageSlop / 3);
 
         if (finishedScrolling) {
             mIsBeingDragged = false;
@@ -977,7 +982,7 @@
         final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
         final int diff = (int) Math.abs(primaryDirection - mLastMotion);
         final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
-        boolean moved = diff > touchSlop;
+        boolean moved = diff > touchSlop || ev.getAction() == ACTION_MOVE_ALLOW_EASY_FLING;
 
         if (moved) {
             // Scroll if the user moved far enough along the X axis
@@ -1160,6 +1165,7 @@
             mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
             mLastMotionRemainder = 0;
             mTotalMotion = 0;
+            mAllowEasyFling = false;
             mActivePointerId = ev.getPointerId(0);
             if (mIsBeingDragged) {
                 onScrollInteractionBegin();
@@ -1167,8 +1173,14 @@
             }
             break;
 
-            case MotionEvent.ACTION_MOVE:
-                if (mIsBeingDragged) {
+        case ACTION_MOVE_ALLOW_EASY_FLING:
+            // Start scrolling immediately
+            determineScrollingStart(ev);
+            mAllowEasyFling = true;
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            if (mIsBeingDragged) {
                 // Scroll to follow the motion event
                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
 
@@ -1214,9 +1226,14 @@
                     SIGNIFICANT_MOVE_THRESHOLD;
 
                 mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
-                boolean isFling = mTotalMotion > mTouchSlop && shouldFlingForVelocity(velocity);
+                boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
+                boolean isFling = passedSlop && shouldFlingForVelocity(velocity);
                 boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0;
                 boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0;
+                if (DEBUG_FAILED_QUICKSWITCH && !isFling && mAllowEasyFling) {
+                    Log.d("Quickswitch", "isFling=false vel=" + velocity
+                            + " threshold=" + mEasyFlingThresholdVelocity);
+                }
 
                 if (!mFreeScroll) {
                     // In the case that the page is moved far to one direction and then is flung
@@ -1316,7 +1333,8 @@
     }
 
     protected boolean shouldFlingForVelocity(int velocity) {
-        return Math.abs(velocity) > mFlingThresholdVelocity;
+        float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
+        return Math.abs(velocity) > threshold;
     }
 
     private void resetTouchState() {
@@ -1393,8 +1411,7 @@
     }
 
     private void onSecondaryPointerUp(MotionEvent ev) {
-        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
-                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+        final int pointerIndex = ev.getActionIndex();
         final int pointerId = ev.getPointerId(pointerIndex);
         if (pointerId == mActivePointerId) {
             // This was our active pointer going up. Choose a new
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index e950f3c..4af3544 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -28,16 +28,12 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
 import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
 
-import static java.util.Arrays.asList;
-import static java.util.Optional.ofNullable;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.annotation.SuppressLint;
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.Rect;
@@ -104,6 +100,7 @@
 import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -327,11 +324,7 @@
     public void startEditingFolderName() {
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                ofNullable(mInfo)
-                        .map(info -> info.suggestedFolderNames)
-                        .map(folderNames -> (FolderNameInfo[]) folderNames
-                                .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
-                        .ifPresent(this::showLabelSuggestions);
+                showLabelSuggestions();
             }
             mFolderName.setHint("");
             mIsEditingName = true;
@@ -461,32 +454,24 @@
      * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
      * rest of the suggestions to InputMethodManager.
      */
-    private void showLabelSuggestions(FolderNameInfo[] nameInfos) {
-        if (nameInfos == null) {
+    private void showLabelSuggestions() {
+        if (mInfo.suggestedFolderNames == null) {
             return;
         }
-        // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
-        // string.
-        boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !isEmpty(
-                nameInfos[0].getLabel())
-                || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
-                nameInfos[1].getLabel());
-
-        if (shouldOpen) {
+        if (mInfo.suggestedFolderNames.hasSuggestions()) {
             // update the primary suggestion if the folder name is empty.
             if (isEmpty(mFolderName.getText())) {
-                CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
-                if (!isEmpty(firstLabel)) {
+                if (mInfo.suggestedFolderNames.hasPrimary()) {
                     mFolderName.setHint("");
-                    mFolderName.setText(firstLabel);
+                    mFolderName.setText(mInfo.suggestedFolderNames.getLabels()[0]);
                     mFolderName.selectAll();
                 }
             }
             mFolderName.showKeyboard();
             mFolderName.displayCompletions(
-                    asList(nameInfos).subList(0, nameInfos.length).stream()
+                    Stream.of(mInfo.suggestedFolderNames.getLabels())
                             .filter(Objects::nonNull)
-                            .map(s -> s.getLabel().toString())
+                            .map(Object::toString)
                             .filter(s -> !s.isEmpty())
                             .filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString()))
                             .collect(Collectors.toList()));
@@ -1021,14 +1006,11 @@
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind
                 && total > 1 /* no need to update if there's one icon */) {
             Executors.MODEL_EXECUTOR.post(() -> {
-                FolderNameInfo[] nameInfos =
-                        new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+                FolderNameInfos nameInfos = new FolderNameInfos();
                 FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
                 fnp.getSuggestedFolderName(
                         getContext(), mInfo.contents, nameInfos);
-                mInfo.suggestedFolderNames = new Intent().putExtra(
-                        FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
-                        nameInfos);
+                mInfo.suggestedFolderNames = nameInfos;
             });
         }
     }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e24033f..152fd37 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -84,7 +84,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
-import java.util.stream.Stream;
+
 
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
@@ -411,8 +411,7 @@
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
 
-            FolderNameInfo[] nameInfos =
-                    new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+            FolderNameInfos nameInfos = new FolderNameInfos();
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 Executors.MODEL_EXECUTOR.post(() -> {
                     d.folderNameProvider.getSuggestedFolderName(
@@ -428,7 +427,7 @@
     }
 
     private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
-            FolderNameInfo[] nameInfos, InstanceId instanceId) {
+            FolderNameInfos nameInfos, InstanceId instanceId) {
         postDelayed(() -> {
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
@@ -440,7 +439,7 @@
     /**
      * Set the suggested folder name.
      */
-    public void setLabelSuggestion(FolderNameInfo[] nameInfos, InstanceId instanceId) {
+    public void setLabelSuggestion(FolderNameInfos nameInfos, InstanceId instanceId) {
         if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
             return;
         }
@@ -448,22 +447,21 @@
                 || mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
             return;
         }
-        if (nameInfos == null || Stream.of(nameInfos)
-                .allMatch(nameInfo -> nameInfo == null || isEmpty(nameInfo.getLabel()))) {
+        if (nameInfos == null || !nameInfos.hasSuggestions()) {
             StatsLogManager.newInstance(getContext()).logger()
                     .withInstanceId(instanceId)
                     .withItemInfo(mInfo)
                     .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS);
             return;
         }
-        if (nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
+        if (!nameInfos.hasPrimary()) {
             StatsLogManager.newInstance(getContext()).logger()
                     .withInstanceId(instanceId)
                     .withItemInfo(mInfo)
                     .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY);
             return;
         }
-        CharSequence newTitle = nameInfos[0].getLabel();
+        CharSequence newTitle = nameInfos.getLabels()[0];
         FromState fromState = mInfo.getFromLabelState();
 
         mInfo.setTitle(newTitle);
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
deleted file mode 100644
index 1841cd9..0000000
--- a/src/com/android/launcher3/folder/FolderNameInfo.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.folder;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-
-/**
- * Information about a single label suggestions of the Folder.
- */
-
-public final class FolderNameInfo implements Parcelable {
-    private final double mScore;
-    private final CharSequence mLabel;
-
-    /**
-     * Create a simple completion with label.
-     *
-     * @param label The text that should be inserted into the editor and pushed to
-     *              InputMethodManager suggestions.
-     * @param score The score for the label between 0.0 and 1.0.
-     */
-    public FolderNameInfo(CharSequence label, double score) {
-        mScore = score;
-        mLabel = label;
-    }
-
-    private FolderNameInfo(Parcel source) {
-        mScore = source.readDouble();
-        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-    }
-
-    public CharSequence getLabel() {
-        return mLabel;
-    }
-
-    public double getScore() {
-        return mScore;
-    }
-
-    /**
-     * Used to package this object into a {@link Parcel}.
-     *
-     * @param dest  The {@link Parcel} to be written.
-     * @param flags The flags used for parceling.
-     */
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeDouble(mScore);
-        TextUtils.writeToParcel(mLabel, dest, flags);
-    }
-
-    /**
-     * Used to make this class parcelable.
-     */
-    @NonNull
-    public static final Parcelable.Creator<FolderNameInfo> CREATOR =
-            new Parcelable.Creator<FolderNameInfo>() {
-                public FolderNameInfo createFromParcel(Parcel source) {
-                    return new FolderNameInfo(source);
-                }
-
-                public FolderNameInfo[] newArray(int size) {
-                    return new FolderNameInfo[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-        return String.format("%s:%.2f", mLabel, mScore);
-    }
-}
diff --git a/src/com/android/launcher3/folder/FolderNameInfos.java b/src/com/android/launcher3/folder/FolderNameInfos.java
new file mode 100644
index 0000000..457ae87
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfos.java
@@ -0,0 +1,117 @@
+/*
+ * 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.folder;
+
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Information about a  label suggestions of a Folder.
+ */
+
+public class FolderNameInfos {
+    public static final int SUCCESS = 1;
+    public static final int HAS_PRIMARY = 1 << 1;
+    public static final int HAS_SUGGESTIONS = 1 << 2;
+    public static final int ERROR_NO_PROVIDER = 1 << 3;
+    public static final int ERROR_APP_LOOKUP_FAILED = 1 << 4;
+    public static final int ERROR_ALL_APP_LOOKUP_FAILED = 1 << 5;
+    public static final int ERROR_NO_LABELS_GENERATED = 1 << 6;
+    public static final int ERROR_LABEL_LOOKUP_FAILED = 1 << 7;
+    public static final int ERROR_ALL_LABEL_LOOKUP_FAILED = 1 << 8;
+    public static final int ERROR_NO_PACKAGES = 1 << 9;
+
+    private int mStatus;
+    private final CharSequence[] mLabels;
+    private final Float[] mScores;
+
+    public FolderNameInfos() {
+        mStatus = 0;
+        mLabels = new CharSequence[FolderNameProvider.SUGGEST_MAX];
+        mScores = new Float[FolderNameProvider.SUGGEST_MAX];
+    }
+
+    /**
+     * set the status of FolderNameInfos.
+     */
+    public void setStatus(int statusBit) {
+        mStatus = mStatus | statusBit;
+    }
+
+    /**
+     * returns status of FolderNameInfos generations.
+     */
+    public int status() {
+        return mStatus;
+    }
+
+    /**
+     * return true if the first suggestion is a Primary suggestion.
+     */
+    public boolean hasPrimary() {
+        return (mStatus & HAS_PRIMARY) > 0 && (mLabels[0] != null);
+    }
+
+    /**
+     * return true if there is at least one valid suggestion.
+     */
+    public boolean hasSuggestions() {
+        for (CharSequence l : mLabels) {
+            if (l != null && !TextUtils.isEmpty(l)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * assign label and score in the specified index.
+     */
+    public void setLabel(int index, CharSequence label, Float score) {
+        if (index < mLabels.length) {
+            mLabels[index] = label;
+            mScores[index] = score;
+        }
+    }
+
+    /**
+     * returns true if the label is found in label suggestions/
+     */
+    public boolean contains(CharSequence label) {
+        return Arrays.stream(mLabels)
+                .filter(Objects::nonNull)
+                .anyMatch(l -> l.toString().equalsIgnoreCase(label.toString()));
+    }
+
+
+    public CharSequence[] getLabels() {
+        return mLabels;
+    }
+
+    public Float[] getScores() {
+        return mScores;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return String.format("status=%s, labels=%s", Integer.toBinaryString(mStatus),
+                Arrays.toString(mLabels));
+    }
+}
+
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 2be0bce..d166e27 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -96,10 +96,10 @@
      */
     public void getSuggestedFolderName(Context context,
             ArrayList<WorkspaceItemInfo> workspaceItemInfos,
-            FolderNameInfo[] nameInfos) {
+            FolderNameInfos nameInfos) {
         Preconditions.assertWorkerThread();
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+            Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
@@ -124,7 +124,7 @@
             info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
+            Log.d(TAG, "getSuggestedFolderName:" + nameInfos.toString());
         }
     }
 
@@ -138,39 +138,37 @@
                 .findAny();
     }
 
-    private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
-        if (nameInfos.length == 0 || contains(nameInfos, label)) {
+    private void setAsFirstSuggestion(FolderNameInfos nameInfos, CharSequence label) {
+        if (nameInfos == null || nameInfos.contains(label)) {
             return;
         }
-        for (int i = nameInfos.length - 1; i > 0; i--) {
-            if (nameInfos[i - 1] != null && !TextUtils.isEmpty(nameInfos[i - 1].getLabel())) {
-                nameInfos[i] = nameInfos[i - 1];
+        nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
+        nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
+        CharSequence[] labels = nameInfos.getLabels();
+        Float[] scores = nameInfos.getScores();
+        for (int i = labels.length - 1; i > 0; i--) {
+            if (labels[i - 1] != null && !TextUtils.isEmpty(labels[i - 1])) {
+                nameInfos.setLabel(i, labels[i - 1], scores[i - 1]);
             }
         }
-        nameInfos[0] = new FolderNameInfo(label, 1.0);
+        nameInfos.setLabel(0, label, 1.0f);
     }
 
-    private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
-        if (nameInfos.length == 0 || contains(nameInfos, label)) {
+    private void setAsLastSuggestion(FolderNameInfos nameInfos, CharSequence label) {
+        if (nameInfos == null || nameInfos.contains(label)) {
             return;
         }
-
-        for (int i = 0; i < nameInfos.length; i++) {
-            if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
-                nameInfos[i] = new FolderNameInfo(label, 1.0);
+        nameInfos.setStatus(FolderNameInfos.HAS_PRIMARY);
+        nameInfos.setStatus(FolderNameInfos.HAS_SUGGESTIONS);
+        CharSequence[] labels = nameInfos.getLabels();
+        for (int i = 0; i < labels.length; i++) {
+            if (labels[i] == null || TextUtils.isEmpty(labels[i])) {
+                nameInfos.setLabel(i, label, 1.0f);
                 return;
             }
         }
         // Overwrite the last suggestion.
-        int lastIndex = nameInfos.length - 1;
-        nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
-    }
-
-    private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
-        return Arrays.stream(nameInfos)
-                .filter(Objects::nonNull)
-                .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
-                        label.toString()));
+        nameInfos.setLabel(labels.length - 1, label, 1.0f);
     }
 
     private class FolderNameWorker extends BaseModelUpdateTask {
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
index 13ab033..629a0ee 100644
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ b/src/com/android/launcher3/model/AppLaunchTracker.java
@@ -40,6 +40,7 @@
     public static final String CONTAINER_ALL_APPS = Integer.toString(ContainerType.ALLAPPS);
     public static final String CONTAINER_PREDICTIONS = Integer.toString(ContainerType.PREDICTION);
     public static final String CONTAINER_SEARCH = Integer.toString(ContainerType.SEARCHRESULT);
+    public static final String CONTAINER_OVERVIEW = Integer.toString(ContainerType.OVERVIEW);
 
 
     public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index f2073ef..102ec31 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -59,7 +59,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
-import com.android.launcher3.folder.FolderNameInfo;
+import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
@@ -953,13 +953,12 @@
 
         synchronized (mBgDataModel) {
             for (int i = 0; i < mBgDataModel.folders.size(); i++) {
-                FolderNameInfo[] suggestionInfos =
-                        new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+                FolderNameInfos suggestionInfos = new FolderNameInfos();
                 FolderInfo info = mBgDataModel.folders.valueAt(i);
                 if (info.suggestedFolderNames == null) {
                     provider.getSuggestedFolderName(mApp.getContext(), info.contents,
                             suggestionInfos);
-                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
+                    info.suggestedFolderNames = suggestionInfos;
                 }
             }
         }
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 08eb383..ecd18ce 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -29,17 +29,12 @@
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
 
-import static java.util.Arrays.stream;
-import static java.util.Optional.ofNullable;
-
-import android.content.Intent;
 import android.os.Process;
-import android.text.TextUtils;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderNameInfo;
+import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -51,8 +46,6 @@
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
-import java.util.Objects;
-import java.util.Optional;
 import java.util.OptionalInt;
 import java.util.stream.IntStream;
 
@@ -85,7 +78,7 @@
 
     public int options;
 
-    public Intent suggestedFolderNames;
+    public FolderNameInfos suggestedFolderNames;
 
     /**
      * The apps and shortcuts
@@ -224,17 +217,16 @@
     public OptionalInt getAcceptedSuggestionIndex() {
         String newLabel = checkNotNull(title,
                 "Expected valid folder label, but found null").toString();
-        return getSuggestedLabels()
-                .map(suggestionsArray ->
-                        IntStream.range(0, suggestionsArray.length)
-                                .filter(
-                                        index -> !isEmpty(suggestionsArray[index])
-                                                && newLabel.equalsIgnoreCase(
-                                                suggestionsArray[index]))
-                                .sequential()
-                                .findFirst()
-                ).orElse(OptionalInt.empty());
-
+        if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
+            return OptionalInt.empty();
+        }
+        CharSequence[] labels = suggestedFolderNames.getLabels();
+        return IntStream.range(0, labels.length)
+                .filter(index -> !isEmpty(labels[index])
+                        && newLabel.equalsIgnoreCase(
+                        labels[index].toString()))
+                .sequential()
+                .findFirst();
     }
 
     /**
@@ -264,19 +256,15 @@
                     : LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
         }
 
-        Optional<String[]> suggestedLabels = getSuggestedLabels();
-        boolean isEmptySuggestions = suggestedLabels
-                .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
-                .orElse(true);
-        if (isEmptySuggestions) {
+        // TODO: if suggestedFolderNames is null then it infrastructure issue, not
+        // ranking issue. We should log these appropriately.
+        if (suggestedFolderNames == null || !suggestedFolderNames.hasSuggestions()) {
             return title.length() > 0
                     ? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS
                     : LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
         }
 
-        boolean hasValidPrimary = suggestedLabels
-                .map(labels -> !isEmpty(labels[0]))
-                .orElse(false);
+        boolean hasValidPrimary = suggestedFolderNames != null && suggestedFolderNames.hasPrimary();
         if (title.length() == 0) {
             return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY
                     : LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
@@ -306,20 +294,6 @@
         return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
     }
 
-    private Optional<String[]> getSuggestedLabels() {
-        return ofNullable(suggestedFolderNames)
-                .map(folderNames ->
-                        (FolderNameInfo[])
-                                folderNames.getParcelableArrayExtra(EXTRA_FOLDER_SUGGESTIONS))
-                .map(folderNameInfoArray ->
-                        stream(folderNameInfoArray)
-                                .filter(Objects::nonNull)
-                                .map(FolderNameInfo::getLabel)
-                                .filter(Objects::nonNull)
-                                .map(CharSequence::toString)
-                                .toArray(String[]::new));
-    }
-
     /**
      * Returns {@link LauncherLogProto.LauncherEvent} to log current folder label info.
      *