Merge "Follow up change to GridBackupTable per V2 backup / restore (multi-db)" into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index 01302cf..e556c94 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -48,3 +48,6 @@
 -dontwarn android.view.**
 -dontwarn android.os.**
 -dontwarn android.graphics.**
+
+# Ignore warnings for hidden utility classes referenced from the shared lib
+-dontwarn com.android.internal.util.**
\ No newline at end of file
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 0560d68..3c7f308 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -120,6 +120,8 @@
   BACK_GESTURE = 19;
   UNDO = 20;
   DISMISS_PREDICTION = 21;
+  HYBRID_HOTSEAT_ACCEPTED = 22;
+  HYBRID_HOTSEAT_CANCELED = 23;
 }
 
 enum TipType {
@@ -129,6 +131,7 @@
   QUICK_SCRUB_TEXT = 3;
   PREDICTION_TEXT = 4;
   DWB_TOAST = 5;
+  HYBRID_HOTSEAT = 6;
 }
 
 // Used to define the action component of the LauncherEvent.
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 4c87945..00e72b1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.hybridhotseat;
 
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -32,6 +35,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -95,6 +99,7 @@
         handleClose(true);
         mHotseatEduController.migrate();
         mHotseatEduController.finishOnboarding();
+        logUserAction(true);
         Toast.makeText(mLauncher, R.string.hotseat_items_migrated, Toast.LENGTH_LONG).show();
     }
 
@@ -102,6 +107,7 @@
         if (mHotseatEduController == null) return;
         Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
         mHotseatEduController.finishOnboarding();
+        logUserAction(false);
         handleClose(true);
     }
 
@@ -133,7 +139,28 @@
                 mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
     }
 
+    private void logUserAction(boolean migrated) {
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        LauncherLogProto.Target target = new LauncherLogProto.Target();
+        action.type = LauncherLogProto.Action.Type.TOUCH;
+        action.touch = LauncherLogProto.Action.Touch.TAP;
+        target.containerType = LauncherLogProto.ContainerType.TIP;
+        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+        target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
+                : HYBRID_HOTSEAT_CANCELED;
+        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+    }
 
+    private void logOnBoardingSeen() {
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        LauncherLogProto.Target target = new LauncherLogProto.Target();
+        action.type = LauncherLogProto.Action.Type.TIP;
+        target.containerType = LauncherLogProto.ContainerType.TIP;
+        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+    }
     private void animateOpen() {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
@@ -165,6 +192,7 @@
             return;
         }
         mLauncher.getDragLayer().addView(this);
+        logOnBoardingSeen();
         animateOpen();
         for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
             WorkspaceItemInfo info = predictions.get(i);
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 cc6ec69..0b05427 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
@@ -109,6 +109,7 @@
     private AppPredictor mAppPredictor;
     private AllAppsStore mAllAppsStore;
     private AnimatorSet mIconRemoveAnimators;
+    private boolean mUIUpdatePaused = false;
 
     private HotseatEduController mHotseatEduController;
 
@@ -168,7 +169,7 @@
     }
 
     private void fillGapsWithPrediction(boolean animate, Runnable callback) {
-        if (!isReady() || mDragObject != null) {
+        if (!isReady() || mUIUpdatePaused || mDragObject != null) {
             return;
         }
         List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
@@ -250,6 +251,16 @@
     }
 
     /**
+     * start and pauses predicted apps update on the hotseat
+     */
+    public void setPauseUIUpdate(boolean paused) {
+        mUIUpdatePaused = paused;
+        if (!paused) {
+            fillGapsWithPrediction();
+        }
+    }
+
+    /**
      * Creates App Predictor with all the current apps pinned on the hotseat
      */
     public void createPredictor() {
@@ -447,17 +458,40 @@
     /**
      * Unpins pinned app when it's converted into a folder
      */
-    public void folderCreatedFromIcon(ItemInfo info, FolderInfo folderInfo) {
+    public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
+        if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+            return;
+        }
         AppTarget target = getAppTargetFromItemInfo(info);
-        if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && !isInHotseat(
-                info)) {
+        ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
+        ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
+                Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
+
+        if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains(
+                target)) {
             notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
-        } else if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
-                && folderInfo.screenId == Workspace.FIRST_SCREEN_ID && !isInFirstPage(info)) {
+        } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup(
+                firstScreenVG).contains(target)) {
             notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
         }
     }
 
+    /**
+     * Pins workspace item created when all folder items are removed but one
+     */
+    public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
+        if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+            return;
+        }
+        AppTarget target = getAppTargetFromItemInfo(info);
+        if (isInHotseat(info)) {
+            notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+        } else if (isInFirstPage(info)) {
+            notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
+        }
+    }
+
+
     @Override
     public void onDragEnd() {
         if (mDragObject == null) {
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 b2e1798..4bbb48c 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
@@ -86,6 +86,7 @@
         mIsDrawingDot = true;
         int count = canvas.save();
         canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+        canvas.scale(1 + 2 * RING_EFFECT_RATIO, 1 + 2 * RING_EFFECT_RATIO);
         super.drawDotIfNecessary(canvas);
         canvas.restoreToCount(count);
         mIsDrawingDot = false;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b87fcf2..d1a487a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,20 +20,24 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Gravity;
+import android.view.View;
+
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.popup.SystemShortcut;
@@ -161,6 +165,15 @@
     }
 
     @Override
+    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
+            @Nullable String sourceContainer) {
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.setPauseUIUpdate(true);
+        }
+        return super.startActivitySafely(v, intent, item, sourceContainer);
+    }
+
+    @Override
     protected void onActivityFlagsChanged(int changeBits) {
         super.onActivityFlagsChanged(changeBits);
 
@@ -169,16 +182,27 @@
                 && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) {
             onStateOrResumeChanged();
         }
+
+        if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null
+                && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) {
+            mHotseatPredictionController.setPauseUIUpdate(false);
+        }
     }
 
     @Override
-    public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
-            int screenId, int cellX, int cellY) {
-        FolderIcon fi =  super.addFolder(layout, info, container, screenId, cellX, cellY);
+    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
+        super.folderCreatedFromItem(folder, itemInfo);
         if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.folderCreatedFromIcon(info, fi.getFolder().getInfo());
+            mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
         }
-        return fi;
+    }
+
+    @Override
+    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
+        super.folderConvertedToItem(folder, itemInfo);
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 28fc3da..1b6d291 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -234,6 +234,7 @@
             public void adjustActivityControllerInterpolators() {
                 if (mAdjustInterpolatorsRunnable != null) {
                     mAdjustInterpolatorsRunnable.run();
+                    mAdjustInterpolatorsRunnable = null;
                 }
             }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index f0516ac..482348f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -21,7 +21,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -972,7 +972,7 @@
         }
         mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
 
-        if (QUICKSTEP_SPRINGS.get()) {
+        if (UNSTABLE_SPRINGS.get()) {
             mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
         }
         mLauncherTransitionController.getAnimationPlayer().start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 3e6def3..34b2bdb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,23 +1,18 @@
 package com.android.quickstep;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
+import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
 import android.util.Log;
 
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
@@ -46,33 +41,8 @@
             }
 
             case TestProtocol.REQUEST_HOTSEAT_TOP: {
-                if (mLauncher == null) return null;
-
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        PortraitStatesTouchController.getHotseatTop(mLauncher));
-                return response;
-            }
-
-            case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
-                try {
-                    final int leftMargin = MAIN_EXECUTOR.submit(() ->
-                            getRecentsView().getLeftGestureMargin()).get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return response;
-            }
-
-            case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
-                try {
-                    final int rightMargin = MAIN_EXECUTOR.submit(() ->
-                            getRecentsView().getRightGestureMargin()).get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return response;
+                return getLauncherUIProperty(
+                        Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
             }
 
             case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
@@ -99,11 +69,12 @@
         return super.call(method);
     }
 
-    private RecentsView getRecentsView() {
+    @Override
+    protected Activity getCurrentActivity() {
         OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
                 new RecentsAnimationDeviceState(mContext));
         try {
-            return observer.getActivityInterface().getCreatedActivity().getOverviewPanel();
+            return observer.getActivityInterface().getCreatedActivity();
         } finally {
             observer.onDestroy();
         }
@@ -111,11 +82,6 @@
 
     @Override
     protected boolean isLauncherInitialized() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "isLauncherInitialized.TouchInteractionService.isInitialized=" +
-                            TouchInteractionService.isInitialized());
-        }
         return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
     }
 }
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 edaef30..3364b66 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -136,16 +136,13 @@
                 TouchInteractionService.this.initInputMonitor();
                 preloadOverview(true /* fromInit */);
             });
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized");
-            }
             sIsInitialized = true;
         }
 
         @BinderThread
         @Override
         public void onOverviewToggle() {
-            TestLogging.recordEvent("onOverviewToggle");
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
             mOverviewCommandHelper.onOverviewToggle();
         }
 
