Merge "Allow shortcut predictions in hybrid hotseat" into ub-launcher3-rvc-dev
diff --git a/Android.mk b/Android.mk
index fcd4a94..7805b32 100644
--- a/Android.mk
+++ b/Android.mk
@@ -67,7 +67,10 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, src_shortcuts_overrides) \
-    $(call all-java-files-under, src_ui_overrides)
+    $(call all-java-files-under, src_ui_overrides) \
+    $(call all-java-files-under, ext_tests/src)
+    
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 # Proguard is disable for testing. Derivarive prjects to keep proguard enabled
diff --git a/ext_tests/res/values/overrides.xml b/ext_tests/res/values/overrides.xml
new file mode 100644
index 0000000..3f071d4
--- /dev/null
+++ b/ext_tests/res/values/overrides.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="test_information_handler_class" translatable="false">com.android.launcher3.testing.DebugTestInformationHandler</string>
+</resources>
+
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
new file mode 100644
index 0000000..b84c7aa
--- /dev/null
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -0,0 +1,141 @@
+/*
+ * 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.testing;
+
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Debug;
+import android.system.Os;
+import android.view.View;
+
+import androidx.annotation.Keep;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to handle requests from tests, including debug ones.
+ */
+public class DebugTestInformationHandler extends TestInformationHandler {
+    private static LinkedList sLeaks;
+
+    public DebugTestInformationHandler(Context context) {
+        init(context);
+    }
+
+    private static void runGcAndFinalizersSync() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+
+        final CountDownLatch fence = new CountDownLatch(1);
+        createFinalizationObserver(fence);
+        try {
+            do {
+                Runtime.getRuntime().gc();
+                Runtime.getRuntime().runFinalization();
+            } while (!fence.await(100, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    // 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.
+    @Keep
+    private static void createFinalizationObserver(CountDownLatch fence) {
+        new Object() {
+            @Override
+            protected void finalize() throws Throwable {
+                try {
+                    fence.countDown();
+                } finally {
+                    super.finalize();
+                }
+            }
+        };
+    }
+
+    @Override
+    public Bundle call(String method) {
+        final Bundle response = new Bundle();
+        switch (method) {
+            case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
+            }
+
+            case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
+                TestProtocol.sDebugTracing = true;
+                return response;
+
+            case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
+                TestProtocol.sDebugTracing = false;
+                return response;
+
+            case TestProtocol.REQUEST_PID: {
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
+                return response;
+            }
+
+            case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+                runGcAndFinalizersSync();
+                Debug.MemoryInfo mem = new Debug.MemoryInfo();
+                Debug.getMemoryInfo(mem);
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
+                return response;
+            }
+
+            case TestProtocol.REQUEST_JAVA_LEAK: {
+                if (sLeaks == null) sLeaks = new LinkedList();
+
+                // Allocate and dirty the memory.
+                final int leakSize = 1024 * 1024;
+                final byte[] bytes = new byte[leakSize];
+                for (int i = 0; i < leakSize; i += 239) {
+                    bytes[i] = (byte) (i % 256);
+                }
+                sLeaks.add(bytes);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_NATIVE_LEAK: {
+                if (sLeaks == null) sLeaks = new LinkedList();
+
+                // Allocate and dirty a bitmap.
+                final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
+                bitmap.eraseColor(Color.RED);
+                sLeaks.add(bitmap);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_VIEW_LEAK: {
+                if (sLeaks == null) sLeaks = new LinkedList();
+                sLeaks.add(new View(mContext));
+                return response;
+            }
+
+            default:
+                return super.call(method);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
index 7b3e378..cd64a94 100644
--- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
+++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
@@ -13,25 +13,30 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.fallback.RecentsRootView
+<com.android.launcher3.LauncherRootView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/drag_layer"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:clipChildren="false"
     android:fitsSystemWindows="true">
 
-    <com.android.quickstep.fallback.FallbackRecentsView
-        android:id="@id/overview_panel"
+    <com.android.quickstep.fallback.RecentsDragLayer
+        android:id="@+id/drag_layer"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:outlineProvider="none"
-        android:theme="@style/HomeScreenElementTheme" />
+        android:clipChildren="false">
 
-    <include
-        android:id="@+id/overview_actions_view"
-        layout="@layout/overview_actions_container" />
+        <com.android.quickstep.fallback.FallbackRecentsView
+            android:id="@id/overview_panel"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:outlineProvider="none"
+            android:theme="@style/HomeScreenElementTheme" />
 
-</com.android.quickstep.fallback.RecentsRootView>
+        <include
+            android:id="@+id/overview_actions_view"
+            layout="@layout/overview_actions_container" />
+
+    </com.android.quickstep.fallback.RecentsDragLayer>
+</com.android.launcher3.LauncherRootView>
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index f5c5874..02bae64 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -205,26 +205,30 @@
         mRecentsAnimationController = recentsAnimationController;
         mRecentsAnimationTargets = targets;
         mTransformParams.setTargetSet(mRecentsAnimationTargets);
-        DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
         RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
                 mGestureState.getRunningTaskId());
 
-        if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
-            Rect overviewStackBounds = mActivityInterface
-                    .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
-            dp = dp.getMultiWindowProfile(mContext,
-                    new WindowBounds(overviewStackBounds, targets.homeContentInsets));
-        } else {
-            // If we are not in multi-window mode, home insets should be same as system insets.
-            dp = dp.copy(mContext);
-        }
-        dp.updateInsets(targets.homeContentInsets);
-        dp.updateIsSeascape(mContext);
         if (runningTaskTarget != null) {
             mTaskViewSimulator.setPreview(runningTaskTarget);
         }
 
-        initTransitionEndpoints(dp);
+        // Only initialize the device profile, if it has not been initialized before, as in some
+        // configurations targets.homeContentInsets may not be correct.
+        if (mActivity == null) {
+            DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+            if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+                Rect overviewStackBounds = mActivityInterface
+                        .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+                dp = dp.getMultiWindowProfile(mContext,
+                        new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+            } else {
+                // If we are not in multi-window mode, home insets should be same as system insets.
+                dp = dp.copy(mContext);
+            }
+            dp.updateInsets(targets.homeContentInsets);
+            dp.updateIsSeascape(mContext);
+            initTransitionEndpoints(dp);
+        }
 
         // Notify when the animation starts
         if (!mRecentsAnimationStartCallbacks.isEmpty()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
index e49c466..ba8ba33 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
@@ -17,6 +17,7 @@
 package com.android.quickstep;
 
 import static android.content.Intent.EXTRA_STREAM;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.ImageActionUtils.persistBitmapAndStartActivity;
@@ -67,7 +68,9 @@
 
         UI_HELPER_EXECUTOR.execute(() -> persistBitmapAndStartActivity(mContext,
                 mBitmapSupplier.get(), crop, intent, (uri, intentForUri) -> {
-                    intentForUri.putExtra(EXTRA_STREAM, uri);
+                    intentForUri
+                            .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
+                            .putExtra(EXTRA_STREAM, uri);
                     return new Intent[]{intentForUri};
                 }, TAG));
 
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 ebc83c6..6f4d34c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -4,21 +4,18 @@
 import android.content.Context;
 import android.os.Bundle;
 
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
 
-    private final Context mContext;
+    protected final Context mContext;
+
     public QuickstepTestInformationHandler(Context context) {
         mContext = context;
     }
@@ -27,6 +24,15 @@
     public Bundle call(String method) {
         final Bundle response = new Bundle();
         switch (method) {
+            case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
+                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_OVERVIEW_SWIPE_HEIGHT: {
                 final float swipeHeight =
                         LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
@@ -47,26 +53,6 @@
                         Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
             }
 
-            case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
-                ArrayList<String> taskBaseIntentComponents = new ArrayList<>();
-                CountDownLatch latch = new CountDownLatch(1);
-                RecentsModel.INSTANCE.get(mContext).getTasks((tasks) -> {
-                    for (Task t : tasks) {
-                        taskBaseIntentComponents.add(
-                                t.key.baseIntent.getComponent().flattenToString());
-                    }
-                    latch.countDown();
-                });
-                try {
-                    latch.await(2, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                response.putStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        taskBaseIntentComponents);
-                return response;
-            }
-
             case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                         FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 1701020..6f2f9fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -55,7 +55,7 @@
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.fallback.FallbackRecentsStateController;
 import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.fallback.RecentsRootView;
+import com.android.quickstep.fallback.RecentsDragLayer;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.views.OverviewActionsView;
@@ -78,7 +78,8 @@
             new ActivityTracker<>();
 
     private Handler mUiHandler = new Handler(Looper.getMainLooper());
-    private RecentsRootView mRecentsRootView;
+
+    private RecentsDragLayer mDragLayer;
     private FallbackRecentsView mFallbackRecentsView;
     private OverviewActionsView mActionsView;
 
@@ -89,13 +90,14 @@
     /**
      * Init drag layer and overview panel views.
      */
-    protected void initViews() {
-        setContentView(R.layout.fallback_recents_activity);
-        mRecentsRootView = findViewById(R.id.drag_layer);
+    protected void setupViews() {
+        inflateRootView(R.layout.fallback_recents_activity);
+        setContentView(getRootView());
+        mDragLayer = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
         mActionsView = findViewById(R.id.overview_actions_view);
 
-        mRecentsRootView.recreateControllers();
+        mDragLayer.recreateControllers();
         mFallbackRecentsView.init(mActionsView);
     }
 
@@ -105,12 +107,6 @@
         super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
     }
 
-    public void onRootViewSizeChanged() {
-        if (isInMultiWindowMode()) {
-            onHandleConfigChanged();
-        }
-    }
-
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
@@ -130,7 +126,7 @@
         dispatchDeviceProfileChanged();
 
         reapplyUi();
-        mRecentsRootView.recreateControllers();
+        mDragLayer.recreateControllers();
     }
 
     /**
@@ -142,19 +138,14 @@
 
         // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
         // activity.
-        return (mRecentsRootView != null) && isInMultiWindowMode()
+        return (mDragLayer != null) && isInMultiWindowMode()
                 ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize())
                 : dp.copy(this);
     }
 
     @Override
     public BaseDragLayer getDragLayer() {
-        return mRecentsRootView;
-    }
-
-    @Override
-    public View getRootView() {
-        return mRecentsRootView;
+        return mDragLayer;
     }
 
     @Override
@@ -252,7 +243,7 @@
 
         mOldConfig = new Configuration(getResources().getConfiguration());
         initDeviceProfile();
-        initViews();
+        setupViews();
 
         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 9242771..d20bbe9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -107,11 +107,22 @@
     }
 
     @Override
+    protected boolean shouldAddDummyTaskView(int runningTaskId) {
+        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+                && getTaskViewCount() == 0) {
+            // Do not add a dummy task if we are running over home with empty recents, so that we
+            // show the empty recents message instead of showing a dummy task and later removing it.
+            return false;
+        }
+        return super.shouldAddDummyTaskView(runningTaskId);
+    }
+
+    @Override
     protected void applyLoadPlan(ArrayList<Task> tasks) {
         // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
         // track the index of the next task appropriately, as if we are switching on any other app.
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId) {
+        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
             // Check if the task list has running task
             boolean found = false;
             for (Task t : tasks) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
new file mode 100644
index 0000000..a00015a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.quickstep.fallback;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.RecentsActivity;
+
+/**
+ * Drag layer for fallback recents activity
+ */
+public class RecentsDragLayer extends BaseDragLayer<RecentsActivity> {
+
+    public RecentsDragLayer(Context context, AttributeSet attrs) {
+        super(context, attrs, 1 /* alphaChannelCount */);
+    }
+
+    @Override
+    public void recreateControllers() {
+        mControllers = new TouchController[] {
+                new RecentsTaskController(mActivity),
+                new FallbackNavBarTouchController(mActivity),
+        };
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        super.setInsets(insets);
+        setBackground(insets.top == 0  || !mAllowSysuiScrims
+                ? null
+                : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
deleted file mode 100644
index 7f5ec9b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ /dev/null
@@ -1,93 +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.quickstep.fallback;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsActivity;
-
-public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
-
-    private static final int MIN_SIZE = 10;
-
-    private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
-
-    public RecentsRootView(Context context, AttributeSet attrs) {
-        super(context, attrs, 1 /* alphaChannelCount */);
-        setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                | SYSTEM_UI_FLAG_LAYOUT_STABLE);
-    }
-
-    public Point getLastKnownSize() {
-        return mLastKnownSize;
-    }
-
-    @Override
-    public void recreateControllers() {
-        mControllers = new TouchController[] {
-                new RecentsTaskController(mActivity),
-                new FallbackNavBarTouchController(mActivity),
-        };
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Check size changes before the actual measure, to avoid multiple measure calls.
-        int width = Math.max(MIN_SIZE, MeasureSpec.getSize(widthMeasureSpec));
-        int height = Math.max(MIN_SIZE, MeasureSpec.getSize(heightMeasureSpec));
-        if (mLastKnownSize.x != width || mLastKnownSize.y != height) {
-            mLastKnownSize.set(width, height);
-            mActivity.onRootViewSizeChanged();
-        }
-
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @TargetApi(23)
-    @Override
-    protected boolean fitSystemWindows(Rect insets) {
-        // Update device profile before notifying the children.
-        mActivity.getDeviceProfile().updateInsets(insets);
-        setInsets(insets);
-        return false; // Let children get the full insets
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
-        // modifying child layout params.
-        if (!insets.equals(mInsets)) {
-            super.setInsets(insets);
-        }
-        setBackground(insets.top == 0  || !mAllowSysuiScrims
-                ? null
-                : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
-    }
-
-    public void dispatchInsets() {
-        mActivity.getDeviceProfile().updateInsets(mInsets);
-        super.setInsets(mInsets);
-    }
-}
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 70fb78e..32da52e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -256,8 +256,8 @@
         float taskHeight = mTaskRect.height();
 
         mMatrix.set(mPositionHelper.getMatrix());
-        mMatrix.postScale(scale, scale);
         mMatrix.postTranslate(insets.left, insets.top);
+        mMatrix.postScale(scale, scale);
 
         // Apply TaskView matrix: translate, scale, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
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 99afe39..cb22570 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
@@ -105,6 +105,7 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -146,8 +147,8 @@
  * A list of recent tasks.
  */
 @TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
-        TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
+public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
+        Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
         InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
         SplitScreenBounds.OnChangeListener {
 
@@ -389,12 +390,12 @@
         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
         setEnableFreeScroll(true);
         mSizeStrategy = sizeStrategy;
+        mActivity = BaseActivity.fromContext(context);
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
 
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
-        mActivity = BaseActivity.fromContext(context);
         mModel = RecentsModel.INSTANCE.get(context);
         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
 
@@ -798,8 +799,10 @@
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             TaskView taskView = getTaskViewAt(i);
             if (mIgnoreResetTaskId != taskView.getTask().key.id) {
-                taskView.resetVisualProperties();
+                taskView.resetViewTransforms();
                 taskView.setStableAlpha(mContentAlpha);
+                taskView.setFullscreenProgress(mFullscreenProgress);
+                taskView.setModalness(mTaskModalness);
             }
         }
         if (mRunningTaskTileHidden) {
@@ -998,6 +1001,7 @@
         mDwbToastShown = false;
         mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
         LayoutUtils.setViewEnabled(mActionsView, true);
+        mOrientationState.setGestureActive(false);
     }
 
     public @Nullable TaskView getRunningTaskView() {
@@ -1035,6 +1039,7 @@
      */
     public void onGestureAnimationStart(int runningTaskId) {
         // This needs to be called before the other states are set since it can create the task view
+        mOrientationState.setGestureActive(true);
         showCurrentTask(runningTaskId);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
@@ -1097,6 +1102,8 @@
      * Called when a gesture from an app has finished.
      */
     public void onGestureAnimationEnd() {
+        mOrientationState.setGestureActive(false);
+
         setOnScrollChangeListener(null);
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(true);
@@ -1109,13 +1116,20 @@
     }
 
     /**
+     * Returns true if we should add a dummy taskView for the running task id
+     */
+    protected boolean shouldAddDummyTaskView(int runningTaskId) {
+        return getTaskView(runningTaskId) == null;
+    }
+
+    /**
      * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
      *
      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
      * is called.  Also scrolls the view to this task.
      */
     public void showCurrentTask(int runningTaskId) {
-        if (getTaskView(runningTaskId) == null) {
+        if (shouldAddDummyTaskView(runningTaskId)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView = mTaskViewPool.getView();
@@ -1650,6 +1664,8 @@
                     !mOrientationState.canLauncherRotate() && isInLandscape);
             resetPaddingFromTaskSize();
             requestLayout();
+            // Reapply the current page to update page scrolls.
+            setCurrentPage(mCurrentPage);
         }
     }
 
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 411bab4..82fabac 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
@@ -545,7 +545,7 @@
         setIconAndDimTransitionProgress(iconScale, invert);
     }
 
-    private void resetViewTransforms() {
+    protected void resetViewTransforms() {
         setCurveScale(1);
         setTranslationX(0f);
         setTranslationY(0f);
@@ -554,12 +554,6 @@
         setIconScaleAndDim(1);
     }
 
-    public void resetVisualProperties() {
-        resetViewTransforms();
-        setFullscreenProgress(0);
-        setModalness(0);
-    }
-
     public void setStableAlpha(float parentAlpha) {
         mStableAlpha = parentAlpha;
         setAlpha(mStableAlpha);
@@ -953,9 +947,6 @@
      */
     public void setFullscreenProgress(float progress) {
         progress = Utilities.boundToRange(progress, 0, 1);
-        if (progress == mFullscreenProgress) {
-            return;
-        }
         mFullscreenProgress = progress;
         boolean isFullscreen = mFullscreenProgress > 0;
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
new file mode 100644
index 0000000..a31ba21
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.quickstep.util;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.shadows.LShadowDisplay;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.LauncherActivityInterface;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowDisplayManager;
+
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class TaskViewSimulatorTest {
+
+    @Test
+    public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(0, 80, 0, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(55, 80, 55, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(0, 80, 0, 40))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(0, 80, 120, 0))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(2450, 1250)
+                .withInsets(new Rect(55, 80, 55, 120))
+                .verifyNoTransforms();
+    }
+
+    @Test
+    public void taskProperlyScaled_landscape_rotated() {
+        new TaskMatrixVerifier()
+                .withLauncherSize(1200, 2450)
+                .withInsets(new Rect(0, 80, 0, 120))
+                .withAppBounds(
+                        new Rect(0, 0, 2450, 1200),
+                        new Rect(0, 80, 0, 120),
+                        Surface.ROTATION_90)
+                .verifyNoTransforms();
+    }
+
+    private static class TaskMatrixVerifier extends TransformParams {
+
+        private final Context mContext = RuntimeEnvironment.application;
+
+        private Rect mAppBounds = new Rect();
+        private Rect mLauncherInsets = new Rect();
+
+        private Rect mAppInsets;
+
+        private int mAppRotation = -1;
+        private DeviceProfile mDeviceProfile;
+
+        TaskMatrixVerifier withLauncherSize(int width, int height) {
+            ShadowDisplayManager.changeDisplay(DEFAULT_DISPLAY,
+                    String.format("w%sdp-h%sdp-mdpi", width, height));
+            if (mAppBounds.isEmpty()) {
+                mAppBounds.set(0, 0, width, height);
+            }
+            return this;
+        }
+
+        TaskMatrixVerifier withInsets(Rect insets) {
+            LShadowDisplay shadowDisplay = Shadow.extract(
+                    mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
+            shadowDisplay.setInsets(insets);
+            mLauncherInsets.set(insets);
+            return this;
+        }
+
+        TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) {
+            mAppBounds.set(bounds);
+            mAppInsets = insets;
+            mAppRotation = appRotation;
+            return this;
+        }
+
+        void verifyNoTransforms() {
+            mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext)
+                    .getDeviceProfile(mContext);
+            mDeviceProfile.updateInsets(mLauncherInsets);
+
+            TaskViewSimulator tvs = new TaskViewSimulator(mContext,
+                    LauncherActivityInterface.INSTANCE);
+            tvs.setDp(mDeviceProfile);
+
+            int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+            if (mAppRotation < 0) {
+                mAppRotation = launcherRotation;
+            }
+            tvs.setLayoutRotation(launcherRotation, mAppRotation);
+            if (mAppInsets == null) {
+                mAppInsets = new Rect(mLauncherInsets);
+            }
+            tvs.setPreviewBounds(mAppBounds, mAppInsets);
+
+            tvs.fullScreenProgress.value = 1;
+            tvs.recentsViewScale.value = tvs.getFullScreenScale();
+            tvs.apply(this);
+        }
+
+        @Override
+        public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
+            SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null);
+            proxy.onBuildTargetParams(builder, null, this);
+            return new SurfaceParams[] {builder.build()};
+        }
+
+        @Override
+        public void applySurfaceParams(SurfaceParams[] params) {
+            // Verify that the task position remains the same
+            RectF newAppBounds = new RectF(mAppBounds);
+            params[0].matrix.mapRect(newAppBounds);
+            Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds));
+
+            System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds);
+        }
+    }
+
+    private static class AlmostSame extends TypeSafeMatcher<RectF>  {
+
+        // Allow 1px error margin to account for float to int conversions
+        private final float mError = 1f;
+        private final Rect mExpected;
+
+        AlmostSame(Rect expected) {
+            mExpected = expected;
+        }
+
+        @Override
+        protected boolean matchesSafely(RectF item) {
+            return Math.abs(item.left - mExpected.left) < mError
+                    && Math.abs(item.top - mExpected.top) < mError
+                    && Math.abs(item.right - mExpected.right) < mError
+                    && Math.abs(item.bottom - mExpected.bottom) < mError;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendValue(mExpected);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index e496807..2fd807d 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -34,6 +34,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderIcon;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
@@ -174,6 +177,9 @@
         private Optional<ContainerInfo> mContainerInfo = Optional.empty();
         private int mSrcState = LAUNCHER_UICHANGED__SRC_STATE__HOME;
         private int mDstState = LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
+        private Optional<FromState> mFromState = Optional.empty();
+        private Optional<ToState> mToState = Optional.empty();
+        private Optional<String> mEditText = Optional.empty();
 
         @Override
         public StatsLogger withItemInfo(ItemInfo itemInfo) {
@@ -220,25 +226,33 @@
         }
 
         @Override
+        public StatsLogger withFromState(FromState fromState) {
+            this.mFromState = Optional.of(fromState);
+            return this;
+        }
+
+        @Override
+        public StatsLogger withToState(ToState toState) {
+            this.mToState = Optional.of(toState);
+            return this;
+        }
+
+        @Override
+        public StatsLogger withEditText(String editText) {
+            this.mEditText = Optional.of(editText);
+            return this;
+        }
+
+        @Override
         public void log(EventEnum event) {
             if (!Utilities.ATLEAST_R) {
                 return;
             }
 
-            LauncherAtom.ItemInfo.Builder itemInfoBuilder =
-                    (LauncherAtom.ItemInfo.Builder) mItemInfo.buildProto().toBuilder();
-            mRank.ifPresent(itemInfoBuilder::setRank);
-            if (mContainerInfo.isPresent()) {
-                // User already provided container info;
-                // default container info from item info will be ignored.
-                itemInfoBuilder.setContainerInfo(mContainerInfo.get());
-                write(event, mInstanceId, itemInfoBuilder.build(), mSrcState, mDstState);
-                return;
-            }
-
             if (mItemInfo.container < 0) {
                 // Item is not within a folder. Write to StatsLog in same thread.
-                write(event, mInstanceId, itemInfoBuilder.build(), mSrcState, mDstState);
+                write(event, mInstanceId, applyOverwrites(mItemInfo.buildProto()), mSrcState,
+                        mDstState);
             } else {
                 // Item is inside the folder, fetch folder info in a BG thread
                 // and then write to StatsLog.
@@ -248,17 +262,33 @@
                             public void execute(LauncherAppState app, BgDataModel dataModel,
                                     AllAppsList apps) {
                                 FolderInfo folderInfo = dataModel.folders.get(mItemInfo.container);
-                                LauncherAtom.ItemInfo.Builder atomInfoBuilder =
-                                        (LauncherAtom.ItemInfo.Builder) mItemInfo
-                                                .buildProto(folderInfo).toBuilder();
-                                mRank.ifPresent(atomInfoBuilder::setRank);
-                                write(event, mInstanceId, atomInfoBuilder.build(), mSrcState,
-                                        mDstState);
+                                write(event, mInstanceId,
+                                        applyOverwrites(mItemInfo.buildProto(folderInfo)),
+                                        mSrcState, mDstState);
                             }
                         });
             }
         }
 
+        private LauncherAtom.ItemInfo applyOverwrites(LauncherAtom.ItemInfo atomInfo) {
+            LauncherAtom.ItemInfo.Builder itemInfoBuilder =
+                    (LauncherAtom.ItemInfo.Builder) atomInfo.toBuilder();
+
+            mRank.ifPresent(itemInfoBuilder::setRank);
+            mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
+
+            if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
+                FolderIcon.Builder folderIconBuilder = (FolderIcon.Builder) itemInfoBuilder
+                        .getFolderIcon()
+                        .toBuilder();
+                mFromState.ifPresent(folderIconBuilder::setFromLabelState);
+                mToState.ifPresent(folderIconBuilder::setToLabelState);
+                mEditText.ifPresent(folderIconBuilder::setLabelInfo);
+                itemInfoBuilder.setFolderIcon(folderIconBuilder);
+            }
+            return itemInfoBuilder.build();
+        }
+
         private void write(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo,
                 int srcState, int dstState) {
             if (IS_VERBOSE) {
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 0b48a8b..f302fdd 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -88,8 +88,14 @@
     @WorkerThread
     public static void persistBitmapAndStartActivity(Context context, Bitmap bitmap, Rect crop,
             Intent intent, BiFunction<Uri, Intent, Intent[]> uriToIntentMap, String tag) {
-        context.startActivities(
-                uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent));
+        Intent[] intents = uriToIntentMap.apply(getImageUri(bitmap, crop, context, tag), intent);
+
+        // Work around b/159412574
+        if (intents.length == 1) {
+            context.startActivity(intents[0]);
+        } else {
+            context.startActivities(intents);
+        }
     }
 
     /**
@@ -158,13 +164,13 @@
             intent = new Intent();
         }
         ClipData clipdata = new ClipData(new ClipDescription("content",
-                new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+                new String[]{"image/png"}),
                 new ClipData.Item(uri));
         intent.setAction(Intent.ACTION_SEND)
                 .setComponent(null)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(FLAG_GRANT_READ_URI_PERMISSION)
                 .setType("image/png")
-                .setFlags(FLAG_GRANT_READ_URI_PERMISSION)
                 .putExtra(Intent.EXTRA_STREAM, uri)
                 .setClipData(clipdata);
         return new Intent[]{Intent.createChooser(intent, null).addFlags(FLAG_ACTIVITY_NEW_TASK)};
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 68636cb..90ee18f 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -105,6 +105,9 @@
     private static final int FLAG_ROTATION_WATCHER_ENABLED = 1 << 6;
     // Enable home rotation for UI tests, ignoring home rotation value from prefs
     private static final int FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING = 1 << 7;
+    // Whether the swipe gesture is running, so the recents would stay locked in the
+    // current orientation
+    private static final int FLAG_SWIPE_UP_NOT_RUNNING = 1 << 8;
 
     private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
             FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
@@ -114,7 +117,8 @@
     // multi-window is enabled as in that case, activity itself rotates.
     private static final int VALUE_ROTATION_WATCHER_ENABLED =
             MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED
-                    | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED;
+                    | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED
+                    | FLAG_SWIPE_UP_NOT_RUNNING;
 
     private final Context mContext;
     private final ContentResolver mContentResolver;
@@ -156,6 +160,7 @@
         if (originalSmallestWidth < 600) {
             mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
         }
+        mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
         initFlags();
     }
 
@@ -167,6 +172,13 @@
     }
 
     /**
+     * Sets if the swipe up gesture is currently running or not
+     */
+    public void setGestureActive(boolean isGestureActive) {
+        setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
+    }
+
+    /**
      * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
      * @param touchRotation The rotation the nav bar region that is touched is in
      * @param displayRotation Rotation of the display/device
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 4b7097a..881df1b 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -22,7 +22,7 @@
     android:theme="@style/PopupItem" >
 
     <com.android.launcher3.BubbleTextView
-        style="@style/BaseIcon"
+        style="@style/BaseIconUnBounded"
         android:id="@+id/bubble_text"
         android:background="?android:attr/selectableItemBackground"
         android:gravity="start|center_vertical"
@@ -30,6 +30,7 @@
         android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
         android:paddingEnd="@dimen/popup_padding_end"
         android:textSize="14sp"
+        android:maxLines="2"
         android:textColor="?android:attr/textColorPrimary"
         launcher:iconDisplay="shortcut_popup"
         launcher:layoutHorizontal="true"
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
index b5237db..31953c7 100644
--- a/res/layout/work_mode_switch.xml
+++ b/res/layout/work_mode_switch.xml
@@ -17,7 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    style="@style/PrimaryMediumText"
+    style="@style/PrimaryHeadline"
     android:id="@+id/work_mode_toggle"
     android:drawableStart="@drawable/ic_corp"
     android:drawablePadding="16dp"
@@ -25,6 +25,7 @@
     android:textColor="?attr/workProfileOverlayTextColor"
     android:layout_alignParentBottom="true"
     android:ellipsize="end"
+    android:elevation="10dp"
     android:gravity="start"
     android:lines="1"
     android:showText="false"
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ac00488..25f21f3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -55,7 +55,7 @@
         <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
         <item name="workProfileOverlayTextColor">#FF212121</item>
         <item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
-        <item name="disabledIconAlpha">.36</item>
+        <item name="disabledIconAlpha">.54</item>
 
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
@@ -69,7 +69,7 @@
     <style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
         <item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
         <item name="folderTextColor">?attr/workspaceTextColor</item>
-        <item name="disabledIconAlpha">.24</item>
+        <item name="disabledIconAlpha">.254</item>
 
     </style>
 
@@ -117,7 +117,7 @@
     <style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
         <item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
         <item name="folderTextColor">@android:color/white</item>
-        <item name="disabledIconAlpha">.24</item>
+        <item name="disabledIconAlpha">.54</item>
     </style>
 
     <style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
@@ -187,28 +187,31 @@
     </style>
 
     <style name="AllAppsTheme">
-        <item name="disabledIconAlpha">.24</item>
+        <item name="disabledIconAlpha">.54</item>
     </style>
 
     <style name="AllAppsTheme.Dark">
-        <item name="disabledIconAlpha">.28</item>
+        <item name="disabledIconAlpha">.54</item>
     </style>
 
-    <!-- Base theme for BubbleTextView and sub classes -->
-    <style name="BaseIcon" parent="@android:style/TextAppearance.DeviceDefault">
+
+    <style name="BaseIconUnBounded" parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:layout_gravity">center</item>
         <item name="android:focusable">true</item>
         <item name="android:gravity">center_horizontal</item>
-        <item name="android:lines">1</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:defaultFocusHighlightEnabled">false</item>
-
         <!-- No shadows in the base theme -->
         <item name="android:shadowRadius">0</item>
     </style>
 
+    <!-- Base theme for BubbleTextView and sub classes -->
+    <style name="BaseIcon" parent="BaseIconUnBounded">
+        <item name="android:lines">1</item>
+    </style>
+
     <!-- Icon displayed on the workspace -->
     <style name="BaseIcon.Workspace" >
         <item name="android:shadowRadius">2.0</item>
@@ -243,7 +246,6 @@
     <style name="DropTargetButton" parent="DropTargetButtonBase" />
 
     <style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
-    <style name="PrimaryMediumText" parent="@android:style/TextAppearance.DeviceDefault.Medium"/>
     <style name="PrimaryHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
 
     <style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index b171712..a8e0cb3 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -5,6 +5,7 @@
     com.android.launcher3.shadows.LShadowAppWidgetManager \
     com.android.launcher3.shadows.LShadowBackupManager \
     com.android.launcher3.shadows.LShadowBitmap \
+    com.android.launcher3.shadows.LShadowDisplay \
     com.android.launcher3.shadows.LShadowLauncherApps \
     com.android.launcher3.shadows.LShadowTypeface \
     com.android.launcher3.shadows.LShadowUserManager \
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
new file mode 100644
index 0000000..3813fa1
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowDisplay.java
@@ -0,0 +1,54 @@
+/*
+ * 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.shadows;
+
+import static org.robolectric.shadow.api.Shadow.directlyOn;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowDisplay;
+
+/**
+ * Extension of {@link ShadowDisplay} with missing shadow methods
+ */
+@Implements(value = Display.class)
+public class LShadowDisplay extends ShadowDisplay {
+
+    private final Rect mInsets = new Rect();
+
+    @RealObject Display realObject;
+
+    /**
+     * Sets the insets for the display
+     */
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+    }
+
+    @Override
+    protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
+        directlyOn(realObject, Display.class).getCurrentSizeRange(outSmallestSize, outLargestSize);
+        outSmallestSize.x -= mInsets.left + mInsets.right;
+        outLargestSize.x -= mInsets.left + mInsets.right;
+
+        outSmallestSize.y -= mInsets.top + mInsets.bottom;
+        outLargestSize.y -= mInsets.top + mInsets.bottom;
+    }
+}
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index c1aed98..b27abc4 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.Executors;
 
 /**
  * Interface defining an object that can receive a drag.
@@ -84,7 +85,9 @@
 
         public DragObject(Context context) {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                folderNameProvider = FolderNameProvider.newInstance(context);
+                Executors.MODEL_EXECUTOR.post(() -> {
+                    folderNameProvider = FolderNameProvider.newInstance(context);
+                });
             }
         }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ec32e62..7fc64ea 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -260,7 +260,6 @@
 
     @Thunk
     Workspace mWorkspace;
-    private View mLauncherView;
     @Thunk
     DragLayer mDragLayer;
     private DragController mDragController;
@@ -363,6 +362,7 @@
         LauncherAppState app = LauncherAppState.getInstance(this);
         mOldConfig = new Configuration(getResources().getConfiguration());
         mModel = app.getModel();
+
         mRotationHelper = new RotationHelper(this);
         InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
         initDeviceProfile(idp);
@@ -382,8 +382,7 @@
                 appWidgetId -> getWorkspace().removeWidget(appWidgetId));
         mAppWidgetHost.startListening();
 
-        mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
-
+        inflateRootView(R.layout.launcher);
         setupViews();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
@@ -420,7 +419,7 @@
         // For handling default keys
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
-        setContentView(mLauncherView);
+        setContentView(getRootView());
         getRootView().dispatchInsets();
 
         // Listen for broadcasts
@@ -520,12 +519,6 @@
     }
 
     @Override
-    public void reapplyUi(boolean cancelCurrentAnimation) {
-        getRootView().dispatchInsets();
-        super.reapplyUi(cancelCurrentAnimation);
-    }
-
-    @Override
     public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
         onIdpChanged(idp);
     }
@@ -581,11 +574,6 @@
         return mStateManager;
     }
 
-    @Override
-    public <T extends View> T findViewById(int id) {
-        return mLauncherView.findViewById(id);
-    }
-
     private LauncherCallbacks mLauncherCallbacks;
 
     /**
@@ -1118,10 +1106,6 @@
         mHotseat = findViewById(R.id.hotseat);
         mHotseat.setWorkspace(mWorkspace);
 
-        mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-
         // Setup the drag layer
         mDragLayer.setup(mDragController, mWorkspace);
 
@@ -1335,11 +1319,6 @@
     }
 
     @Override
-    public LauncherRootView getRootView() {
-        return (LauncherRootView) mLauncherView;
-    }
-
-    @Override
     public DragLayer getDragLayer() {
         return mDragLayer;
     }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 6951ff2..51504ce 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -10,6 +10,8 @@
 import android.view.ViewDebug;
 import android.view.WindowInsets;
 
+import com.android.launcher3.statemanager.StatefulActivity;
+
 import java.util.Collections;
 import java.util.List;
 
@@ -17,7 +19,7 @@
 
     private final Rect mTempRect = new Rect();
 
-    private final Launcher mLauncher;
+    private final StatefulActivity mActivity;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
@@ -31,17 +33,17 @@
 
     public LauncherRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mLauncher = Launcher.getLauncher(context);
+        mActivity = StatefulActivity.fromContext(context);
     }
 
     private void handleSystemWindowInsets(Rect insets) {
         // Update device profile before notifying th children.
-        mLauncher.getDeviceProfile().updateInsets(insets);
+        mActivity.getDeviceProfile().updateInsets(insets);
         boolean resetState = !insets.equals(mInsets);
         setInsets(insets);
 
         if (resetState) {
-            mLauncher.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+            mActivity.getStateManager().reapplyState(true /* cancelCurrentAnimation */);
         }
     }
 
@@ -63,7 +65,7 @@
     }
 
     public void dispatchInsets() {
-        mLauncher.getDeviceProfile().updateInsets(mInsets);
+        mActivity.getDeviceProfile().updateInsets(mInsets);
         super.setInsets(mInsets);
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fb58f21..a8dca12 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -415,7 +415,7 @@
 
         // Always enter the spring loaded mode
         mLauncher.getStateManager().goToState(SPRING_LOADED);
-        mStatsLogManager.logger().withItemInfo(dragObject.originalDragInfo)
+        mStatsLogManager.logger().withItemInfo(dragObject.dragInfo)
                 .withInstanceId(dragObject.logInstanceId)
                 .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
     }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7998c2d..e950f3c 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -81,7 +81,10 @@
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.FolderInfo.FolderListener;
@@ -99,6 +102,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.StringJoiner;
 import java.util.stream.Collectors;
 
 /**
@@ -109,6 +113,12 @@
         View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
     private static final boolean DEBUG = false;
+
+    /**
+     * Used for separating folder title when logging together.
+     */
+    private static final CharSequence FOLDER_LABEL_DELIMITER = "~";
+
     /**
      * We avoid measuring {@link #mContent} with a 0 width or height, as this
      * results in CellLayout being measured as UNSPECIFIED, which it does not support.
@@ -155,6 +165,8 @@
     protected final Launcher mLauncher;
     protected DragController mDragController;
     public FolderInfo mInfo;
+    private CharSequence mFromTitle;
+    private FromState mFromLabelState;
 
     @Thunk FolderIcon mFolderIcon;
 
@@ -335,7 +347,6 @@
             Log.d(TAG, "onBackKey newTitle=" + newTitle);
         }
         mInfo.setTitle(newTitle);
-        mInfo.fromCustom = mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
         mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(),
                 mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
@@ -415,6 +426,8 @@
 
     void bind(FolderInfo info) {
         mInfo = info;
+        mFromTitle = info.title;
+        mFromLabelState = info.getFromLabelState();
         ArrayList<WorkspaceItemInfo> children = info.contents;
         Collections.sort(children, ITEM_POS_COMPARATOR);
         updateItemLocationsInDatabaseBatch(true);
@@ -1005,7 +1018,8 @@
         if (!items.isEmpty()) {
             mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
         }
-        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind) {
+        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind
+                && total > 1 /* no need to update if there's one icon */) {
             Executors.MODEL_EXECUTOR.post(() -> {
                 FolderNameInfo[] nameInfos =
                         new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
@@ -1441,10 +1455,38 @@
     public void onFocusChange(View v, boolean hasFocus) {
         if (v == mFolderName) {
             if (hasFocus) {
+                mFromLabelState = mInfo.getFromLabelState();
+                mFromTitle = mInfo.title;
                 startEditingFolderName();
             } else {
-                mStatsLogManager.logger().withItemInfo(mInfo).log(LAUNCHER_FOLDER_LABEL_UPDATED);
-                logFolderLabelState();
+                StatsLogger statsLogger = mStatsLogManager.logger()
+                        .withItemInfo(mInfo)
+                        .withFromState(mFromLabelState);
+
+                // If the folder label is suggested, it is logged to improve prediction model.
+                // When both old and new labels are logged together delimiter is used.
+                StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER);
+                if (mFromLabelState.equals(FromState.FROM_SUGGESTED)) {
+                    labelInfoBuilder.add(mFromTitle);
+                }
+
+                ToState toLabelState;
+                if (mFromTitle != null && mFromTitle.equals(mInfo.title)) {
+                    toLabelState = ToState.UNCHANGED;
+                } else {
+                    toLabelState = mInfo.getToLabelState();
+                    if (toLabelState.toString().startsWith("TO_SUGGESTION")) {
+                        labelInfoBuilder.add(mInfo.title);
+                    }
+                }
+                statsLogger.withToState(toLabelState);
+
+                if (labelInfoBuilder.length() > 0) {
+                    statsLogger.withEditText(labelInfoBuilder.toString());
+                }
+
+                statsLogger.log(LAUNCHER_FOLDER_LABEL_UPDATED);
+                logFolderLabelState(mFromLabelState, toLabelState);
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1649,8 +1691,8 @@
      * @deprecated This method is only used for log validation and soon will be removed.
      */
     @Deprecated
-    public void logFolderLabelState() {
+    public void logFolderLabelState(FromState fromState, ToState toState) {
         mLauncher.getUserEventDispatcher()
-                .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent());
+                .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent(fromState, toState));
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index b40b1e2..e24033f 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -20,7 +20,9 @@
 
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -63,6 +65,8 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
@@ -80,6 +84,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
@@ -409,7 +414,7 @@
             FolderNameInfo[] nameInfos =
                     new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                Executors.UI_HELPER_EXECUTOR.post(() -> {
+                Executors.MODEL_EXECUTOR.post(() -> {
                     d.folderNameProvider.getSuggestedFolderName(
                             getContext(), mInfo.contents, nameInfos);
                     showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
@@ -428,7 +433,6 @@
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
             setLabelSuggestion(nameInfos, instanceId);
-            mFolder.logFolderLabelState();
             invalidate();
         }, DROP_IN_ANIMATION_DURATION);
     }
@@ -444,15 +448,40 @@
                 || mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
             return;
         }
-        if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
+        if (nameInfos == null || Stream.of(nameInfos)
+                .allMatch(nameInfo -> nameInfo == null || isEmpty(nameInfo.getLabel()))) {
+            StatsLogManager.newInstance(getContext()).logger()
+                    .withInstanceId(instanceId)
+                    .withItemInfo(mInfo)
+                    .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS);
             return;
         }
-        mInfo.setTitle(nameInfos[0].getLabel());
-        StatsLogManager.newInstance(getContext()).logger().withItemInfo(mInfo)
-                .withInstanceId(instanceId).log(LAUNCHER_FOLDER_LABEL_UPDATED);
+        if (nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
+            StatsLogManager.newInstance(getContext()).logger()
+                    .withInstanceId(instanceId)
+                    .withItemInfo(mInfo)
+                    .log(LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY);
+            return;
+        }
+        CharSequence newTitle = nameInfos[0].getLabel();
+        FromState fromState = mInfo.getFromLabelState();
+
+        mInfo.setTitle(newTitle);
         onTitleChanged(mInfo.title);
         mFolder.mFolderName.setText(mInfo.title);
         mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
+
+        // Logging for folder creation flow
+        StatsLogManager.newInstance(getContext()).logger()
+                .withInstanceId(instanceId)
+                .withItemInfo(mInfo)
+                .withFromState(fromState)
+                .withToState(ToState.TO_SUGGESTION0)
+                // When LAUNCHER_FOLDER_LABEL_UPDATED event.edit_text does not have delimiter,
+                // event is assumed to be folder creation on the server side.
+                .withEditText(newTitle.toString())
+                .log(LAUNCHER_FOLDER_AUTO_LABELED);
+        mFolder.logFolderLabelState(fromState, ToState.TO_SUGGESTION0);
     }
 
 
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 7731e6e..2be0bce 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.ArrayList;
@@ -64,6 +65,7 @@
     public static FolderNameProvider newInstance(Context context) {
         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
                 context.getApplicationContext(), R.string.folder_name_provider_class);
+        Preconditions.assertWorkerThread();
         fnp.load(context);
 
         return fnp;
@@ -71,6 +73,7 @@
 
     public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
             IntSparseArrayMap<FolderInfo> folderInfos) {
+        Preconditions.assertWorkerThread();
         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
                 context.getApplicationContext(), R.string.folder_name_provider_class);
         fnp.load(appInfos, folderInfos);
@@ -94,7 +97,7 @@
     public void getSuggestedFolderName(Context context,
             ArrayList<WorkspaceItemInfo> workspaceItemInfos,
             FolderNameInfo[] nameInfos) {
-
+        Preconditions.assertWorkerThread();
         if (DEBUG) {
             Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 82d61da..3edfa8d 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -21,6 +21,8 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -66,8 +68,16 @@
                 + "resulting in a new folder creation")
         LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
 
-        @UiEvent(doc = "User action resulted in or manually updated the folder label to "
-                + "new/same value.")
+        @UiEvent(doc = "Folder's label is automatically assigned.")
+        LAUNCHER_FOLDER_AUTO_LABELED(591),
+
+        @UiEvent(doc = "Could not auto-label a folder because primary suggestion is null or empty.")
+        LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY(592),
+
+        @UiEvent(doc = "Could not auto-label a folder because no suggestions exist.")
+        LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_SUGGESTIONS(593),
+
+        @UiEvent(doc = "User manually updated the folder label.")
         LAUNCHER_FOLDER_LABEL_UPDATED(460),
 
         @UiEvent(doc = "User long pressed on the workspace empty space.")
@@ -246,6 +256,27 @@
         }
 
         /**
+         * Sets FromState field of log message.
+         */
+        default StatsLogger withFromState(FromState fromState) {
+            return this;
+        }
+
+        /**
+         * Sets ToState field of log message.
+         */
+        default StatsLogger withToState(ToState toState) {
+            return this;
+        }
+
+        /**
+         * Sets editText field of log message.
+         */
+        default StatsLogger withEditText(String editText) {
+            return this;
+        }
+
+        /**
          * Sets the final value for container related fields of log message.
          *
          * By default container related fields are derived from {@link ItemInfo}, this method would
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 8f577b5..08eb383 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -54,7 +54,6 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.OptionalInt;
-import java.util.StringJoiner;
 import java.util.stream.IntStream;
 
 
@@ -88,20 +87,6 @@
 
     public Intent suggestedFolderNames;
 
-    // Represents the title before current.
-    // Primarily used for logging purpose.
-    private CharSequence mPreviousTitle;
-
-    // True if the title before was manually entered, suggested otherwise.
-    // Primarily used for logging purpose.
-    public boolean fromCustom;
-
-    /**
-     * Used for separating {@link #mPreviousTitle} and {@link #title} when concatenating them
-     * for logging.
-     */
-    private static final CharSequence FOLDER_LABEL_DELIMITER = "=>";
-
     /**
      * The apps and shortcuts
      */
@@ -207,21 +192,16 @@
         return getDefaultItemInfoBuilder()
                 .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
                 .setRank(rank)
-                .setAttribute(fromCustom ? MANUAL_LABEL : SUGGESTED_LABEL)
+                .setAttribute(hasOption(FLAG_MANUAL_FOLDER_NAME) ? MANUAL_LABEL : SUGGESTED_LABEL)
                 .setContainerInfo(getContainerInfo())
                 .build();
     }
 
     @Override
     public void setTitle(CharSequence title) {
-        mPreviousTitle = this.title;
         this.title = title;
     }
 
-    public CharSequence getPreviousTitle() {
-        return mPreviousTitle;
-    }
-
     @Override
     public ItemInfo makeShallowCopy() {
         FolderInfo folderInfo = new FolderInfo();
@@ -235,30 +215,7 @@
      */
     @Override
     public LauncherAtom.ItemInfo buildProto() {
-        FromState fromFolderLabelState = getFromFolderLabelState();
-        ToState toFolderLabelState = getToFolderLabelState();
-        LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder()
-                .setCardinality(contents.size())
-                .setFromLabelState(fromFolderLabelState)
-                .setToLabelState(toFolderLabelState);
-
-        // If the folder label is suggested, it is logged to improve prediction model.
-        // When both old and new labels are logged together delimiter is used.
-        StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER);
-        if (fromFolderLabelState.equals(FromState.FROM_SUGGESTED)) {
-            labelInfoBuilder.add(mPreviousTitle);
-        }
-        if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) {
-            labelInfoBuilder.add(title);
-        }
-        if (labelInfoBuilder.length() > 0) {
-            folderIconBuilder.setLabelInfo(labelInfoBuilder.toString());
-        }
-
-        return getDefaultItemInfoBuilder()
-                .setFolderIcon(folderIconBuilder)
-                .setContainerInfo(getContainerInfo())
-                .build();
+        return buildProto(null);
     }
 
     /**
@@ -280,15 +237,27 @@
 
     }
 
-    private LauncherAtom.ToState getToFolderLabelState() {
+    /**
+     * Returns {@link FromState} based on current {@link #title}.
+     */
+    public LauncherAtom.FromState getFromLabelState() {
+        return title == null
+                ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
+                : title.length() == 0
+                        ? LauncherAtom.FromState.FROM_EMPTY
+                        : hasOption(FLAG_MANUAL_FOLDER_NAME)
+                                ? LauncherAtom.FromState.FROM_CUSTOM
+                                : LauncherAtom.FromState.FROM_SUGGESTED;
+    }
+
+    /**
+     * Returns {@link ToState} based on current {@link #title}.
+     */
+    public LauncherAtom.ToState getToLabelState() {
         if (title == null) {
             return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
         }
 
-        if (title.equals(mPreviousTitle)) {
-            return LauncherAtom.ToState.UNCHANGED;
-        }
-
         if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
             return title.length() > 0
                     ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED
@@ -335,17 +304,6 @@
                 // fall through
         }
         return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