@@ -397,9 +394,6 @@
 
     @Override
     public void onDestroy() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed");
-        }
         sIsInitialized = false;
         if (mDeviceState.isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
@@ -428,13 +422,17 @@
             Log.e(TAG, "Unknown event " + ev);
             return;
         }
+        MotionEvent event = (MotionEvent) ev;
+
+        TestLogging.recordMotionEvent(
+                TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
+
         if (!mDeviceState.isUserUnlocked()) {
             return;
         }
 
         Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
-        MotionEvent event = (MotionEvent) ev;
         if (event.getAction() == ACTION_DOWN) {
             GestureState newGestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.generateAndSetLogId());
@@ -715,20 +713,15 @@
             }
         } else {
             // Dump everything
+            FeatureFlags.dump(pw);
+            PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
             mDeviceState.dump(pw);
+            mOverviewComponentObserver.dump(pw);
             pw.println("TouchState:");
             boolean resumed = mOverviewComponentObserver != null
                     && mOverviewComponentObserver.getActivityInterface().isResumed();
             pw.println("  resumed=" + resumed);
             pw.println("  mConsumer=" + mConsumer.getName());
-            pw.println("FeatureFlags:");
-            pw.println("  APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
-            pw.println("  QUICKSTEP_SPRINGS=" + QUICKSTEP_SPRINGS.get());
-            pw.println("  UNSTABLE_SPRINGS=" + UNSTABLE_SPRINGS.get());
-            pw.println("  ADAPTIVE_ICON_WINDOW_ANIM=" + ADAPTIVE_ICON_WINDOW_ANIM.get());
-            pw.println("  ENABLE_QUICKSTEP_LIVE_TILE=" + ENABLE_QUICKSTEP_LIVE_TILE.get());
-            pw.println("  ENABLE_HINTS_IN_OVERVIEW=" + ENABLE_HINTS_IN_OVERVIEW.get());
-            pw.println("  FAKE_LANDSCAPE_UI=" + FAKE_LANDSCAPE_UI.get());
             ActiveGestureLog.INSTANCE.dump("", pw);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 8ae4f06..05c206f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -3,6 +3,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -35,7 +36,7 @@
 
     protected void setActive(MotionEvent ev) {
         mState = STATE_ACTIVE;
-        TestLogging.recordEvent("pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
         mInputMonitor.pilferPointers();
 
         // Send cancel event
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index d01e1a4..ba1d38c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -203,7 +204,7 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        TestLogging.recordEvent("pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         Intent intent = new Intent(Intent.ACTION_MAIN)
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 1b0e05a..3ee3c2d 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
@@ -45,6 +45,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.BaseActivityInterface;
@@ -303,7 +304,7 @@
         if (mInteractionHandler == null) {
             return;
         }
-        TestLogging.recordEvent("pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         mActivityInterface.closeOverlay();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 32a67ca..f161cc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
@@ -107,7 +108,7 @@
                 ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
-                TestLogging.recordEvent("pilferPointers");
+                TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
                 mInputMonitor.pilferPointers();
             }
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 6bfc3fd..823b254 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.quickstep.GestureState;
@@ -64,7 +65,7 @@
 
     private void onInterceptTouch() {
         if (mInputMonitor != null) {
-            TestLogging.recordEvent("pilferPointers");
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
             mInputMonitor.pilferPointers();
         }
     }
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 aaba308..321af6c 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
@@ -74,7 +74,6 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ListView;
@@ -1890,16 +1889,6 @@
         }
     }
 
-    public int getLeftGestureMargin() {
-        final WindowInsets insets = getRootWindowInsets();
-        return Math.max(insets.getSystemGestureInsets().left, insets.getSystemWindowInsetLeft());
-    }
-
-    public int getRightGestureMargin() {
-        final WindowInsets insets = getRootWindowInsets();
-        return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
-    }
-
     /** If it's in the live tile mode, switch the running task into screenshot mode. */
     public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
         TaskView taskView = getRunningTaskView();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 49f667e..8ed1392 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ConfigurationCompat;
 
 /**
  * A task in the Recents view.
@@ -357,7 +358,8 @@
 
             final float thumbnailScale;
             int thumbnailRotation = mThumbnailData.rotation;
-            int currentRotation = getDisplay() != null ? getDisplay().getRotation() : 0;
+            int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
+                    getResources().getConfiguration());
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
             // Landscape vs portrait change
             boolean windowingModeSupportsRotation = !mActivity.isInMultiWindowMode()
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 294bb7b..8b7ce10 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
@@ -58,6 +58,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -334,9 +335,8 @@
             Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
         if (mTask != null) {
             final ActivityOptions opts;
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                TestLogging.recordEvent("startActivityFromRecentsAsync:" + mTask);
-            }
+            TestLogging.recordEvent(
+                    TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
             if (animate) {
                 opts = mActivity.getActivityLaunchOptions(this);
                 if (freezeTaskList) {
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 5f81e1f..45a62ab 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -67,13 +67,13 @@
     <string  name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
 
     <!-- Hotseat migration notification title -->
-    <string translatable="false" name="hotseat_migrate_prompt_title">Get suggested apps on the home screen</string>
+    <string translatable="false" name="hotseat_migrate_prompt_title">Easily access your most-used apps</string>
     <!-- Hotseat migration notification content -->
-    <string translatable="false" name="hotseat_migrate_prompt_content">Tap to set up</string>
+    <string translatable="false" name="hotseat_migrate_prompt_content">Pixel suggests your favorite apps based on your routines. Tap to learn more.</string>
     <!-- Hotseat migration wizard title -->
     <string translatable="false" name="hotseat_migrate_title">Suggested apps replace the bottom row of apps</string>
     <!-- Hotseat migration wizard message -->
-    <string translatable="false" name="hotseat_migrate_message">To pin a favorite app, drag it over a suggested app. To hide a suggested app, touch &amp; hold it.</string>
+    <string translatable="false" name="hotseat_migrate_message">Your current apps will move to the last screen. To pin or block a suggested app, drag it off the bottom row.</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
     <string translatable="false" name="hotseat_items_migrated">Bottom row of apps moved to last screen</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 94ff63b..15503b8 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -167,7 +167,7 @@
     @Override
     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
         if (requestCode != -1) {
-            mPendingActivityRequestCode = -1;
+            mPendingActivityRequestCode = requestCode;
             StartActivityParams params = new StartActivityParams(this, requestCode);
             params.intent = intent;
             params.options = options;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java b/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
deleted file mode 100644
index 2d9a161..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2018 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.uioverrides;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.shared.system.RotationWatcher;
-
-/**
- * Utility class for listening for rotation changes
- */
-public class DisplayRotationListener extends RotationWatcher {
-
-    private final Runnable mCallback;
-    private Handler mHandler;
-
-    public DisplayRotationListener(Context context, Runnable callback) {
-        super(context);
-        mCallback = callback;
-    }
-
-    @Override
-    public void enable() {
-        if (mHandler == null) {
-            mHandler = new Handler();
-        }
-        super.enable();
-    }
-
-    @Override
-    protected void onRotationChanged(int i) {
-        mHandler.post(mCallback);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 6e7c087..2e422b7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -14,8 +14,12 @@
 
 package com.android.launcher3.uioverrides.plugins;
 
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.plugins.Plugin;
@@ -24,6 +28,9 @@
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.plugins.PluginPrefs;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class PluginManagerWrapper {
@@ -75,4 +82,27 @@
     public static boolean hasPlugins(Context context) {
         return PluginPrefs.hasPlugins(context);
     }
+
+    public void dump(PrintWriter pw) {
+        final List<ComponentName> enabledPlugins = new ArrayList<>();
+        final List<ComponentName> disabledPlugins = new ArrayList<>();
+        for (String action : getPluginActions()) {
+            for (ResolveInfo resolveInfo : mContext.getPackageManager().queryIntentServices(
+                    new Intent(action), MATCH_DISABLED_COMPONENTS)) {
+                ComponentName installedPlugin = new ComponentName(
+                        resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+                if (mPluginEnabler.isEnabled(installedPlugin)) {
+                    enabledPlugins.add(installedPlugin);
+                } else {
+                    disabledPlugins.add(installedPlugin);
+                }
+            }
+        }
+
+        pw.println("PluginManager:");
+        pw.println("  numEnabledPlugins=" + enabledPlugins.size());
+        pw.println("  numDisabledPlugins=" + disabledPlugins.size());
+        pw.println("  enabledPlugins=" + enabledPlugins);
+        pw.println("  disabledPlugins=" + disabledPlugins);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 99b2a81..d5ce734 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -25,7 +25,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.TimeInterpolator;
@@ -277,7 +277,7 @@
     private void handleFirstSwipeToOverview(final ValueAnimator animator,
             final long expectedDuration, final LauncherState targetState, final float velocity,
             final boolean isFling) {
-        if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+        if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
                 && targetState == OVERVIEW) {
             mFinishFastOnSecondTouch = true;
         } else  if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 73b78db..85ef4c6 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -35,6 +35,7 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -233,4 +234,13 @@
     public BaseActivityInterface getActivityInterface() {
         return mActivityInterface;
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println("OverviewComponentObserver:");
+        pw.println("  isDefaultHome=" + mIsDefaultHome);
+        pw.println("  isHomeDisabled=" + mIsHomeDisabled);
+        pw.println("  homeAndOverviewSame=" + mIsHomeAndOverviewSame);
+        pw.println("  overviewIntent=" + mOverviewIntent);
+        pw.println("  homeIntent=" + mCurrentHomeIntent);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 46af8bf..00adfbe 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -201,6 +201,7 @@
         if (mInputConsumerController != null) {
             mInputConsumerController.setInputListener(null);
         }
+        mInputProxySupplier = null;
     }
 
     private boolean onInputConsumerEvent(InputEvent ev) {
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 97424bb..1229a63 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -197,7 +197,8 @@
 
         Wait.atMost(() -> "Switching nav mode: "
                         + launcher.getNavigationModeMismatchError(),
-                () -> launcher.getNavigationModeMismatchError() == null, WAIT_TIME_MS, launcher);
+                () -> launcher.getNavigationModeMismatchError() == null,
+                60000 /* b/148422894 */, launcher);
 
         return true;
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index d2f5d8f..3b3e1c7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -25,7 +25,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
-import android.os.RemoteException;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -84,17 +83,6 @@
     }
 
     @Test
-    @PortraitLandscape
-    @Ignore // Enable after b/131115533
-    public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
-        mDevice.pressRecentApps();
-        waitForState("Launcher internal state didn't switch to Overview",
-                () -> LauncherState.OVERVIEW);
-
-        assertNotNull("getOverview() returned null", mLauncher.getOverview());
-    }
-
-    @Test
     @NavigationModeSwitch
     @PortraitLandscape
     public void testWorkspaceSwitchToAllApps() {
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 893d796..923352e 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -46,9 +46,9 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
+            style="@style/TextHeadline"
             android:layout_weight="1"
             android:background="@android:color/transparent"
-            android:fontFamily="sans-serif-condensed"
             android:textStyle="bold"
             android:gravity="center_horizontal"
             android:hint="@string/folder_hint_text"
@@ -58,7 +58,7 @@
             android:singleLine="true"
             android:textColor="?attr/folderTextColor"
             android:textColorHighlight="?android:attr/colorControlHighlight"
-            android:textColorHint="?attr/folderTextColor"
+            android:textColorHint="?attr/folderHintColor"
             android:textSize="@dimen/folder_label_text_size" />
 
         <com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 707424c..aef878b 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -40,6 +40,7 @@
     <attr name="folderIconRadius" format="float" />
     <attr name="folderIconBorderColor" format="color" />
     <attr name="folderTextColor" format="color" />
+    <attr name="folderHintColor" format="color" />
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bfa92f7..bde3c31 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -142,7 +142,7 @@
     <string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string>
 
     <!-- Default folder title -->
-    <string name="folder_hint_text">Unnamed Folder</string>
+    <string name="folder_hint_text">Tap to edit</string>
 
     <!-- Accessibility -->
     <!-- The format string for when an app is temporarily disabled. -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 35ae49c..1174a2f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -48,6 +48,7 @@
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="folderTextColor">#FF212121</item>
+        <item name="folderHintColor">#FF616161</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
 
         <item name="android:windowTranslucentStatus">false</item>
@@ -96,6 +97,7 @@
         <item name="folderFillColor">#DD3C4043</item> <!-- 87% GM2 800 -->
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="folderTextColor">@android:color/white</item>
+        <item name="folderHintColor">#FFCCCCCC</item>
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
     </style>
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 6059981..7c7e73c 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -52,7 +52,7 @@
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_TEST_PACKAGE := Launcher3
-LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
+LOCAL_INSTRUMENT_SOURCE_DIRS := packages/apps/Launcher3/src
 
 LOCAL_ROBOTEST_TIMEOUT := 36000
 
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index f769055..74ef55e 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.folder;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -61,15 +61,16 @@
         ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
         list.add(mItem1);
         list.add(mItem2);
-        String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
-        assertTrue(suggestedNameOut[0].equals("Work"));
+        FolderNameInfo[] nameInfos =
+                new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        assertEquals("Work", nameInfos[0].getLabel());
 
-        suggestedNameOut[0] = "candidate1";
-        suggestedNameOut[1] = "candidate2";
-        suggestedNameOut[2] = "candidate3";
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
-        assertTrue(suggestedNameOut[3].equals("Work"));
+        nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
+        nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
+        nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        assertEquals("Work", nameInfos[3].getLabel());
 
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index 48b5a45..95a4146 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -49,8 +49,9 @@
     @After
     public void tearDown() {
         // Clear existing logs
-        new File(mTempDir, "log-0").delete();
-        new File(mTempDir, "log-1").delete();
+        for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+            new File(mTempDir, "log-" + i).delete();
+        }
         mTempDir.delete();
 
         mTestActive = false;
@@ -89,8 +90,9 @@
 
         Calendar threeDaysAgo = Calendar.getInstance();
         threeDaysAgo.add(Calendar.HOUR, -72);
-        new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
-        new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
+        for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+            new File(mTempDir, "log-" + i).setLastModified(threeDaysAgo.getTimeInMillis());
+        }
 
         FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
         writer = new StringWriter();
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index f3c5191..217a41c 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
@@ -330,9 +331,7 @@
             return;
         }
         try {
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                TestLogging.recordEvent("start: shortcut: " + packageName);
-            }
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: shortcut", packageName);
             getSystemService(LauncherApps.class).startShortcut(packageName, id, sourceBounds,
                     startActivityOptions, user);
         } catch (SecurityException | IllegalStateException e) {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index dda38b3..9f3b48f 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
+
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
@@ -37,9 +39,12 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.DisplayRotationListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DefaultDisplay.DisplayInfoChangeListener;
+import com.android.launcher3.util.DefaultDisplay.Info;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
@@ -48,7 +53,7 @@
  * Extension of BaseActivity allowing support for drag-n-drop
  */
 public abstract class BaseDraggingActivity extends BaseActivity
-        implements WallpaperColorInfo.OnChangeListener {
+        implements WallpaperColorInfo.OnChangeListener, DisplayInfoChangeListener {
 
     private static final String TAG = "BaseDraggingActivity";
 
@@ -63,8 +68,6 @@
 
     private int mThemeRes = R.style.AppTheme;
 
-    private DisplayRotationListener mRotationListener;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -72,7 +75,7 @@
 
         mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        mRotationListener = new DisplayRotationListener(this, this::onDeviceRotationChanged);
+        DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
 
         // Update theme
         WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
@@ -167,9 +170,7 @@
                 startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
             } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                    TestLogging.recordEvent("start: activity: " + intent);
-                }
+                TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: activity", intent);
                 startActivity(intent, optsBundle);
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
                         Process.myUserHandle(), sourceContainer);
@@ -238,7 +239,7 @@
     protected void onDestroy() {
         super.onDestroy();
         WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
-        mRotationListener.disable();
+        DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
     }
 
     public void runOnceOnStart(Runnable action) {
@@ -251,15 +252,13 @@
 
     protected void onDeviceProfileInitiated() {
         if (mDeviceProfile.isVerticalBarLayout()) {
-            mRotationListener.enable();
             mDeviceProfile.updateIsSeascape(this);
-        } else {
-            mRotationListener.disable();
         }
     }
 
-    private void onDeviceRotationChanged() {
-        if (mDeviceProfile.updateIsSeascape(this)) {
+    @Override
+    public void onDisplayInfoChanged(Info info, int flags) {
+        if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.updateIsSeascape(this)) {
             reapplyUi();
         }
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0d71da4..c049069 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -271,7 +271,7 @@
         // In multi-window mode, we can have widthPx = availableWidthPx
         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
         // widthPx and heightPx values where it's needed.
-        DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
+        DeviceProfile profile = new DeviceProfile(context, inv, null, mwSize, mwSize,
                 mwSize.x, mwSize.y, isLandscape, true);
 
         // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 787eee1..336e423 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -48,6 +48,8 @@
 
     public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
 
+    public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
+
     public int options;
 
     public Intent suggestedFolderNames;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d250658..397a38b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -100,6 +100,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.RotationMode;
@@ -1718,8 +1719,8 @@
     /**
      * Creates and adds new folder to CellLayout
      */
-    public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
-            final int screenId, int cellX, int cellY) {
+    public FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
+            int cellY) {
         final FolderInfo folderInfo = new FolderInfo();
         folderInfo.title = "";
 
@@ -1737,6 +1738,16 @@
     }
 
     /**
+     * Called when a workspace item is converted into a folder
+     */
+    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo){}
+
+    /**
+     * Called when a folder is converted into a workspace item
+     */
+    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {}
+
+    /**
      * Unbinds the view for the specified item, and removes the item and all its children.
      *
      * @param v the view being removed.
@@ -1778,17 +1789,13 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            TestLogging.recordEvent("Key event: " + event);
-        }
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Key event", event);
         return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
     }
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS && ev.getAction() != MotionEvent.ACTION_MOVE) {
-            TestLogging.recordEvent("Touch event: " + ev);
-        }
+        TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
         return super.dispatchTouchEvent(ev);
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index efe85c7..bb6c330 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -96,10 +96,6 @@
     private boolean mModelLoaded;
     public boolean isModelLoaded() {
         synchronized (mLock) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                        "isModelLoaded: " + mModelLoaded + ", " + mLoaderTask);
-            }
             return mModelLoaded && mLoaderTask == null;
         }
     }
@@ -372,9 +368,6 @@
     public boolean stopLoader() {
         synchronized (mLock) {
             LoaderTask oldTask = mLoaderTask;
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "LauncherModel.stopLoader");
-            }
             mLoaderTask = null;
             if (oldTask != null) {
                 oldTask.stopLocked();
@@ -388,10 +381,6 @@
         synchronized (mLock) {
             stopLoader();
             mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                        "LauncherModel.startLoaderForResults " + mLoaderTask);
-            }
 
             // Always post the loader task, instead of running directly (even on same thread) so
             // that we exit any nested synchronized blocks
@@ -493,10 +482,6 @@
         public void close() {
             synchronized (mLock) {
                 // If we are still the last one to be scheduled, remove ourselves.
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                            "LauncherModel.close " + mLoaderTask + ", " + mTask);
-                }
                 if (mLoaderTask == mTask) {
                     mLoaderTask = null;
                 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b725002..b7f8547 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1702,8 +1702,8 @@
             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
             target.removeView(v);
 
-            FolderIcon fi = mLauncher.addFolder(target, sourceInfo, container, screenId,
-                    targetCell[0], targetCell[1]);
+            FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
+                    targetCell[1]);
             destInfo.cellX = -1;
             destInfo.cellY = -1;
             sourceInfo.cellX = -1;
@@ -1722,6 +1722,7 @@
                 fi.addItem(destInfo);
                 fi.addItem(sourceInfo);
             }
+            mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo);
             return true;
         }
         return false;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 1bde138..93bdac9 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,7 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
@@ -185,7 +185,7 @@
     }
 
     public Animator createSpringAnimation(float... progressValues) {
-        if (QUICKSTEP_SPRINGS.get()) {
+        if (UNSTABLE_SPRINGS.get()) {
             return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
                     SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
         }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index cc33965..ea549b9 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -22,6 +22,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.uioverrides.DeviceFlag;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -90,13 +91,13 @@
             "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout");
 
     public static final BooleanFlag FOLDER_NAME_SUGGEST = getDebugFlag(
-            "FOLDER_NAME_SUGGEST", false, "Suggests folder names instead of blank text.");
+            "FOLDER_NAME_SUGGEST", true, "Suggests folder names instead of blank text.");
 
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
             "APP_SEARCH_IMPROVEMENTS", false,
             "Adds localized title and keyword search and ranking");
 
-    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
+    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = new DeviceFlag(
             "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
 
     public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
@@ -106,7 +107,7 @@
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
+    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
             "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
 
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
@@ -145,6 +146,15 @@
         }
     }
 
+    public static void dump(PrintWriter pw) {
+        pw.println("FeatureFlags:");
+        synchronized (sDebugFlags) {
+            for (DebugFlag flag : sDebugFlags) {
+                pw.println("  " + flag.key + "=" + flag.get());
+            }
+        }
+    }
+
     public static class BooleanFlag {
 
         public final String key;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 024c7dd..544efd5 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -89,6 +89,8 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -301,12 +303,12 @@
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 if (TextUtils.isEmpty(mFolderName.getText())) {
-                    String[] suggestedNames =
-                            mInfo.suggestedFolderNames.getStringArrayExtra("suggest");
-                    mFolderName.setText(suggestedNames[0]);
-                    mFolderName.displayCompletions(Arrays.asList(suggestedNames).subList(1,
-                            suggestedNames.length));
-                    mFolderName.setEnteredCompose(false);
+                    FolderNameInfo[] nameInfos =
+                            (FolderNameInfo[]) mInfo.suggestedFolderNames.getParcelableArrayExtra(
+                                    FolderInfo.EXTRA_FOLDER_SUGGESTIONS);
+                    if (nameInfos != null) {
+                        showLabelSuggestion(nameInfos);
+                    }
                 }
             }
             mFolderName.setHint("");
@@ -443,27 +445,53 @@
     }
 
     /**
-     * Show suggested folder title.
+     * Show suggested folder title in FolderEditText, push InputMethodManager suggestions and save
+     * the suggestedFolderNames.
      */
-    public void showSuggestedTitle(String[] suggestName) {
+    public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mInfo.suggestedFolderNames = new Intent().putExtra("suggest", suggestName);
+            mInfo.suggestedFolderNames = new Intent().putExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
+                    nameInfos);
             if (TextUtils.isEmpty(mFolderName.getText().toString())
                     && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
-                if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
-                    mFolderName.setHint("");
-                    mFolderName.setText(suggestName[0]);
-                    mInfo.title = suggestName[0];
-                    animateOpen(mInfo.contents, 0, true);
-                    mFolderName.showKeyboard();
-                    mFolderName.displayCompletions(
-                            Arrays.asList(suggestName).subList(1, suggestName.length));
-                }
+                showLabelSuggestion(nameInfos);
             }
         }
     }
 
     /**
+     * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
+     * InputMethodManager suggestions.
+     */
+    private void showLabelSuggestion(FolderNameInfo[] nameInfos) {
+        if (nameInfos == 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 && !TextUtils.isEmpty(
+                nameInfos[0].getLabel())
+                || nameInfos.length > 1 && nameInfos[1] != null && !TextUtils.isEmpty(
+                nameInfos[1].getLabel());
+        CharSequence firstLabel = nameInfos[0].getLabel();
+
+        if (shouldOpen) {
+            if (!TextUtils.isEmpty(firstLabel)) {
+                mFolderName.setHint("");
+                mFolderName.setText(firstLabel);
+                mInfo.title = firstLabel;
+            }
+            animateOpen(mInfo.contents, 0, true);
+            mFolderName.showKeyboard();
+            mFolderName.displayCompletions(
+                    Arrays.asList(nameInfos).subList(1, nameInfos.length).stream()
+                            .filter(Objects::nonNull)
+                            .map(s -> s.getLabel().toString())
+                            .collect(Collectors.toList()));
+        }
+    }
+
+    /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
      * @param launcher The main activity.
@@ -1109,13 +1137,14 @@
                 int itemCount = getItemCount();
                 if (itemCount <= 1) {
                     View newIcon = null;
+                    WorkspaceItemInfo finalItem = null;
 
                     if (itemCount == 1) {
                         // Move the item from the folder to the workspace, in the position of the
                         // folder
                         CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container,
                                 mInfo.screenId);
-                        WorkspaceItemInfo finalItem = mInfo.contents.remove(0);
+                        finalItem =  mInfo.contents.remove(0);
                         newIcon = mLauncher.createShortcut(cellLayout, finalItem);
                         mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
                                 mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY);
@@ -1136,6 +1165,9 @@
                         // Focus the newly created child
                         newIcon.requestFocus();
                     }
+                    if (finalItem != null) {
+                        mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem);
+                    }
                 }
             }
         };
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6a47b98..ab1ff10 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -393,15 +393,16 @@
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
 
-            String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
+            FolderNameInfo[] nameInfos =
+                    new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 Executors.UI_HELPER_EXECUTOR.post(() -> {
                     d.folderNameProvider.getSuggestedFolderName(
-                            getContext(), mInfo.contents, suggestedNameOut);
-                    showFinalView(finalIndex, item, suggestedNameOut);
+                            getContext(), mInfo.contents, nameInfos);
+                    showFinalView(finalIndex, item, nameInfos);
                 });
             } else {
-                showFinalView(finalIndex, item, suggestedNameOut);
+                showFinalView(finalIndex, item, nameInfos);
             }
         } else {
             addItem(item);
@@ -409,12 +410,12 @@
     }
 
     private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
-            String[] suggestedNameOut) {
+            FolderNameInfo[] nameInfos) {
         postDelayed(() -> {
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
             invalidate();
-            mFolder.showSuggestedTitle(suggestedNameOut);
+            mFolder.showSuggestedTitle(nameInfos);
         }, DROP_IN_ANIMATION_DURATION);
     }
 
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
new file mode 100644
index 0000000..eb9da90
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+    }
+
+    /**
+     * 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;
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 957e636..d5990fa 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -30,6 +30,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
@@ -60,29 +61,33 @@
         return fnp;
     }
 
-    public CharSequence getSuggestedFolderName(Context context,
-            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] candidates) {
+    /**
+     * Generate and rank the suggested Folder names.
+     */
+    public void getSuggestedFolderName(Context context,
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos,
+            FolderNameInfo[] nameInfos) {
 
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(candidates));
+            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
         List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p-> p.user))