-
-    }
-
-    private LauncherAtom.FromState getFromFolderLabelState() {
-        return mPreviousTitle == null
-                ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
-                : mPreviousTitle.length() == 0
-                        ? LauncherAtom.FromState.FROM_EMPTY
-                        : fromCustom
-                                ? LauncherAtom.FromState.FROM_CUSTOM
-                                : LauncherAtom.FromState.FROM_SUGGESTED;
     }
 
     private Optional<String[]> getSuggestedLabels() {
@@ -368,7 +326,8 @@
      * @deprecated This method is used only for validation purpose and soon will be removed.
      */
     @Deprecated
-    public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent() {
+    public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent(FromState fromState,
+            ToState toState) {
         return LauncherLogProto.LauncherEvent.newBuilder()
                 .setAction(LauncherLogProto.Action
                         .newBuilder()
@@ -377,8 +336,8 @@
                         .newBuilder()
                         .setType(Target.Type.ITEM)
                         .setItemType(LauncherLogProto.ItemType.EDITTEXT)
-                        .setFromFolderLabelState(convertFolderLabelState(getFromFolderLabelState()))
-                        .setToFolderLabelState(convertFolderLabelState(getToFolderLabelState())))
+                        .setFromFolderLabelState(convertFolderLabelState(fromState))
+                        .setToFolderLabelState(convertFolderLabelState(toState)))
                 .addSrcTarget(Target.newBuilder()
                         .setType(Target.Type.CONTAINER)
                         .setContainerType(LauncherLogProto.ContainerType.FOLDER)
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 0a1607c..dbe5f42 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,10 +18,13 @@
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
 
 import androidx.annotation.CallSuper;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -38,6 +41,8 @@
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
     private boolean mDeferredResumePending;
 
+    private LauncherRootView mRootView;
+
     /**
      * Create handlers to control the property changes for this activity
      */
@@ -55,6 +60,23 @@
      */
     public abstract StateManager<STATE_TYPE> getStateManager();
 
+    protected void inflateRootView(int layoutId) {
+        mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null);
+        mRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+    }
+
+    @Override
+    public final LauncherRootView getRootView() {
+        return mRootView;
+    }
+
+    @Override
+    public <T extends View> T findViewById(int id) {
+        return mRootView.findViewById(id);
+    }
+
     /**
      * Called when transition to the state starts
      */
@@ -87,6 +109,7 @@
      * the transition if requested.
      */
     public void reapplyUi(boolean cancelCurrentAnimation) {
+        getRootView().dispatchInsets();
         getStateManager().reapplyState(cancelCurrentAnimation);
     }
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index e786f07..38b3712 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,27 +15,17 @@
  */
 package com.android.launcher3.testing;
 
-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;
-
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
@@ -44,10 +34,7 @@
 import com.android.launcher3.R;
 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;
 
@@ -65,7 +52,6 @@
     protected Context mContext;
     protected DeviceProfile mDeviceProfile;
     protected LauncherAppState mLauncherAppState;
-    private static LinkedList mLeaks;
 
     public void init(Context context) {
         mContext = context;
@@ -77,15 +63,6 @@
     public Bundle call(String method) {
         final Bundle response = new Bundle();
         switch (method) {
-            case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
-                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: {
                 return getLauncherUIProperty(Bundle::putInt, l -> {
                     final float progress = LauncherState.NORMAL.getVerticalProgress(l)
@@ -99,14 +76,6 @@
                 return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
             }
 
-            case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
-                TestProtocol.sDebugTracing = true;
-                break;
-
-            case TestProtocol.REQUEST_DISABLE_DEBUG_TRACING:
-                TestProtocol.sDebugTracing = false;
-                break;
-
             case TestProtocol.REQUEST_FREEZE_APP_LIST:
                 return getLauncherUIProperty(Bundle::putBoolean, l -> {
                     l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
@@ -118,11 +87,6 @@
                     return true;
                 });
 
-            case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
-                return getLauncherUIProperty(Bundle::putInt,
-                        l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
-            }
-
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
                 return getLauncherUIProperty(Bundle::putInt,
                         l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
@@ -137,59 +101,19 @@
                 }, this::getCurrentActivity);
             }
 
-            case TestProtocol.REQUEST_PID: {
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, Os.getpid());
-                break;
-            }
-
-            case TestProtocol.REQUEST_TOTAL_PSS_KB: {
-                runGcAndFinalizersSync();
-                Debug.MemoryInfo mem = new Debug.MemoryInfo();
-                Debug.getMemoryInfo(mem);
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
-                break;
-            }
-
-            case TestProtocol.REQUEST_JAVA_LEAK: {
-                if (mLeaks == null) mLeaks = new LinkedList();
-
-                // Allocate and dirty the memory.
-                final int leakSize = 1024 * 1024;
-                final byte[] bytes = new byte[leakSize];
-                for (int i = 0; i < leakSize; i += 239) {
-                    bytes[i] = (byte) (i % 256);
-                }
-                mLeaks.add(bytes);
-                break;
-            }
-
-            case TestProtocol.REQUEST_NATIVE_LEAK: {
-                if (mLeaks == null) mLeaks = new LinkedList();
-
-                // Allocate and dirty a bitmap.
-                final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
-                bitmap.eraseColor(Color.RED);
-                mLeaks.add(bitmap);
-                break;
-            }
-
-            case TestProtocol.REQUEST_VIEW_LEAK: {
-                if (mLeaks == null) mLeaks = new LinkedList();
-                mLeaks.add(new View(mContext));
-                break;
-            }
-
             case TestProtocol.REQUEST_ICON_HEIGHT: {
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                         mDeviceProfile.allAppsCellHeightPx);
-                break;
+                return response;
             }
 
             case TestProtocol.REQUEST_MOCK_SENSOR_ROTATION:
                 TestProtocol.sDisableSensorRotation = true;
-                break;
+                return response;
+
+            default:
+                return null;
         }
-        return response;
     }
 
     protected boolean isLauncherInitialized() {
@@ -201,22 +125,6 @@
         return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
-    private static void runGcAndFinalizersSync() {
-        Runtime.getRuntime().gc();
-        Runtime.getRuntime().runFinalization();
-
-        final CountDownLatch fence = new CountDownLatch(1);
-        createFinalizationObserver(fence);
-        try {
-            do {
-                Runtime.getRuntime().gc();
-                Runtime.getRuntime().runFinalization();
-            } while (!fence.await(100, TimeUnit.MILLISECONDS));
-        } catch (InterruptedException ex) {
-            throw new RuntimeException(ex);
-        }
-    }
-
     /**
      * Returns the result by getting a Launcher property on UI thread
      */
@@ -257,21 +165,4 @@
          */
         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.
-    @Keep
-    private static void createFinalizationObserver(CountDownLatch fence) {
-        new Object() {
-            @Override
-            protected void finalize() throws Throwable {
-                try {
-                    fence.countDown();
-                } finally {
-                    super.finalize();
-                }
-            }
-        };
-    }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 7cce044..3d39d25 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -294,7 +294,8 @@
         // Limits UI tests affecting tests running after them.
         mLauncher.waitForLauncherInitialized();
         if (mLauncherPid != 0) {
-            assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+            assertEquals("Launcher crashed, pid mismatch:",
+                    mLauncherPid, mLauncher.getPid().intValue());
         }
         checkDetectedLeaks(mLauncher);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 808be66..b6c17df 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -244,8 +245,8 @@
     }
 
     private void verifyNotFrozen(String message) {
-        mLauncher.assertEquals(message, 0, mLauncher.getTestInfo(
-                TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS).
-                getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
+        final Bundle testInfo = mLauncher.getTestInfo(TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS);
+        if (testInfo == null) return;
+        mLauncher.assertEquals(message, 0, testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD));
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7ddb492..9f4d9ce 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1309,8 +1309,9 @@
                 getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    public int getPid() {
-        return getTestInfo(TestProtocol.REQUEST_PID).getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    public Integer getPid() {
+        final Bundle testInfo = getTestInfo(TestProtocol.REQUEST_PID);
+        return testInfo != null ? testInfo.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) : null;
     }
 
     public void produceJavaLeak() {
@@ -1338,13 +1339,13 @@
     public Closable eventsCheck() {
         Assert.assertTrue("Nested event checking", !sCheckingEvents);
         disableSensorRotation();
-        final int initialPid = getPid();
+        final Integer initialPid = getPid();
         if (sEventChecker == null) sEventChecker = new LogEventChecker();
         sEventChecker.start();
         sCheckingEvents = true;
 
         return () -> {
-            if (initialPid != getPid()) {
+            if (initialPid != null && initialPid.intValue() != getPid()) {
                 if (mOnLauncherCrashed != null) mOnLauncherCrashed.run();
                 checkForAnomaly();
                 Assert.fail(