+                .filter(distinctByKey(p -> p.user))
                 .collect(Collectors.toList());
 
         if (distinctItemInfos.size() == 1
                 && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
             // Place it as last viable suggestion
-            setAsLastSuggestion(candidates,
+            setAsLastSuggestion(nameInfos,
                     context.getResources().getString(R.string.work_folder_name));
         }
 
         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
         // Then, suggest the package's title as the folder name
         distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p-> p.getTargetComponent() != null
+                .filter(distinctByKey(p -> p.getTargetComponent() != null
                         ? p.getTargetComponent().getPackageName() : ""))
                 .collect(Collectors.toList());
 
@@ -91,44 +96,46 @@
                     .getAppInfoByPackageName(distinctItemInfos.get(0).getTargetComponent()
                             .getPackageName());
             // Place it as first viable suggestion and shift everything else
-            info.ifPresent(i -> setAsFirstSuggestion(candidates, i.title.toString()));
+            info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(candidates));
+            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
-        return candidates[0];
     }
 
-    private void setAsFirstSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
-        if (contains(candidatesOut, candidate)) {
+    private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+        if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
         }
-        for (int i = candidatesOut.length - 1; i > 0; i--) {
-            if (!TextUtils.isEmpty(candidatesOut[i - 1])) {
-                candidatesOut[i] = candidatesOut[i - 1];
+        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];
             }
         }
-        candidatesOut[0] = candidate;
+        nameInfos[0] = new FolderNameInfo(label, 1.0);
     }
 
-    private void setAsLastSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
-        if (contains(candidatesOut, candidate)) {
+    private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+        if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
         }
 
-        for (int i = 0; i < candidate.length(); i++) {
-            if (TextUtils.isEmpty(candidatesOut[i])) {
-                candidatesOut[i] = candidate;
+        for (int i = 0; i < nameInfos.length; i++) {
+            if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
+                nameInfos[i] = new FolderNameInfo(label, 1.0);
                 return;
             }
         }
-        candidatesOut[candidate.length() - 1] = candidate;
+        // Overwrite the last suggestion.
+        int lastIndex = nameInfos.length - 1;
+        nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
     }
 
-    private boolean contains(CharSequence[] list, CharSequence key) {
-        return Arrays.asList(list).stream()
-                .filter(s -> s != null)
-                .anyMatch(s -> s.toString().equalsIgnoreCase(key.toString()));
+    private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
+        return Arrays.stream(nameInfos)
+                .filter(Objects::nonNull)
+                .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
+                        label.toString()));
     }
 
     // This method can be moved to some Utility class location.
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 2c972a0..67f07b1 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -42,7 +42,7 @@
     private static Handler sHandler = null;
     private static File sLogsDirectory = null;
 
-    private static final int LOG_DAYS = 2;
+    public static final int LOG_DAYS = 4;
 
     public static void setDir(File logsDir) {
         if (ENABLED) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8dc7a3a..af802ef 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -59,6 +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.FolderNameProvider;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
@@ -167,32 +168,15 @@
     }
 
     public void run() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "LoaderTask1 " + this);
-        }
         synchronized (this) {
             // Skip fast if we are already stopped.
             if (mStopped) {
                 return;
             }
         }
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "LoaderTask2 " + this);
-        }
 
         Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
-        TimingLogger logger = TestProtocol.sDebugTracing ?
-                new TimingLogger(TAG, "run") {
-                    @Override
-                    public void addSplit(String splitLabel) {
-                        super.addSplit(splitLabel);
-                        Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                                "LoaderTask.addSplit " + splitLabel);
-                    }
-                }
-                : new TimingLogger(TAG, "run");
+        TimingLogger logger = new TimingLogger(TAG, "run");
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             loadWorkspace(allShortcuts);
@@ -283,10 +267,6 @@
             updateHandler.finish();
             logger.addSplit("finish icon update");
 
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                        "LoaderTask3 " + this);
-            }
             transaction.commit();
         } catch (CancellationException e) {
             // Loader stopped, ignore
@@ -925,11 +905,13 @@
 
         synchronized (mBgDataModel) {
             for (int i = 0; i < mBgDataModel.folders.size(); i++) {
-                String[] suggestedOut = new String[FolderNameProvider.SUGGEST_MAX];
+                FolderNameInfo[] suggestionInfos =
+                        new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
                 FolderInfo info = mBgDataModel.folders.valueAt(i);
                 if (info.suggestedFolderNames == null) {
-                    provider.getSuggestedFolderName(mApp.getContext(), info.contents, suggestedOut);
-                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestedOut);
+                    provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+                            suggestionInfos);
+                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
                 }
             }
         }
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index f30470a..a91303a 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -20,7 +20,6 @@
 
 import android.app.AlertDialog;
 import android.app.Dialog;
-import android.app.DialogFragment;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -30,13 +29,14 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
 import com.android.launcher3.R;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.SecureSettingsObserver;
 
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
 /**
  * A {@link Preference} for indicating notification dots status.
  * Also has utility methods for updating UI based on dots status changes.
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 4af5e0a..4e49c6e 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -17,16 +17,22 @@
 
 import static android.graphics.Bitmap.Config.ARGB_8888;
 
+import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import android.annotation.TargetApi;
+import android.app.Activity;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.Insets;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.system.Os;
 import android.util.Log;
 import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.annotation.Keep;
 
@@ -36,14 +42,19 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.LinkedList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
+/**
+ * Class to handle requests from tests
+ */
+@TargetApi(Build.VERSION_CODES.Q)
 public class TestInformationHandler implements ResourceBasedOverride {
 
     public static TestInformationHandler newInstance(Context context) {
@@ -54,7 +65,6 @@
     protected Context mContext;
     protected DeviceProfile mDeviceProfile;
     protected LauncherAppState mLauncherAppState;
-    protected Launcher mLauncher;
     private static LinkedList mLeaks;
 
     public void init(Context context) {
@@ -62,35 +72,31 @@
         mDeviceProfile = InvariantDeviceProfile.INSTANCE.
                 get(context).getDeviceProfile(context);
         mLauncherAppState = LauncherAppState.getInstanceNoCreate();
-        mLauncher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     public Bundle call(String method) {
         final Bundle response = new Bundle();
         switch (method) {
             case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
-                if (mLauncher == null) return null;
-
-                final float progress = LauncherState.OVERVIEW.getVerticalProgress(mLauncher)
-                        - LauncherState.ALL_APPS.getVerticalProgress(mLauncher);
-                final float distance = mLauncher.getAllAppsController().getShiftRange() * progress;
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
-                break;
+                return getLauncherUIProperty(Bundle::putInt, l -> {
+                    final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
+                            - LauncherState.ALL_APPS.getVerticalProgress(l);
+                    final float distance = l.getAllAppsController().getShiftRange() * progress;
+                    return (int) distance;
+                });
             }
 
             case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
-                if (mLauncher == null) return null;
-
-                final float progress = LauncherState.NORMAL.getVerticalProgress(mLauncher)
-                        - LauncherState.ALL_APPS.getVerticalProgress(mLauncher);
-                final float distance = mLauncher.getAllAppsController().getShiftRange() * progress;
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
-                break;
+                return getLauncherUIProperty(Bundle::putInt, l -> {
+                    final float progress = LauncherState.NORMAL.getVerticalProgress(l)
+                            - LauncherState.ALL_APPS.getVerticalProgress(l);
+                    final float distance = l.getAllAppsController().getShiftRange() * progress;
+                    return (int) distance;
+                });
             }
 
             case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
-                break;
+                return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
             }
 
             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
@@ -102,40 +108,33 @@
                 break;
 
             case TestProtocol.REQUEST_FREEZE_APP_LIST:
-                MAIN_EXECUTOR.execute(() ->
-                        mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
-                                AllAppsStore.DEFER_UPDATES_TEST));
-                break;
-
+                return getLauncherUIProperty(Bundle::putBoolean, l -> {
+                    l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+                    return true;
+                });
             case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
-                MAIN_EXECUTOR.execute(() ->
-                        mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
-                                AllAppsStore.DEFER_UPDATES_TEST));
-                break;
+                return getLauncherUIProperty(Bundle::putBoolean, l -> {
+                    l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+                    return true;
+                });
 
             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
-                try {
-                    final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
-                            mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                            deferUpdatesFlags);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                break;
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
             }
 
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
-                try {
-                    final int scroll = MAIN_EXECUTOR.submit(() ->
-                            mLauncher.getAppsView().getActiveRecyclerView().getCurrentScrollY())
-                            .get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                            scroll);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                break;
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
+            }
+
+            case TestProtocol.REQUEST_WINDOW_INSETS: {
+                return getUIProperty(Bundle::putParcelable, a -> {
+                    WindowInsets insets = a.getWindow()
+                            .getDecorView().getRootWindowInsets();
+                    return Insets.max(
+                            insets.getSystemGestureInsets(), insets.getSystemWindowInsets());
+                }, this::getCurrentActivity);
             }
 
             case TestProtocol.REQUEST_PID: {
@@ -176,7 +175,6 @@
 
             case TestProtocol.REQUEST_VIEW_LEAK: {
                 if (mLeaks == null) mLeaks = new LinkedList();
-
                 mLeaks.add(new View(mContext));
                 break;
             }
@@ -191,15 +189,14 @@
     }
 
     protected boolean isLauncherInitialized() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "isLauncherInitialized " + Launcher.ACTIVITY_TRACKER.getCreatedActivity() + ", "
-                            + LauncherAppState.getInstance(mContext).getModel().isModelLoaded());
-        }
         return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
                 || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
     }
 
+    protected Activity getCurrentActivity() {
+        return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+    }
+
     private static void runGcAndFinalizersSync() {
         Runtime.getRuntime().gc();
         Runtime.getRuntime().runFinalization();
@@ -216,6 +213,47 @@
         }
     }
 
+    /**
+     * Returns the result by getting a Launcher property on UI thread
+     */
+    public static <T> Bundle getLauncherUIProperty(
+            BundleSetter<T> bundleSetter, Function<Launcher, T> provider) {
+        return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedActivity);
+    }
+
+    /**
+     * Returns the result by getting a generic property on UI thread
+     */
+    private static <S, T> Bundle getUIProperty(
+            BundleSetter<T> bundleSetter, Function<S, T> provider, Supplier<S> targetSupplier) {
+        try {
+            return MAIN_EXECUTOR.submit(() -> {
+                S target = targetSupplier.get();
+                if (target == null) {
+                    return null;
+                }
+                T value = provider.apply(target);
+                Bundle response = new Bundle();
+                bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value);
+                return response;
+            }).get();
+        } catch (ExecutionException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Generic interface for setting a fiend in bundle
+     * @param <T> the type of value being set
+     */
+    public interface BundleSetter<T> {
+
+        /**
+         * Sets any generic property to the bundle
+         */
+        void set(Bundle b, String key, T value);
+    }
+
     // Create the observer in the scope of a method to minimize the chance that
     // it remains live in a DEX/machine register at the point of the fence guard.
     // This must be kept to avoid R8 inlining it.
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index fd066c1..d522d81 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -17,13 +17,30 @@
 package com.android.launcher3.testing;
 
 import android.util.Log;
+import android.view.MotionEvent;
 
 import com.android.launcher3.Utilities;
 
 public final class TestLogging {
-    public static void recordEvent(String event) {
+    private static void recordEventSlow(String sequence, String event) {
+        Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+    }
+
+    public static void recordEvent(String sequence, String event) {
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.TAPL_EVENTS_TAG, event);
+            recordEventSlow(sequence, event);
+        }
+    }
+
+    public static void recordEvent(String sequence, String message, Object parameter) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            recordEventSlow(sequence, message + ": " + parameter);
+        }
+    }
+
+    public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
+            recordEventSlow(sequence, message + ": " + event);
         }
     }
 }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 2f053c9..f995c61 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -33,6 +33,8 @@
     public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
     public static final int HINT_STATE_ORDINAL = 7;
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
+    public static final String SEQUENCE_MAIN = "Main";
+    public static final String SEQUENCE_TIS = "TIS";
 
     public static String stateOrdinalToString(int ordinal) {
         switch (ordinal) {
@@ -75,8 +77,7 @@
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
     public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
-    public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
-    public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
+    public static final String REQUEST_WINDOW_INSETS = "window-insets";
     public static final String REQUEST_PID = "pid";
     public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
     public static final String REQUEST_JAVA_LEAK = "java-leak";
@@ -92,5 +93,4 @@
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String APP_NOT_DISABLED = "b/139891609";
-    public static final String LAUNCHER_DIDNT_INITIALIZE = "b/148313079";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 7ae0526..9df6241 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -23,7 +23,7 @@
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
@@ -435,7 +435,7 @@
         updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
                 targetState, velocity, fling);
         mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, progressVelocity);
-        if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
+        if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
         anim.start();
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index b83c8fc..499f655 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -45,13 +45,7 @@
     }
 
     public void onActivityDestroyed(T activity) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed");
-        }
         if (mCurrentActivity.get() == activity) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed: clear");
-            }
             mCurrentActivity.clear();
         }
     }
@@ -116,10 +110,6 @@
     }
 
     public boolean handleCreate(T activity) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "ActivityTracker.handleCreate " + mCurrentActivity.get() + " => " + activity);
-        }
         mCurrentActivity = new WeakReference<>(activity);
         return handleIntent(activity, activity.getIntent(), false, false);
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java
deleted file mode 100644
index b1a67e9..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 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.uioverrides;
-
-import android.content.Context;
-import android.view.OrientationEventListener;
-
-/**
- * Utility class for listening for rotation changes
- */
-public class DisplayRotationListener extends OrientationEventListener {
-
-    private final Runnable mCallback;
-
-    public DisplayRotationListener(Context context, Runnable callback) {
-        super(context);
-        mCallback = callback;
-    }
-
-    @Override
-    public void onOrientationChanged(int i) {
-        mCallback.run();
-    }
-}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4b72882..54caf1e 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,9 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -39,7 +36,6 @@
 import com.android.launcher3.tapl.AppIconMenuItem;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.WidgetsRecyclerView;
@@ -117,9 +113,7 @@
         mLauncher.pressHome();
     }
 
-    // b/146432215: remove @Stability after 2/1/2020 if this test doesn't flake
     @Test
-    @Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void testOpenHomeSettingsFromWorkspace() {
         mDevice.pressMenu();
         mDevice.waitForIdle();
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index 6445501..5880eb6 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -87,6 +87,11 @@
             return 145935261;
         }
 
+        if (matches("java\\.lang\\.AssertionError\\: http\\:\\/\\/go\\/tapl \\: want to get "
+                + "workspace object; Presence of recents button doesn't match the interaction "
+                + "mode, mode\\=ZERO_BUTTON, hasRecents\\=true", exception)) {
+            return 148422894;
+        }
 
         final String logSinceBoot;
         try {
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 02d07bb..cdda0f0 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -8,7 +8,6 @@
 
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -16,7 +15,6 @@
 
 public class FailureWatcher extends TestWatcher {
     private static final String TAG = "FailureWatcher";
-    private static boolean sHadFailedTestDeinitialization;
     final private UiDevice mDevice;
 
     public FailureWatcher(UiDevice device) {
@@ -62,35 +60,4 @@
 
         device.takeScreenshot(new File(pathname));
     }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (sHadFailedTestDeinitialization) {
-                    Log.d(TAG, "Skipping due to a recent test deinitialization failure: " +
-                            description.getDisplayName());
-                    return;
-                }
-
-                try {
-                    base.evaluate();
-                } catch (Throwable e) {
-                    final String stackTrace = Log.getStackTraceString(e);
-                    if (!stackTrace.contains(
-                            "androidx.test.internal.runner.junit4.statement.RunBefores.evaluate")) {
-                        // Test failed to deinitialize. Since the global state is probably
-                        // corrupted, won't execute other tests.
-                        Log.d(TAG,
-                                "Detected an exception from test finalizer, will skip further "
-                                        + "tests: " + stackTrace);
-                        sHadFailedTestDeinitialization = true;
-                    }
-                    throw e;
-                }
-            }
-        };
-    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index afb50e0..468f54c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -21,6 +21,8 @@
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 public class AddToHomeScreenPrompt {
@@ -38,6 +40,13 @@
 
     public void addAutomatically() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            if (mLauncher.getNavigationModel()
+                    != LauncherInstrumentation.NavigationModel.THREE_BUTTON) {
+                mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+                mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+            }
             mLauncher.waitForObjectInContainer(
                     mWidgetCell.getParent().getParent().getParent().getParent(),
                     By.text(ADD_AUTOMATICALLY)).click();
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 3f814fd..8932291 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -22,6 +22,8 @@
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 /**
@@ -56,6 +58,6 @@
 
     @Override
     protected void expectActivityStartEvents() {
-        mLauncher.expectEvent(START_EVENT);
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_EVENT);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index fadfd9f..f8dd89c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -18,6 +18,8 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 /**
@@ -45,6 +47,6 @@
 
     @Override
     protected void expectActivityStartEvents() {
-        mLauncher.expectEvent(START_SHORTCUT_EVENT);
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_SHORTCUT_EVENT);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 9f29a1a..2acab97 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -131,7 +131,7 @@
             }
 
             case THREE_BUTTON:
-                mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.runToState(
                         () -> mLauncher.waitForSystemUiObject("recent_apps").click(),
                         OVERVIEW_STATE_ORDINAL);
@@ -195,14 +195,14 @@
             case THREE_BUTTON:
                 // Double press the recents button.
                 UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
-                mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
                 mLauncher.getOverview();
-                mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 recentsButton.click();
                 break;
         }
-        mLauncher.expectEvent(TASK_START_EVENT);
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
     }
 
     protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index e5c83e2..a769acf 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -23,8 +23,6 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
-
 import java.util.Collections;
 import java.util.List;
 
@@ -58,9 +56,7 @@
                      mLauncher.addContextLayer("want to fling forward in overview")) {
             LauncherInstrumentation.log("Overview.flingForward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            final int leftMargin = mLauncher.getTestInfo(
-                    TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            final int leftMargin = mLauncher.getTargetInsets().left;
             mLauncher.scroll(
                     overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false);
             verifyActiveContainer();
@@ -96,9 +92,7 @@
                      mLauncher.addContextLayer("want to fling backward in overview")) {
             LauncherInstrumentation.log("Overview.flingBackward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            final int rightMargin = mLauncher.getTestInfo(
-                    TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            final int rightMargin = mLauncher.getTargetInsets().right;
             mLauncher.scroll(
                     overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
             verifyActiveContainer();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 83e1b38..abd0f24 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
@@ -75,8 +76,10 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -101,13 +104,17 @@
     static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
             "(?<time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] "
                     + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
-                    + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<event>.*)");
+                    + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / "
+                    + "(?<event>.*)");
 
     private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
     private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
 
+    static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
+    static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
+
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
     public enum ContainerType {
@@ -117,7 +124,7 @@
     public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
 
     // Where the gesture happens: outside of Launcher, inside or from inside to outside.
-    enum GestureScope {
+    public enum GestureScope {
         OUTSIDE, INSIDE, INSIDE_TO_OUTSIDE
     }
 
@@ -146,7 +153,7 @@
         }
     }
 
-    interface Closable extends AutoCloseable {
+    public interface Closable extends AutoCloseable {
         void close();
     }
 
@@ -169,19 +176,27 @@
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
+    // Map from an event sequence name to an ordered list of expected events in that sequence.
     // Not null when we are collecting expected events to compare with actual ones.
-    private List<Pattern> mExpectedEvents;
+    private Map<String, List<Pattern>> mExpectedEvents;
 
     private Date mStartRecordingTime;
     private boolean mCheckEventsForSuccessfulGestures = false;
 
-    private static Pattern getTouchEventPattern(String action) {
+    private static Pattern getTouchEventPattern(String prefix, String action) {
         // The pattern includes sanity checks that we don't get a multi-touch events or other
         // surprises.
         return Pattern.compile(
-                "Touch event: MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
-                        +
-                        ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
+                prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
+                        + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
+    }
+
+    private static Pattern getTouchEventPattern(String action) {
+        return getTouchEventPattern("Touch event", action);
+    }
+
+    private static Pattern getTouchEventPatternTIS(String action) {
+        return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
     }
 
     /**
@@ -257,6 +272,11 @@
         return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
     }
 
+    Insets getTargetInsets() {
+        return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS)
+                .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     void setActiveContainer(VisibleContainer container) {
         sActiveContainer = new WeakReference<>(container);
     }
@@ -490,6 +510,12 @@
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
 
+        // b/148422894
+        for (int i = 0; i != 600; ++i) {
+            if (getNavigationModeMismatchError() == null) break;
+            sleep(100);
+        }
+
         final String error = getNavigationModeMismatchError();
         assertTrue(error, error == null);
         log("verifyContainerType: " + containerType);
@@ -640,10 +666,14 @@
                         if (hasLauncherObject(CONTEXT_MENU_RES_ID) ||
                                 hasLauncherObject(WIDGETS_RES_ID)
                                         && !mDevice.isNaturalOrientation()) {
-                            expectEvent(EVENT_PILFER_POINTERS);
+                            expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
                         }
                     }
 
+                    if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
+                        expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+                        expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+                    }
                     runToState(
                             waitForSystemUiObject("home")::click,
                             NORMAL_STATE_ORDINAL,
@@ -923,8 +953,12 @@
     }
 
     void clickLauncherObject(UiObject2 object) {
-        expectEvent(LauncherInstrumentation.EVENT_TOUCH_DOWN);
-        expectEvent(LauncherInstrumentation.EVENT_TOUCH_UP);
+        expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_DOWN);
+        expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_UP);
+        if (getNavigationModel() != NavigationModel.THREE_BUTTON) {
+            expectEvent(TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+            expectEvent(TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+        }
         object.click();
     }
 
@@ -1008,7 +1042,8 @@
 
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
-    void linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown,
+    public void linearGesture(int startX, int startY, int endX, int endY, int steps,
+            boolean slowDown,
             GestureScope gestureScope) {
         log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
         final long downTime = SystemClock.uptimeMillis();
@@ -1060,22 +1095,28 @@
                 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
     }
 
-    void sendPointer(long downTime, long currentTime, int action, Point point,
+    public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (gestureScope != GestureScope.OUTSIDE) {
-                    expectEvent(EVENT_TOUCH_DOWN);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
+                }
+                if (getNavigationModel() != NavigationModel.THREE_BUTTON) {
+                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                 }
                 break;
             case MotionEvent.ACTION_UP:
                 if (gestureScope != GestureScope.INSIDE) {
-                    expectEvent(EVENT_PILFER_POINTERS);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
                 }
                 if (gestureScope != GestureScope.OUTSIDE) {
-                    expectEvent(gestureScope == GestureScope.INSIDE
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
                             ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
                 }
+                if (getNavigationModel() != NavigationModel.THREE_BUTTON) {
+                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+                }
                 break;
         }
 
@@ -1084,7 +1125,7 @@
         event.recycle();
     }
 
-    long movePointer(long downTime, long startTime, long duration, Point from, Point to,
+    public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
             GestureScope gestureScope) {
         log("movePointer: " + from + " to " + to);
         final Point point = new Point();
@@ -1204,12 +1245,18 @@
         return tasks;
     }
 
-    private List<String> getEvents() {
-        final ArrayList<String> events = new ArrayList<>();
+    // Returns actual events retrieved from logcat. The return value's key set is the set of all
+    // sequence names that actually had at least one event, and the values are lists of events in
+    // the given sequence, in the order they were recorded.
+    private Map<String, List<String>> getEvents() {
+        final Map<String, List<String>> events = new HashMap<>();
         try {
+            // Logcat may skip events after the specified time. Querying for events starting 1 sec
+            // earlier.
+            final Date startTime = new Date(mStartRecordingTime.getTime() - 10000);
             final String logcatEvents = mDevice.executeShellCommand(
                     "logcat -d -v year --pid=" + getPid() + " -t "
-                            + DATE_TIME_FORMAT.format(mStartRecordingTime).replaceAll(" ", "")
+                            + DATE_TIME_FORMAT.format(startTime).replaceAll(" ", "")
                             + " -s " + TestProtocol.TAPL_EVENTS_TAG);
             final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
             while (matcher.find()) {
@@ -1219,7 +1266,8 @@
                     continue;
                 }
 
-                events.add(matcher.group("event"));
+                eventsListForSequence(matcher.group("sequence"), events).add(
+                        matcher.group("event"));
             }
             return events;
         } catch (IOException e) {
@@ -1229,9 +1277,20 @@
         }
     }
 
+    // Returns an event list for a given sequence, adding it to the map as needed.
+    private static <T> List<T> eventsListForSequence(
+            String sequenceName, Map<String, List<T>> events) {
+        List<T> eventSequence = events.get(sequenceName);
+        if (eventSequence == null) {
+            eventSequence = new ArrayList<>();
+            events.put(sequenceName, eventSequence);
+        }
+        return eventSequence;
+    }
+
     private void startRecordingEvents() {
         Assert.assertTrue("Already recording events", mExpectedEvents == null);
-        mExpectedEvents = new ArrayList<>();
+        mExpectedEvents = new HashMap<>();
         mStartRecordingTime = new Date();
         log("startRecordingEvents: " + DATE_TIME_FORMAT.format(mStartRecordingTime));
     }
@@ -1241,7 +1300,7 @@
         mStartRecordingTime = null;
     }
 
-    Closable eventsCheck() {
+    public Closable eventsCheck() {
         if ("com.android.launcher3".equals(getLauncherPackageName())) {
             // Not checking specific Launcher3 event sequences.
             return () -> {
@@ -1265,65 +1324,124 @@
             final String message = getEventMismatchMessage(true);
             if (message != null) {
                 Assert.fail(formatSystemHealthMessage(
-                        "http://go/tapl : unexpected event sequence: " + message));
+                        "http://go/tapl : successful gesture produced " + message));
             }
         };
     }
 
-    void expectEvent(Pattern expected) {
-        if (mExpectedEvents != null) mExpectedEvents.add(expected);
+    void expectEvent(String sequence, Pattern expected) {
+        if (mExpectedEvents != null) {
+            eventsListForSequence(sequence, mExpectedEvents).add(expected);
+        }
     }
 
+    // Returns non-null error message if the actual events in logcat don't match expected events.
+    // If we are not checking events, returns null.
     private String getEventMismatchMessage(boolean waitForExpectedCount) {
         if (mExpectedEvents == null) return null;
 
         try {
-            List<String> actual = getEvents();
+            Map<String, List<String>> actual = getEvents();
 
             if (waitForExpectedCount) {
                 // Wait until Launcher generates the expected number of events.
                 final long endTime = SystemClock.uptimeMillis() + WAIT_TIME_MS;
                 while (SystemClock.uptimeMillis() < endTime
-                        && actual.size() < mExpectedEvents.size()) {
+                        && !receivedEnoughEvents(actual)) {
                     SystemClock.sleep(100);
                     actual = getEvents();
                 }
             }
 
-            for (int i = 0; i < mExpectedEvents.size(); ++i) {
-                if (i >= actual.size()) {
-                    return formatEventMismatchMessage("too few actual events", actual, i);
-                }
-                if (!mExpectedEvents.get(i).matcher(actual.get(i)).find()) {
-                    return formatEventMismatchMessage("a mismatched event", actual, i);
-                }
-            }
-
-            if (actual.size() > mExpectedEvents.size()) {
-                return formatEventMismatchMessage(
-                        "too many actual events", actual, mExpectedEvents.size());
-            }
+            return getEventMismatchErrorMessage(actual);
         } finally {
             stopRecordingEvents();
         }
-
-        return null;
     }
 
-    private String formatEventList(List events, int position) {
+    // Returns whether there is a sufficient number of events in the logcat to match the expected
+    // events.
+    private boolean receivedEnoughEvents(Map<String, List<String>> actual) {
+        for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
+            final List<String> actualEventSequence = actual.get(expectedNamedSequence.getKey());
+            if (actualEventSequence == null
+                    || actualEventSequence.size() < expectedNamedSequence.getValue().size()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // If the list of actual events matches the list of expected events, returns -1, otherwise
+    // the position of the mismatch.
+    private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
+        for (int i = 0; i < expected.size(); ++i) {
+            if (i >= actual.size()
+                    || !expected.get(i).matcher(actual.get(i)).find()) {
+                return i;
+            }
+        }
+
+        if (actual.size() > expected.size()) return expected.size();
+
+        return -1;
+    }
+
+    // Returns non-null error message if the actual events passed as a param don't match expected
+    // events.
+    private String getEventMismatchErrorMessage(Map<String, List<String>> actualEvents) {
         final StringBuilder sb = new StringBuilder();
+
+        // Check that all expected even sequences match the actual data.
+        for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
+            List<String> actualEventSequence = actualEvents.get(expectedNamedSequence.getKey());
+            if (actualEventSequence == null) actualEventSequence = new ArrayList<>();
+            final int mismatchPosition = getMismatchPosition(
+                    expectedNamedSequence.getValue(), actualEventSequence);
+            if (mismatchPosition != -1) {
+                formatSequenceWithMismatch(
+                        sb,
+                        expectedNamedSequence.getKey(),
+                        expectedNamedSequence.getValue(),
+                        actualEventSequence,
+                        mismatchPosition);
+            }
+        }
+
+        // Check for unexpected event sequences in the actual data.
+        for (Map.Entry<String, List<String>> actualNamedSequence : actualEvents.entrySet()) {
+            if (!mExpectedEvents.containsKey(actualNamedSequence.getKey())) {
+                formatSequenceWithMismatch(
+                        sb,
+                        actualNamedSequence.getKey(),
+                        new ArrayList<>(),
+                        actualNamedSequence.getValue(),
+                        0);
+            }
+        }
+
+        return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+    }
+
+    private static void formatSequenceWithMismatch(
+            StringBuilder sb,
+            String sequenceName,
+            List<Pattern> expected,
+            List<String> actualEvents,
+            int mismatchPosition) {
+        sb.append("\n>> Sequence " + sequenceName);
+        sb.append("\n  Expected:");
+        formatEventListWithMismatch(sb, expected, mismatchPosition);
+        sb.append("\n  Actual:");
+        formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
+    }
+
+    private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
         for (int i = 0; i < events.size(); ++i) {
-            sb.append("\n| ");
+            sb.append("\n  | ");
             sb.append(i == position ? "---> " : "     ");
             sb.append(events.get(i).toString());
         }
-        if (position == events.size()) sb.append("\n| ---> (end)");
-        return sb.toString();
-    }
-
-    private String formatEventMismatchMessage(String message, List<String> actual, int position) {
-        return message + ":"
-                + "\nExpected:" + formatEventList(mExpectedEvents, position)
-                + "\nActual:" + formatEventList(actual, position);
+        if (position == events.size()) sb.append("\n  | ---> (end)");
     }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 410e5a1..f955cf2 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -22,6 +22,8 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 /**
@@ -76,7 +78,7 @@
                         event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
                         () -> "Launching task didn't open a new window: "
                                 + mTask.getParent().getContentDescription());
-                mLauncher.expectEvent(TASK_START_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
             }
             return new Background(mLauncher);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index a0d5443..3f5dc8d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -260,8 +260,8 @@
     public Widgets openAllWidgets() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             verifyActiveContainer();
-            mLauncher.expectEvent(EVENT_CTRL_W_DOWN);
-            mLauncher.expectEvent(EVENT_CTRL_W_UP);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_UP);
             mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {
                 return new Widgets(mLauncher);