Fix launcher crash after restore with some specific widgets. am: 8dae83ee9b am: 12ef632d00 am: b446d1069b

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/12196250

Change-Id: Ibaa44b172006576d642a19569b0973c50e54f80b
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index ff5bf0d..19a16e3 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -45,9 +45,6 @@
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
-    <!-- TODO(b/150802536): Enabled only for ENABLE_FIXED_ROTATION_TRANSFORM feature flag -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     
     <!--
     Permissions required for read/write access to the workspace data. These permission name
@@ -86,6 +83,7 @@
         <receiver
             android:name="com.android.launcher3.InstallShortcutReceiver"
             android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"
+            android:exported="true"
             android:enabled="@bool/enable_install_shortcut_api" >
             <intent-filter>
                 <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
@@ -94,14 +92,16 @@
 
         <!-- Intent received when a session is committed -->
         <receiver
-            android:name="com.android.launcher3.SessionCommitReceiver" >
+            android:name="com.android.launcher3.SessionCommitReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.content.pm.action.SESSION_COMMITTED" />
             </intent-filter>
         </receiver>
 
         <!-- Intent received used to initialize a restored widget -->
-        <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver" >
+        <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED"/>
             </intent-filter>
@@ -117,6 +117,7 @@
             android:name="com.android.launcher3.notification.NotificationListener"
             android:label="@string/notification_dots_service_title"
             android:enabled="@bool/notification_dots_enabled"
+            android:exported="true"
             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
                 <action android:name="android.service.notification.NotificationListenerService" />
@@ -130,6 +131,7 @@
             android:theme="@style/AppItemActivityTheme"
             android:excludeFromRecents="true"
             android:autoRemoveFromRecents="true"
+            android:exported="true"
             android:label="@string/action_add_to_workspace" >
             <intent-filter>
                 <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
@@ -165,6 +167,7 @@
             android:name="com.android.launcher3.settings.SettingsActivity"
             android:label="@string/settings_button_text"
             android:theme="@style/HomeSettingsTheme"
+            android:exported="true"
             android:autoRemoveFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
@@ -187,6 +190,7 @@
             android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
             android:theme="@style/AppTheme"
             android:launchMode="singleTop"
+            android:exported="true"
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b031ffb..8e01f82 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -53,6 +53,7 @@
             android:resizeableActivity="true"
             android:resumeWhilePausing="true"
             android:taskAffinity=""
+            android:exported="true"
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index e649ce1..aa3710b 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -164,6 +164,12 @@
             }
 
             case TestProtocol.REQUEST_GET_TEST_EVENTS: {
+                if (sEvents == null) {
+                    // sEvents can be null if Launcher died and restarted after
+                    // REQUEST_START_EVENT_LOGGING.
+                    return response;
+                }
+
                 synchronized (sEvents) {
                     response.putStringArrayList(
                             TestProtocol.TEST_INFO_RESPONSE_FIELD, new ArrayList<>(sEvents));
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 60afddb..53910e3 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -53,6 +53,7 @@
             android:resizeableActivity="true"
             android:resumeWhilePausing="true"
             android:taskAffinity=""
+            android:exported="true"
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index e49f2ec..4e7c3fa 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -17,97 +17,91 @@
 ** limitations under the License.
 */
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.launcher3" >
 
-    <permission
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.launcher3">
+
+     <permission
         android:name="${packageName}.permission.HOTSEAT_EDU"
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
         android:protectionLevel="signatureOrSystem" />
 
-    <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
 
-    <application
-        android:backupAgent="com.android.launcher3.LauncherBackupAgent"
-        android:fullBackupOnly="true"
-        android:fullBackupContent="@xml/backupscheme"
-        android:hardwareAccelerated="true"
-        android:icon="@drawable/ic_launcher_home"
-        android:label="@string/derived_app_name"
-        android:theme="@style/AppTheme"
-        android:largeHeap="@bool/config_largeHeap"
-        android:restoreAnyVersion="true"
-        android:supportsRtl="true" >
+    <application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+         android:fullBackupOnly="true"
+         android:fullBackupContent="@xml/backupscheme"
+         android:hardwareAccelerated="true"
+         android:icon="@drawable/ic_launcher_home"
+         android:label="@string/derived_app_name"
+         android:theme="@style/AppTheme"
+         android:largeHeap="@bool/config_largeHeap"
+         android:restoreAnyVersion="true"
+         android:supportsRtl="true">
 
-        <service
-            android:name="com.android.quickstep.TouchInteractionService"
-            android:permission="android.permission.STATUS_BAR_SERVICE"
-            android:directBootAware="true" >
+        <service android:name="com.android.quickstep.TouchInteractionService"
+             android:permission="android.permission.STATUS_BAR_SERVICE"
+             android:directBootAware="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
+                <action android:name="android.intent.action.QUICKSTEP_SERVICE"/>
             </intent-filter>
         </service>
 
         <activity android:name="com.android.quickstep.RecentsActivity"
-            android:excludeFromRecents="true"
-            android:launchMode="singleTask"
-            android:clearTaskOnLaunch="true"
-            android:stateNotNeeded="true"
-            android:theme="@style/LauncherTheme"
-            android:screenOrientation="unspecified"
-            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
-            android:resizeableActivity="true"
-            android:resumeWhilePausing="true"
-            android:taskAffinity="" />
+             android:excludeFromRecents="true"
+             android:launchMode="singleTask"
+             android:clearTaskOnLaunch="true"
+             android:stateNotNeeded="true"
+             android:theme="@style/LauncherTheme"
+             android:screenOrientation="unspecified"
+             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+             android:resizeableActivity="true"
+             android:resumeWhilePausing="true"
+             android:taskAffinity=""/>
 
         <!-- Content provider to settings search. The autority should be same as the packageName -->
-        <provider
-            android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
-            android:authorities="${packageName}"
-            android:grantUriPermissions="true"
-            android:multiprocess="true"
-            android:permission="android.permission.READ_SEARCH_INDEXABLES"
-            android:exported="true">
+        <provider android:name="com.android.quickstep.LauncherSearchIndexablesProvider"
+             android:authorities="${packageName}"
+             android:grantUriPermissions="true"
+             android:multiprocess="true"
+             android:permission="android.permission.READ_SEARCH_INDEXABLES"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
+                <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER"/>
             </intent-filter>
         </provider>
 
         <!-- FileProvider used for sharing images. -->
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="${packageName}.overview.fileprovider"
-            android:exported="false"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/overview_file_provider_paths" />
+        <provider android:name="androidx.core.content.FileProvider"
+             android:authorities="${packageName}.overview.fileprovider"
+             android:exported="false"
+             android:grantUriPermissions="true">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/overview_file_provider_paths"/>
         </provider>
 
-        <service
-            android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
-            tools:node="remove" />
+        <service android:name="com.android.launcher3.uioverrides.dynamicui.WallpaperManagerCompatVL$ColorExtractionService"
+             tools:node="remove"/>
 
-        <activity
-            android:name="com.android.launcher3.proxy.ProxyActivityStarter"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
-            android:launchMode="singleTask"
-            android:clearTaskOnLaunch="true"
-            android:exported="false" />
+        <activity android:name="com.android.launcher3.proxy.ProxyActivityStarter"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
+             android:launchMode="singleTask"
+             android:clearTaskOnLaunch="true"
+             android:exported="false"/>
 
-        <activity
-            android:name="com.android.quickstep.interaction.GestureSandboxActivity"
-            android:autoRemoveFromRecents="true"
-            android:excludeFromRecents="true"
-            android:screenOrientation="portrait">
+        <activity android:name="com.android.quickstep.interaction.GestureSandboxActivity"
+             android:autoRemoveFromRecents="true"
+             android:excludeFromRecents="true"
+             android:screenOrientation="portrait"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.quickstep.action.GESTURE_SANDBOX" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.quickstep.action.GESTURE_SANDBOX"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity
@@ -116,7 +110,8 @@
             android:noHistory="true"
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
-            android:permission="${packageName}.permission.HOTSEAT_EDU">
+            android:permission="${packageName}.permission.HOTSEAT_EDU"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"/>
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index 4c7943b..e4442da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -69,7 +69,6 @@
 
     // Accessed only on worker thread
     private AppPredictor mHomeAppPredictor;
-    private AppPredictor mRecentsOverviewPredictor;
 
     public PredictionAppTracker(Context context) {
         mContext = context;
@@ -97,10 +96,6 @@
             mHomeAppPredictor.destroy();
             mHomeAppPredictor = null;
         }
-        if (mRecentsOverviewPredictor != null) {
-            mRecentsOverviewPredictor.destroy();
-            mRecentsOverviewPredictor = null;
-        }
     }
 
     @WorkerThread
@@ -142,7 +137,6 @@
                 // Initialize the clients
                 int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
                 mHomeAppPredictor = createPredictor(Client.HOME, count);
-                mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count);
                 return true;
             }
             case MSG_DESTROY: {
@@ -157,12 +151,7 @@
             }
             case MSG_PREDICT: {
                 if (mHomeAppPredictor != null) {
-                    String client = (String) msg.obj;
-                    if (Client.HOME.id.equals(client)) {
-                        mHomeAppPredictor.requestPredictionUpdate();
-                    } else {
-                        mRecentsOverviewPredictor.requestPredictionUpdate();
-                    }
+                    mHomeAppPredictor.requestPredictionUpdate();
                 }
                 return true;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index a0f6b04..830c103 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -75,8 +75,7 @@
 
     // TODO (b/129421797): Update the client constants
     public enum Client {
-        HOME("home"),
-        OVERVIEW("overview");
+        HOME("home");
 
         public final String id;
 
@@ -91,10 +90,9 @@
     private final Context mContext;
 
     private final DynamicItemCache mDynamicItemCache;
-    private final List[] mPredictionServicePredictions;
+    private List mPredictionServicePredictions = Collections.emptyList();
 
     private int mMaxIconsPerRow;
-    private Client mActiveClient;
 
     private AllAppsContainerView mAppsView;
 
@@ -108,16 +106,10 @@
 
         mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated);
 
-        mActiveClient = Client.HOME;
-
         InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
         mMaxIconsPerRow = idp.numColumns;
 
         idp.addOnChangeListener(this);
-        mPredictionServicePredictions = new List[Client.values().length];
-        for (int i = 0; i < mPredictionServicePredictions.length; i++) {
-            mPredictionServicePredictions[i] = Collections.emptyList();
-        }
         mGettingValidPredictionResults = Utilities.getDevicePrefs(context)
                 .getBoolean(LAST_PREDICTION_ENABLED_STATE, true);
 
@@ -130,18 +122,6 @@
         mMaxIconsPerRow = profile.numColumns;
     }
 
-    public Client getClient() {
-        return mActiveClient;
-    }
-
-    public void switchClient(Client client) {
-        if (client == mActiveClient) {
-            return;
-        }
-        mActiveClient = client;
-        dispatchOnChange(true);
-    }
-
     public void setTargetAppsView(AllAppsContainerView appsView) {
         if (mAppsView != null) {
             mAppsView.getAppsStore().removeUpdateListener(this);
@@ -195,10 +175,8 @@
     }
 
     private void updatePredictionStateAfterCallback() {
-        boolean validResults = false;
-        for (List l : mPredictionServicePredictions) {
-            validResults |= l != null && !l.isEmpty();
-        }
+        boolean validResults = mPredictionServicePredictions != null
+                && !mPredictionServicePredictions.isEmpty();
         if (validResults != mGettingValidPredictionResults) {
             mGettingValidPredictionResults = validResults;
             Utilities.getDevicePrefs(mContext).edit()
@@ -210,7 +188,7 @@
 
     public AppPredictor.Callback appPredictorCallback(Client client) {
         return targets -> {
-            mPredictionServicePredictions[client.ordinal()] = targets;
+            mPredictionServicePredictions = targets;
             updatePredictionStateAfterCallback();
         };
     }
@@ -238,7 +216,7 @@
 
         state.apps = new ArrayList<>();
 
-        List<AppTarget> appTargets = mPredictionServicePredictions[mActiveClient.ordinal()];
+        List<AppTarget> appTargets = mPredictionServicePredictions;
         if (!appTargets.isEmpty()) {
             for (AppTarget appTarget : appTargets) {
                 ComponentKey key;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index de83caf..9310685 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -24,6 +24,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.util.Log;
 import android.view.animation.Interpolator;
 
@@ -52,17 +53,17 @@
 
     private final BaseActivityInterface<?, T> mActivityInterface;
     // The id of the currently running task that is transitioning to overview.
-    private final int mTargetTaskId;
+    private final RunningTaskInfo mTargetTask;
     private final RecentsAnimationDeviceState mDeviceState;
 
     private T mActivity;
     private RecentsView mRecentsView;
 
     AppToOverviewAnimationProvider(
-            BaseActivityInterface<?, T> activityInterface, int targetTaskId,
+            BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
             RecentsAnimationDeviceState deviceState) {
         mActivityInterface = activityInterface;
-        mTargetTaskId = targetTaskId;
+        mTargetTask = targetTask;
         mDeviceState = deviceState;
     }
 
@@ -73,7 +74,7 @@
      * @param wasVisible true if it was visible before
      */
     boolean onActivityReady(T activity, Boolean wasVisible) {
-        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
+        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
         AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
         BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
                 mDeviceState,
@@ -122,7 +123,8 @@
                 wallpaperTargets, MODE_CLOSING);
 
         // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
+                : targets.findTask(mTargetTask.taskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
             return pa.buildAnim();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index 6c4c5d3..bf03587 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -38,7 +38,6 @@
 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -313,12 +312,6 @@
         }
 
         setupRecentsViewUi();
-
-        if (mDeviceState.getNavMode() == TWO_BUTTONS) {
-            // If the device is in two button mode, swiping up will show overview with predictions
-            // so we need to kick off switching to the overview predictions as soon as possible
-            mActivityInterface.updateOverviewPredictionState();
-        }
         linkRecentsViewScroll();
 
         return true;
@@ -423,7 +416,7 @@
     }
 
     protected void notifyGestureAnimationStartToRecents() {
-        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
+        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
     }
 
     private void launcherFrameDrawn() {
@@ -451,12 +444,6 @@
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
         setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
-
-        if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
-            // In fully gestural nav mode, switch to overview predictions once the user has paused
-            // (this is a no-op if the predictions are already in that state)
-            mActivityInterface.updateOverviewPredictionState();
-        }
     }
 
     public void maybeUpdateRecentsAttachedState() {
@@ -555,14 +542,6 @@
 
     @Override
     public void updateFinalShift() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationTargets != null) {
-                LiveTileOverlay.INSTANCE.update(
-                        mTaskViewSimulator.getCurrentCropRect(),
-                        mTaskViewSimulator.getCurrentCornerRadius());
-            }
-        }
-
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
@@ -573,6 +552,14 @@
 
         updateSysUiFlags(mCurrentShift.value);
         applyWindowTransform();
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsAnimationTargets != null) {
+                LiveTileOverlay.INSTANCE.update(
+                        mTaskViewSimulator.getCurrentRect(),
+                        mTaskViewSimulator.getCurrentCornerRadius());
+            }
+        }
+
         updateLauncherTransitionProgress();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index edefbe1..6621b41 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -262,16 +262,6 @@
     }
 
     @Override
-    public void updateOverviewPredictionState() {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
-                PredictionUiStateManager.Client.OVERVIEW);
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 434a929..dca3378 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -29,7 +29,6 @@
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -165,7 +164,7 @@
             mActivityInterface = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
             mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
-                    RecentsModel.getRunningTaskId(), mDeviceState);
+                    ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
 
             // Preload the plan
             mRecentsModel.getTasks(null);
@@ -227,10 +226,6 @@
                         LauncherLogProto.ContainerType.TASKSWITCHER);
                 mUserEventLogged = true;
             }
-
-            // Switch prediction client to overview
-            PredictionUiStateManager.INSTANCE.get(activity).switchClient(
-                    PredictionUiStateManager.Client.OVERVIEW);
             return mAnimationProvider.onActivityReady(activity, wasVisible);
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 6e0b517..a3705fd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -58,6 +58,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
@@ -75,6 +76,7 @@
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -297,6 +299,7 @@
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+        mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
         mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
         ProtoTracer.INSTANCE.get(this).add(this);
 
@@ -337,6 +340,13 @@
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
+    /**
+     * Called when the one handed mode overlay package changes, to recreate touch region.
+     */
+    private void onOneHandedModeOverlayChanged(int newGesturalHeight) {
+        initInputMonitor();
+    }
+
     @UiThread
     public void onUserUnlocked() {
         mTaskAnimationManager = new TaskAnimationManager();
@@ -502,6 +512,11 @@
                 } else {
                     mUncheckedConsumer = InputConsumer.NO_OP;
                 }
+            } else if (mDeviceState.canTriggerOneHandedAction(event)
+                    && !mDeviceState.isOneHandedModeActive()) {
+                // Consume gesture event for triggering one handed feature.
+                mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
+                        InputConsumer.NO_OP, mInputMonitorCompat);
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -626,6 +641,11 @@
                 base = new ScreenPinnedInputConsumer(this, newGestureState);
             }
 
+            if (mDeviceState.canTriggerOneHandedAction(event)) {
+                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
+            }
+
             if (mDeviceState.isAccessibilityMenuAvailable()) {
                 base = new AccessibilityInputConsumer(this, mDeviceState, base,
                         mInputMonitorCompat);
@@ -634,6 +654,11 @@
             if (mDeviceState.isScreenPinningActive()) {
                 base = mResetGestureInputConsumer;
             }
+
+            if (mDeviceState.canTriggerOneHandedAction(event)) {
+                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
+            }
         }
         return base;
     }
@@ -801,6 +826,13 @@
         }
         if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
                 activity.getResources().getConfiguration().diff(newConfig))) {
+            // Since navBar gestural height are different between portrait and landscape,
+            // can handle orientation changes and refresh navigation gestural region through
+            // onOneHandedModeChanged()
+            int newGesturalHeight = ResourceUtils.getNavbarSize(
+                    ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+                    getApplicationContext().getResources());
+            mDeviceState.onOneHandedModeChanged(newGesturalHeight);
             return;
         }
 
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 d20bbe9..ffe9d6a 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
@@ -76,7 +76,7 @@
      */
     public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
         mHomeTaskInfo = homeTaskInfo;
-        onGestureAnimationStart(homeTaskInfo == null ? -1 : homeTaskInfo.taskId);
+        onGestureAnimationStart(homeTaskInfo);
     }
 
     /**
@@ -107,14 +107,15 @@
     }
 
     @Override
-    protected boolean shouldAddDummyTaskView(int runningTaskId) {
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+    protected boolean shouldAddDummyTaskView(RunningTaskInfo runningTaskInfo) {
+        if (mHomeTaskInfo != null && runningTaskInfo != null &&
+                mHomeTaskInfo.taskId == runningTaskInfo.taskId
                 && 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);
+        return super.shouldAddDummyTaskView(runningTaskInfo);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
new file mode 100644
index 0000000..0bb8fff
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 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.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Touch consumer for handling gesture event to launch one handed
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ */
+public class OneHandedModeInputConsumer extends DelegateInputConsumer {
+
+    private static final String TAG = "OneHandedModeInputConsumer";
+    private static final int ANGLE_MAX = 150;
+    private static final int ANGLE_MIN = 30;
+
+    private final Context mContext;
+    private final RecentsAnimationDeviceState mDeviceState;
+
+    private final float mDragDistThreshold;
+    private final float mSquaredSlop;
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final PointF mStartDragPos = new PointF();
+
+    private boolean mPassedSlop;
+
+    public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        super(delegate, inputMonitor);
+        mContext = context;
+        mDeviceState = deviceState;
+        mDragDistThreshold = context.getResources().getDimensionPixelSize(
+                R.dimen.gestures_onehanded_drag_threshold);
+        mSquaredSlop = Utilities.squaredTouchSlop(context);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_ONE_HANDED | mDelegate.getType();
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                break;
+            }
+            case ACTION_MOVE: {
+                if (mState == STATE_DELEGATE_ACTIVE) {
+                    break;
+                }
+                if (!mDelegate.allowInterceptByParent()) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                    break;
+                }
+
+                mLastPos.set(ev.getX(), ev.getY());
+                if (!mPassedSlop) {
+                    if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+                            > mSquaredSlop) {
+                        mStartDragPos.set(mLastPos.x, mLastPos.y);
+                        if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
+                                mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
+                                || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
+                                mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
+                            mPassedSlop = true;
+                            setActive(ev);
+                        } else {
+                            mState = STATE_DELEGATE_ACTIVE;
+                        }
+                    }
+                } else {
+                    float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
+                            mLastPos.y - mStartDragPos.y);
+                    if (distance > mDragDistThreshold && mPassedSlop) {
+                        onStopGestureDetected();
+                    }
+                }
+                break;
+            }
+            case ACTION_UP:
+            case ACTION_CANCEL: {
+                if (mLastPos.y >= mStartDragPos.y && mPassedSlop) {
+                    onStartGestureDetected();
+                }
+
+                mPassedSlop = false;
+                mState = STATE_INACTIVE;
+                break;
+            }
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private void onStartGestureDetected() {
+        if (mDeviceState.isOneHandedModeEnabled()) {
+            if (!mDeviceState.isOneHandedModeActive()) {
+                SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
+            }
+        } else if (mDeviceState.isSwipeToNotificationEnabled()) {
+            SystemUiProxy.INSTANCE.get(mContext).expandNotificationPanel();
+        }
+    }
+
+    private void onStopGestureDetected() {
+        if (!mDeviceState.isOneHandedModeEnabled() || !mDeviceState.isOneHandedModeActive()) {
+            return;
+        }
+
+        SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
+    }
+
+    private boolean isValidStartAngle(float deltaX, float deltaY) {
+        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
+    }
+
+    private boolean isValidExitAngle(float deltaX, float deltaY) {
+        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        return angle > ANGLE_MIN && angle < ANGLE_MAX;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 3cafd42..4120331 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -205,13 +205,15 @@
         ResourceProvider rp = DynamicResource.provider(v.getContext());
         float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
         float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
+        float endTransY = 0;
+        float springVelocity = Math.abs(mVelocity) * Math.signum(endTransY - mSpringTransY);
         ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
                 .setStiffness(stiffness)
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(1f)
                 .setStartValue(mSpringTransY)
-                .setEndValue(0)
-                .setStartVelocity(mVelocity)
+                .setEndValue(endTransY)
+                .setStartVelocity(springVelocity)
                 .build(v, VIEW_TRANSLATE_Y);
         springTransY.setStartDelay(startDelay);
         springTransY.addListener(new AnimatorListenerAdapter() {
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 c9ed498..64ae1e0 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
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
@@ -197,6 +198,15 @@
         return mTempRectF;
     }
 
+    /**
+     * Returns the current task bounds in the Launcher coordinate space.
+     */
+    public RectF getCurrentRect() {
+        RectF result = getCurrentCropRect();
+        mMatrix.mapRect(result);
+        return result;
+    }
+
     public RecentsOrientedState getOrientationState() {
         return mOrientationState;
     }
@@ -295,6 +305,10 @@
         builder.withMatrix(mMatrix)
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius());
+
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
+            builder.withRelativeLayerTo(params.getRecentsSurface(), Integer.MAX_VALUE);
+        }
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
index 0135f74..756331d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.util;
 
 import android.util.FloatProperty;
+import android.view.SurfaceControl;
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -58,6 +59,7 @@
     private float mCornerRadius;
     private RemoteAnimationTargets mTargetSet;
     private SurfaceTransactionApplier mSyncTransactionApplier;
+    private SurfaceControl mRecentsSurface;
 
     private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
     private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
@@ -138,6 +140,8 @@
     public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
         SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length];
+        mRecentsSurface = getRecentsSurface(targets);
+
         for (int i = 0; i < targets.unfilteredApps.length; i++) {
             RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
             SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
@@ -165,6 +169,20 @@
         return surfaceParams;
     }
 
+    private static SurfaceControl getRecentsSurface(RemoteAnimationTargets targets) {
+        for (int i = 0; i < targets.unfilteredApps.length; i++) {
+            RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
+            if (app.mode == targets.targetMode) {
+                if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS) {
+                    return app.leash.getSurfaceControl();
+                }
+            } else {
+                return app.leash.getSurfaceControl();
+            }
+        }
+        return null;
+    }
+
     // Pubic getters so outside packages can read the values.
 
     public float getProgress() {
@@ -179,6 +197,10 @@
         return mCornerRadius;
     }
 
+    public SurfaceControl getRecentsSurface() {
+        return mRecentsSurface;
+    }
+
     public RemoteAnimationTargets getTargetSet() {
         return mTargetSet;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
index 0979c07..3d44eb6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -42,8 +43,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.Themes;
@@ -139,7 +142,12 @@
         config.userControlled = false;
         AnimatorPlaybackController stateAnimationController =
                 mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, config);
-        float maxAllAppsProgress = 0.15f;
+        float maxAllAppsProgress = mLauncher.getDeviceProfile().isLandscape ? 0.35f : 0.15f;
+
+        AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+        PendingAnimation allAppsAlpha = new PendingAnimation(config.duration);
+        allAppsController.setAlphas(ALL_APPS, config, allAppsAlpha);
+        mAnimation.play(allAppsAlpha.buildAnim());
 
         ValueAnimator intro = ValueAnimator.ofFloat(0, 1f);
         intro.setInterpolator(LINEAR);
@@ -191,7 +199,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mAnimation = null;
-                stateAnimationController.dispatchOnCancel();
+                // Handles cancelling the animation used to hint towards All Apps.
+                mLauncher.getStateManager().goToState(NORMAL, false);
                 handleClose(false);
             }
         });
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 846b944..59c6815 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -41,8 +41,6 @@
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
@@ -237,9 +235,6 @@
         super.reset();
 
         setLayoutRotation(Surface.ROTATION_0, Surface.ROTATION_0);
-        // We are moving to home or some other UI with no recents. Switch back to the home client,
-        // the home predictions should have been updated when the activity was resumed.
-        PredictionUiStateManager.INSTANCE.get(getContext()).switchClient(Client.HOME);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 7b24b03..9c25b24 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
@@ -56,6 +56,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -135,6 +136,7 @@
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.LauncherEventUtil;
@@ -147,7 +149,7 @@
 /**
  * A list of recent tasks.
  */
-@TargetApi(Build.VERSION_CODES.P)
+@TargetApi(Build.VERSION_CODES.R)
 public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
         Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
         InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
@@ -1041,13 +1043,13 @@
     /**
      * Called when a gesture from an app is starting.
      */
-    public void onGestureAnimationStart(int runningTaskId) {
+    public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
             updateOrientationHandler();
         }
 
-        showCurrentTask(runningTaskId);
+        showCurrentTask(runningTaskInfo);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
@@ -1127,8 +1129,8 @@
     /**
      * Returns true if we should add a dummy taskView for the running task id
      */
-    protected boolean shouldAddDummyTaskView(int runningTaskId) {
-        return getTaskView(runningTaskId) == null;
+    protected boolean shouldAddDummyTaskView(RunningTaskInfo runningTaskInfo) {
+        return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null;
     }
 
     /**
@@ -1137,8 +1139,8 @@
      * 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 (shouldAddDummyTaskView(runningTaskId)) {
+    public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
+        if (shouldAddDummyTaskView(runningTaskInfo)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView = mTaskViewPool.getView();
@@ -1148,10 +1150,7 @@
             }
             // The temporary running task is only used for the duration between the start of the
             // gesture and the task list is loaded and applied
-            mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
-                    new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
-                    false, true, false, false, new ActivityManager.TaskDescription(), 0,
-                    new ComponentName("", ""), false);
+            mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
             taskView.bind(mTmpRunningTask, mOrientationState);
 
             // Measure and layout immediately so that the scroll values is updated instantly
@@ -1162,7 +1161,7 @@
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
-        setCurrentTask(runningTaskId);
+        setCurrentTask(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
         setCurrentPage(getRunningTaskIndex());
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
@@ -1680,7 +1679,7 @@
                 : View.LAYOUT_DIRECTION_RTL);
         mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
         mActivity.getDragLayer().recreateControllers();
-        boolean isInLandscape = mOrientationState.getTouchRotation() != 0
+        boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
                 || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
                 !mOrientationState.canRecentsActivityRotate() && isInLandscape);
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9d70316..6737c5f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -76,6 +76,10 @@
     <dimen name="gestures_assistant_drag_threshold">55dp</dimen>
     <dimen name="gestures_assistant_fling_threshold">55dp</dimen>
 
+    <!-- One-Handed Mode -->
+    <!-- Threshold for draging distance to enable one-handed mode -->
+    <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+
     <!-- Distance to move elements when swiping up to go home from launcher -->
     <dimen name="home_pullback_distance">28dp</dimen>
 
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 93b64e6..c148a4b 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.util.LauncherUIHelper.doLayout;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 
@@ -50,7 +51,7 @@
     }
 
     @Test
-    public void testRecets_showCurrentTask() {
+    public void testRecents_showCurrentTask() {
         ActivityController<RecentsActivity> controller =
                 Robolectric.buildActivity(RecentsActivity.class);
 
@@ -58,7 +59,10 @@
         doLayout(activity);
 
         FallbackRecentsView frv = activity.getOverviewPanel();
-        frv.showCurrentTask(22);
+
+        RunningTaskInfo dummyTask = new RunningTaskInfo();
+        dummyTask.taskId = 22;
+        frv.showCurrentTask(dummyTask);
         doLayout(activity);
 
         ThumbnailData thumbnailData = new ThumbnailData();
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 47ce320..6b941be 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -157,6 +157,12 @@
     @Override
     protected void onDeferredResumed() {
         super.onDeferredResumed();
+        handlePendingActivityRequest();
+    }
+
+    @Override
+    protected void handlePendingActivityRequest() {
+        super.handlePendingActivityRequest();
         if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
             // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
             onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 10f789d..2d096d1 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -62,7 +60,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Pair;
-import android.util.TypedValue;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -879,10 +876,8 @@
                             }
                         });
                     } else {
-                        float velocityDpPerS = DynamicResource.provider(mLauncher)
+                        float velocityPxPerS = DynamicResource.provider(mLauncher)
                                 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
-                        float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP,
-                                velocityDpPerS, mLauncher.getResources().getDisplayMetrics());
                         anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false)
                                 .getAnimators());
                     }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 2b698bd..bdeb3a6 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -154,13 +154,6 @@
             Runnable exitRunnable);
 
     /**
-     * Updates the prediction state to the overview state.
-     */
-    public void updateOverviewPredictionState() {
-        // By public overview predictions are not supported
-    }
-
-    /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
     public abstract int getContainerType();
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index ec720d5..67711c0 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -35,6 +35,7 @@
     int TYPE_RESET_GESTURE = 1 << 8;
     int TYPE_OVERSCROLL = 1 << 9;
     int TYPE_SYSUI_OVERLAY = 1 << 10;
+    int TYPE_ONE_HANDED = 1 << 11;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -47,7 +48,8 @@
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
             "TYPE_OVERSCROLL",              // 9
-            "TYPE_SYSUI_OVERLAY"         // 10
+            "TYPE_SYSUI_OVERLAY",           // 10
+            "TYPE_ONE_HANDED",              // 11
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 1081548..0710a0a 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -63,7 +63,9 @@
     private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
     private final RectF mAssistantLeftRegion = new RectF();
     private final RectF mAssistantRightRegion = new RectF();
+    private final RectF mOneHandedModeRegion = new RectF();
     private int mCurrentDisplayRotation;
+    private int mNavBarGesturalHeight;
     private boolean mEnableMultipleRegions;
     private Resources mResources;
     private OrientationRectF mLastRectTouched;
@@ -103,18 +105,33 @@
         mResources = resources;
         mMode = mode;
         mContractInfo = contractInfo;
+        mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+    private void refreshTouchRegion(DefaultDisplay.Info info, Resources newRes) {
+        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
+        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
+        // It tries to cache and reuse swipe regions whenever possible based only on rotation
+        mResources = newRes;
+        mSwipeTouchRegions.clear();
+        resetSwipeRegions(info);
+    }
+
+    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info,
+            Resources newRes) {
         if (mMode == newMode) {
             return;
         }
         this.mMode = newMode;
-        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
-        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
-        // It tries to cache and reuse swipe regions whenever possible based only on rotation
-        mSwipeTouchRegions.clear();
-        resetSwipeRegions(info);
+        refreshTouchRegion(info, newRes);
+    }
+
+    void setGesturalHeight(int newGesturalHeight, DefaultDisplay.Info info, Resources newRes) {
+        if (mNavBarGesturalHeight == newGesturalHeight) {
+            return;
+        }
+        mNavBarGesturalHeight = newGesturalHeight;
+        refreshTouchRegion(info, newRes);
     }
 
     /**
@@ -216,10 +233,10 @@
 
         Point size = display.realSize;
         int rotation = display.rotation;
+        int touchHeight = mNavBarGesturalHeight;
         OrientationRectF orientationRectF =
                 new OrientationRectF(0, 0, size.x, size.y, rotation);
         if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
-            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
             orientationRectF.top = orientationRectF.bottom - touchHeight;
             updateAssistantRegions(orientationRectF);
         } else {
@@ -235,10 +252,11 @@
                             + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
                     break;
                 default:
-                    orientationRectF.top = orientationRectF.bottom
-                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+                    orientationRectF.top = orientationRectF.bottom - touchHeight;
             }
         }
+        // One handed gestural only active on portrait mode
+        mOneHandedModeRegion.set(0, orientationRectF.bottom - touchHeight, size.x, size.y);
 
         return orientationRectF;
     }
@@ -264,6 +282,10 @@
 
     }
 
+    boolean touchInOneHandedModeRegion(MotionEvent ev) {
+        return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
+    }
+
     private int getNavbarSize(String resName) {
         return ResourceUtils.getNavbarSize(resName, mResources);
     }
@@ -355,6 +377,8 @@
             regions.append(rectF.mRotation).append(" ");
         }
         pw.println(regions.toString());
+        pw.println("  mNavBarGesturalHeight=" + mNavBarGesturalHeight);
+        pw.println("  mOneHandedModeRegion=" + mOneHandedModeRegion);
     }
 
     private class OrientationRectF extends RectF {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 0a70bd6..6b0c395 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -45,11 +46,14 @@
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
+import android.view.Surface;
 
 import androidx.annotation.BinderThread;
 
@@ -59,6 +63,7 @@
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -77,7 +82,8 @@
  */
 public class RecentsAnimationDeviceState implements
         NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener {
+        DefaultDisplay.DisplayInfoChangeListener,
+        OneHandedModeChangeListener {
 
     private final Context mContext;
     private final SysUINavigationMode mSysUiNavMode;
@@ -94,6 +100,8 @@
     private final Region mDeferredGestureRegion = new Region();
     private boolean mAssistantAvailable;
     private float mAssistantVisibility;
+    private boolean mIsOneHandedModeEnabled;
+    private boolean mIsSwipeToNotificationEnabled;
 
     private boolean mIsUserUnlocked;
     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
@@ -231,6 +239,22 @@
             }
         }
 
+        if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) {
+            SecureSettingsObserver oneHandedEnabledObserver =
+                    SecureSettingsObserver.newOneHandedSettingsObserver(
+                            mContext, enabled -> mIsOneHandedModeEnabled = enabled);
+            oneHandedEnabledObserver.register();
+            oneHandedEnabledObserver.dispatchOnChange();
+            runOnDestroy(oneHandedEnabledObserver::unregister);
+        }
+
+        SecureSettingsObserver swipeBottomEnabledObserver =
+                SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
+                        mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
+        swipeBottomEnabledObserver.register();
+        swipeBottomEnabledObserver.dispatchOnChange();
+        runOnDestroy(swipeBottomEnabledObserver::unregister);
+
         SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
                 context.getContentResolver(),
                 e -> mIsUserSetupComplete = e,
@@ -297,6 +321,15 @@
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
     }
 
+    /**
+     * Adds a listener for the one handed mode change,
+     * guaranteed to be called after the device state's mode has changed.
+     */
+    public void addOneHandedModeChangedCallback(OneHandedModeChangeListener listener) {
+        listener.onOneHandedModeChanged(mSysUiNavMode.addOneHandedOverlayChangeListener(listener));
+        runOnDestroy(() -> mSysUiNavMode.removeOneHandedOverlayChangeListener(listener));
+    }
+
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
         mDefaultDisplay.removeChangeListener(this);
@@ -311,7 +344,8 @@
 
         mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
 
-        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
+                mContext.getApplicationContext().getResources());
         if (!mMode.hasGestures && newMode.hasGestures) {
             setupOrientationSwipeHandler();
         } else if (mMode.hasGestures && !newMode.hasGestures){
@@ -352,6 +386,12 @@
         }
     }
 
+    @Override
+    public void onOneHandedModeChanged(int newGesturalHeight) {
+        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
+                mContext.getApplicationContext().getResources());
+    }
+
     /**
      * @return the current navigation mode for the device.
      */
@@ -564,6 +604,13 @@
     }
 
     /**
+     * @return whether screen pinning is enabled and active
+     */
+    public boolean isOneHandedModeActive() {
+        return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+    }
+
+    /**
      * Sets the region in screen space where the gestures should be deferred (ie. due to specific
      * nav bar ui).
      */
@@ -626,6 +673,32 @@
     }
 
     /**
+     * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+     *
+     * @param ev The touch screen motion event.
+     * @return whether the given motion event can trigger the one handed mode.
+     */
+    public boolean canTriggerOneHandedAction(MotionEvent ev) {
+        if (!mIsOneHandedModeEnabled && !mIsSwipeToNotificationEnabled) {
+            return false;
+        }
+
+        final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
+        return (mOrientationTouchTransformer.touchInOneHandedModeRegion(ev)
+                && displayInfo.rotation != Surface.ROTATION_90
+                && displayInfo.rotation != Surface.ROTATION_270
+                && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
+    }
+
+    public boolean isOneHandedModeEnabled() {
+        return mIsOneHandedModeEnabled;
+    }
+
+    public boolean isSwipeToNotificationEnabled() {
+        return mIsSwipeToNotificationEnabled;
+    }
+
+    /**
      * *May* apply a transform on the motion event if it lies in the nav bar region for another
      * orientation that is currently being tracked as a part of quickstep
      */
@@ -729,6 +802,8 @@
         pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
         pw.println("  displayRotation=" + getDisplayRotation());
         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
+        pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
+        pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
         mOrientationTouchTransformer.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 517501a..6f54ba2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -93,15 +93,6 @@
     }
 
     /**
-     * @return The task id of the running task, or -1 if there is no current running task.
-     */
-    public static int getRunningTaskId() {
-        ActivityManager.RunningTaskInfo runningTask =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        return runningTask != null ? runningTask.id : -1;
-    }
-
-    /**
      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
      */
     public boolean isTaskListValid(int changeId) {
@@ -140,7 +131,9 @@
         }
 
         // Keep the cache up to date with the latest thumbnails
-        int runningTaskId = RecentsModel.getRunningTaskId();
+        ActivityManager.RunningTaskInfo runningTask =
+                ActivityManagerWrapper.getInstance().getRunningTask();
+        int runningTaskId = runningTask != null ? runningTask.id : -1;
         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
             for (Task task : tasks) {
                 if (task.key.id == runningTaskId) {
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 05ce2a2..6994d66 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -16,14 +16,15 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
@@ -58,15 +59,19 @@
             new MainThreadInitializedObject<>(SysUINavigationMode::new);
 
     private static final String TAG = "SysUINavigationMode";
-
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
             "config_navBarInteractionMode";
+    private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
     private Mode mMode;
 
+    private int mNavBarGesturalHeight;
+
     private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+    private final List<OneHandedModeChangeListener> mOneHandedOverlayChangeListeners =
+            new ArrayList<>();
 
     public SysUINavigationMode(Context context) {
         mContext = context;
@@ -76,8 +81,9 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 updateMode();
+                updateGesturalHeight();
             }
-        }, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
+        }, getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
     }
 
     /** Updates navigation mode when needed. */
@@ -89,9 +95,35 @@
         }
     }
 
+    private void updateGesturalHeight() {
+        int newGesturalHeight = ResourceUtils.getDimenByName(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+                INVALID_RESOURCE_HANDLE);
+
+        if (newGesturalHeight == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return;
+        }
+
+        if (mNavBarGesturalHeight != newGesturalHeight) {
+            mNavBarGesturalHeight = newGesturalHeight;
+            dispatchOneHandedOverlayChange();
+        }
+    }
+
     private void initializeMode() {
-        int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
-        for(Mode m : Mode.values()) {
+        int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
+                mContext.getResources(), INVALID_RESOURCE_HANDLE);
+        mNavBarGesturalHeight = ResourceUtils.getDimenByName(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+                INVALID_RESOURCE_HANDLE);
+
+        if (modeInt == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return;
+        }
+
+        for (Mode m : Mode.values()) {
             if (m.resValue == modeInt) {
                 mMode = m;
             }
@@ -104,6 +136,12 @@
         }
     }
 
+    private void dispatchOneHandedOverlayChange() {
+        for (OneHandedModeChangeListener listener : mOneHandedOverlayChangeListeners) {
+            listener.onOneHandedModeChanged(mNavBarGesturalHeight);
+        }
+    }
+
     public Mode addModeChangeListener(NavigationModeChangeListener listener) {
         mChangeListeners.add(listener);
         return mMode;
@@ -113,20 +151,17 @@
         mChangeListeners.remove(listener);
     }
 
-    public Mode getMode() {
-        return mMode;
+    public int addOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+        mOneHandedOverlayChangeListeners.add(listener);
+        return mNavBarGesturalHeight;
     }
 
-    private static int getSystemIntegerRes(Context context, String resName) {
-        Resources res = context.getResources();
-        int resId = res.getIdentifier(resName, "integer", "android");
+    public void removeOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+        mOneHandedOverlayChangeListeners.remove(listener);
+    }
 
-        if (resId != 0) {
-            return res.getInteger(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return -1;
-        }
+    public Mode getMode() {
+        return mMode;
     }
 
     /** @return Whether we can remove the shelf from overview. */
@@ -144,10 +179,16 @@
     public void dump(PrintWriter pw) {
         pw.println("SysUINavigationMode:");
         pw.println("  mode=" + mMode.name());
+        pw.println("  mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
     }
 
     public interface NavigationModeChangeListener {
 
         void onNavigationModeChanged(Mode newMode);
     }
+
+    public interface OneHandedModeChangeListener {
+
+        void onOneHandedModeChanged(int newGesturalHeight);
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 299e9e5..5b239a4 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -347,6 +347,28 @@
     }
 
     @Override
+    public void startOneHandedMode() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startOneHandedMode();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startOneHandedMode", e);
+            }
+        }
+    }
+
+    @Override
+    public void stopOneHandedMode() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.stopOneHandedMode();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopOneHandedMode", e);
+            }
+        }
+    }
+
+    @Override
     public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
             Insets visibleInsets, Task.TaskKey task) {
         if (mSystemUiProxy != null) {
@@ -358,4 +380,15 @@
             }
         }
     }
+
+    @Override
+    public void expandNotificationPanel() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.expandNotificationPanel();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call expandNotificationPanel", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ae19d73..f7bd1e2 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -49,7 +49,7 @@
             Rect taskSize = new Rect();
             LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
                     orientationHandler);
-            return (dp.heightPx - taskSize.height()) / 2;
+            return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
         }
         int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
         int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
diff --git a/res/layout/launcher_preview_layout.xml b/res/layout/launcher_preview_layout.xml
index 3fd02e3..4a20c70 100644
--- a/res/layout/launcher_preview_layout.xml
+++ b/res/layout/launcher_preview_layout.xml
@@ -17,7 +17,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:focusable="false">
 
     <com.android.launcher3.CellLayout
         android:id="@+id/workspace"
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
deleted file mode 100644
index 0758148..0000000
--- a/res/values-rm/strings.xml
+++ /dev/null
@@ -1,203 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2008 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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for application_name (5181331383435256801) -->
-    <skip />
-    <!-- no translation found for home (7658288663002113681) -->
-    <skip />
-    <!-- no translation found for uid_name (7820867637514617527) -->
-    <skip />
-    <string name="folder_name" msgid="7371454440695724752"></string>
-    <!-- no translation found for activity_not_found (8071924732094499514) -->
-    <skip />
-    <!-- no translation found for widgets_tab_label (2921133187116603919) -->
-    <skip />
-    <!-- no translation found for widget_adder (3201040140710381657) -->
-    <skip />
-    <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
-    <skip />
-    <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
-    <skip />
-    <!-- no translation found for market (2619650989819296998) -->
-    <skip />
-    <!-- no translation found for widget_dims_format (2370757736025621599) -->
-    <skip />
-    <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
-    <skip />
-    <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
-    <skip />
-    <!-- no translation found for rename_folder_label (3727762225964550653) -->
-    <skip />
-    <!-- no translation found for rename_folder_title (3771389277707820891) -->
-    <skip />
-    <!-- no translation found for rename_action (5559600076028658757) -->
-    <skip />
-    <!-- no translation found for cancel_action (7009134900002915310) -->
-    <skip />
-    <!-- no translation found for menu_item_add_item (1264911265836810421) -->
-    <skip />
-    <!-- no translation found for group_applications (3797214114206693605) -->
-    <skip />
-    <!-- no translation found for group_shortcuts (6012256992764410535) -->
-    <skip />
-    <!-- no translation found for group_widgets (1569030723286851002) -->
-    <skip />
-    <!-- no translation found for completely_out_of_space (6106288382070760318) -->
-    <skip />
-    <!-- no translation found for out_of_space (4691004494942118364) -->
-    <skip />
-    <!-- no translation found for hotseat_out_of_space (7448809638125333693) -->
-    <skip />
-    <!-- no translation found for invalid_hotseat_item (5779907847267573691) -->
-    <skip />
-    <!-- no translation found for shortcut_installed (1701742129426969556) -->
-    <skip />
-    <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
-    <skip />
-    <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
-    <skip />
-    <!-- no translation found for title_select_shortcut (6680642571148153868) -->
-    <skip />
-    <!-- no translation found for title_select_application (3280812711670683644) -->
-    <skip />
-    <!-- no translation found for all_apps_button_label (9110807029020582876) -->
-    <skip />
-    <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
-    <skip />
-    <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
-    <skip />
-    <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
-    <skip />
-    <!-- no translation found for delete_target_label (1822697352535677073) -->
-    <skip />
-    <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
-    <skip />
-    <!-- no translation found for info_target_label (8053346143994679532) -->
-    <skip />
-    <!-- no translation found for accessibility_search_button (1628520399424565142) -->
-    <skip />
-    <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
-    <skip />
-    <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
-    <skip />
-    <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
-    <skip />
-    <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
-    <skip />
-    <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
-    <skip />
-    <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
-    <skip />
-    <!-- no translation found for cab_app_selection_text (374688303047985416) -->
-    <skip />
-    <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
-    <skip />
-    <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
-    <skip />
-    <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
-    <skip />
-    <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
-    <skip />
-    <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
-    <skip />
-    <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
-    <skip />
-    <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
-    <skip />
-    <!-- no translation found for permlab_read_settings (1941457408239617576) -->
-    <skip />
-    <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
-    <skip />
-    <!-- no translation found for permlab_write_settings (3574213698004620587) -->
-    <skip />
-    <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
-    <skip />
-    <!-- no translation found for gadget_error_text (6081085226050792095) -->
-    <skip />
-    <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
-    <skip />
-    <!-- no translation found for dream_name (1530253749244328964) -->
-    <skip />
-    <!-- no translation found for folder_hint_text (6617836969016293992) -->
-    <skip />
-    <!-- no translation found for workspace_description_format (2950174241104043327) -->
-    <skip />
-    <!-- no translation found for default_scroll_format (7475544710230993317) -->
-    <skip />
-    <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
-    <skip />
-    <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
-    <skip />
-    <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
-    <skip />
-    <!-- no translation found for first_run_cling_title (2459738000155917941) -->
-    <skip />
-    <!-- no translation found for first_run_cling_description (6447072552696253358) -->
-    <skip />
-    <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
-    <skip />
-    <!-- no translation found for migration_cling_title (9181776667882933767) -->
-    <skip />
-    <!-- no translation found for migration_cling_description (2752413805582227644) -->
-    <skip />
-    <!-- no translation found for migration_cling_copy_apps (946331230090919440) -->
-    <skip />
-    <!-- no translation found for migration_cling_use_default (2626475813981258626) -->
-    <skip />
-    <!-- no translation found for workspace_cling_title (5626202359865825661) -->
-    <skip />
-    <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
-    <skip />
-    <!-- no translation found for folder_cling_title (3894908818693254164) -->
-    <skip />
-    <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
-    <skip />
-    <!-- no translation found for cling_dismiss (8962359497601507581) -->
-    <skip />
-    <!-- no translation found for folder_opened (94695026776264709) -->
-    <skip />
-    <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
-    <skip />
-    <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
-    <skip />
-    <!-- no translation found for folder_closed (4100806530910930934) -->
-    <skip />
-    <!-- no translation found for folder_renamed (1794088362165669656) -->
-    <skip />
-    <!-- no translation found for folder_name_format (6629239338071103179) -->
-    <skip />
-    <!-- no translation found for widget_button_text (2880537293434387943) -->
-    <skip />
-    <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
-    <skip />
-    <!-- no translation found for settings_button_text (8119458837558863227) -->
-    <skip />
-    <!-- no translation found for package_state_enqueued (6227252464303085641) -->
-    <skip />
-    <!-- no translation found for package_state_downloading (4088770468458724721) -->
-    <skip />
-    <!-- no translation found for package_state_installing (7588193972189849870) -->
-    <skip />
-    <!-- no translation found for package_state_unknown (7592128424511031410) -->
-    <skip />
-    <!-- no translation found for package_state_error (7672093962724223588) -->
-    <skip />
-</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index ca25325..75fcc90 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -142,7 +142,7 @@
 
     <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="staggered_stiffness" type="dimen" format="float">150</item>
-    <dimen name="unlock_staggered_velocity_dp_per_s">3dp</dimen>
+    <dimen name="unlock_staggered_velocity_dp_per_s">4dp</dimen>
 
     <item name="hint_scale_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="hint_scale_stiffness" type="dimen" format="float">200</item>
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 8eceec0..41eeb78 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -183,6 +183,10 @@
     public void onScrollStateChanged(int state) {
         super.onScrollStateChanged(state);
 
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
+        }
+
         if (state == SCROLL_STATE_IDLE) {
             AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         }
@@ -192,6 +196,10 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         if (isLayoutSuppressed()) info.setScrollable(false);
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
+                    "onInitializeAccessibilityNodeInfo, scrollable: " + info.isScrollable());
+        }
     }
 
     @Override
@@ -199,8 +207,12 @@
         final boolean changing = frozen != isLayoutSuppressed();
         super.setLayoutFrozen(frozen);
         if (changing) {
-            ActivityContext.lookupContext(getContext()).getDragLayer()
-                    .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
+                        + " @ " + Log.getStackTraceString(new Throwable()));
+                ActivityContext.lookupContext(getContext()).getDragLayer()
+                        .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0970dae..d06ae7a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -923,6 +923,7 @@
         DiscoveryBounce.showForHomeIfNeeded(this);
     }
 
+    protected void handlePendingActivityRequest() { }
 
     private void logStopAndResume(int command) {
         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
@@ -1423,7 +1424,8 @@
                 if (!isInState(NORMAL)) {
                     // Only change state, if not already the same. This prevents cancelling any
                     // animations running as part of resume
-                    mStateManager.goToState(NORMAL);
+                    mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange(),
+                            this::handlePendingActivityRequest);
                 }
 
                 // Reset the apps view
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f434c91..ff4b545 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -58,7 +58,7 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
@@ -410,7 +410,7 @@
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-                final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
+                final IntSet removedIds = new IntSet();
                 synchronized (dataModel) {
                     for (ItemInfo info : dataModel.itemsIdMap) {
                         if (info instanceof WorkspaceItemInfo
@@ -418,13 +418,13 @@
                                 && user.equals(info.user)
                                 && info.getIntent() != null
                                 && TextUtils.equals(packageName, info.getIntent().getPackage())) {
-                            removedIds.put(info.id, true /* remove */);
+                            removedIds.add(info.id);
                         }
                     }
                 }
 
                 if (!removedIds.isEmpty()) {
-                    deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
+                    deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds));
                 }
             }
         });
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index c78df62..39b0f2f 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -250,7 +250,8 @@
     }
 
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
-        if (this != NORMAL || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
+        if ((this != NORMAL && this != HINT_STATE)
+                || !launcher.getDeviceProfile().shouldFadeAdjacentWorkspaceScreens()) {
             return DEFAULT_ALPHA_PROVIDER;
         }
         final int centerPage = launcher.getWorkspace().getNextPage();
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 403d779..c9fb75a 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -22,6 +22,7 @@
 
 public class ResourceUtils {
     public static final int DEFAULT_NAVBAR_VALUE = 48;
+    public static final int INVALID_RESOURCE_HANDLE = -1;
     public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
     public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
 
@@ -51,7 +52,13 @@
         return val;
     }
 
+    public static int getIntegerByName(String resName, Resources res, int defaultValue) {
+        int resId = res.getIdentifier(resName, "integer", "android");
+        return resId != 0 ? res.getInteger(resId) : defaultValue;
+    }
+
     public static int pxFromDp(float size, DisplayMetrics metrics) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+        return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index c989e7b..77b8a32 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -83,7 +83,7 @@
     protected final BaseDraggingActivity mLauncher;
     protected final AdapterHolder[] mAH;
     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
-    private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
+    private final ItemInfoMatcher mWorkMatcher = mPersonalMatcher.negate();
     private final AllAppsStore mAllAppsStore = new AllAppsStore();
 
     private final Paint mNavBarScrimPaint;
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 1d32d1d..d86bb17 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -75,6 +75,9 @@
     }
 
     public static void sendScrollFinishedEventToTest(Context context) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
+        }
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
@@ -94,6 +97,9 @@
                 AccessibilityEvent.TYPE_ANNOUNCEMENT);
         e.setClassName(eventTag);
         e.setParcelableData(data);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendEventToTest " + e);
+        }
         accessibilityManager.sendAccessibilityEvent(e);
     }
 
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d01e189..1c18402 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -241,9 +241,9 @@
         mFolderName.setSelectAllOnFocus(true);
         mFolderName.setInputType(mFolderName.getInputType()
                 & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
-                & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+                | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
                 | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
-        mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get());
+        mFolderName.forceDisableSuggestions(true);
 
         mFooter = findViewById(R.id.folder_footer);
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 9bef847..7524920 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -56,6 +56,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -348,6 +349,19 @@
         }
     }
 
+    /**
+     * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
+     * items and dynamic/predicted items for the provided {@code userHandle}.
+     * Note the call is not synchronized over the model, that should be handled by the called.
+     */
+    public void forAllWorkspaceItemInfos(UserHandle userHandle, Consumer<WorkspaceItemInfo> op) {
+        for (ItemInfo info : itemsIdMap) {
+            if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
+                op.accept((WorkspaceItemInfo) info);
+            }
+        }
+    }
+
     public interface Callbacks {
         // If the launcher has permission to access deep shortcuts.
         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 8e6b064..f644d49 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -21,7 +21,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 
 import java.util.ArrayList;
@@ -48,23 +47,18 @@
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         IconCache iconCache = app.getIconCache();
-
-
         ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
 
         synchronized (dataModel) {
-            for (ItemInfo info : dataModel.itemsIdMap) {
-                if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
-                    WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                    ComponentName cn = si.getTargetComponent();
-                    if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                            && isValidShortcut(si) && cn != null
-                            && mPackages.contains(cn.getPackageName())) {
-                        iconCache.getTitleAndIcon(si, si.usingLowResIcon());
-                        updatedShortcuts.add(si);
-                    }
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                ComponentName cn = si.getTargetComponent();
+                if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                        && isValidShortcut(si) && cn != null
+                        && mPackages.contains(cn.getPackageName())) {
+                    iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                    updatedShortcuts.add(si);
                 }
-            }
+            });
             apps.updateIconsAndLabels(mPackages, mUser);
         }
         bindUpdatedWorkspaceItems(updatedShortcuts);
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 203f1ca..8369c48 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -20,8 +20,6 @@
 import android.content.pm.PackageManager;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
@@ -70,21 +68,18 @@
 
         synchronized (dataModel) {
             final HashSet<ItemInfo> updates = new HashSet<>();
-            for (ItemInfo info : dataModel.itemsIdMap) {
-                if (info instanceof WorkspaceItemInfo) {
-                    WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                    ComponentName cn = si.getTargetComponent();
-                    if (si.hasPromiseIconUi() && (cn != null)
-                            && mInstallInfo.packageName.equals(cn.getPackageName())) {
-                        si.setInstallProgress(mInstallInfo.progress);
-                        if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) {
-                            // Mark this info as broken.
-                            si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
-                        }
-                        updates.add(si);
+            dataModel.forAllWorkspaceItemInfos(mInstallInfo.user, si -> {
+                ComponentName cn = si.getTargetComponent();
+                if (si.hasPromiseIconUi() && (cn != null)
+                        && mInstallInfo.packageName.equals(cn.getPackageName())) {
+                    si.setInstallProgress(mInstallInfo.progress);
+                    if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) {
+                        // Mark this info as broken.
+                        si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
                     }
+                    updates.add(si);
                 }
-            }
+            });
 
             for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
                 if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
@@ -94,12 +89,7 @@
             }
 
             if (!updates.isEmpty()) {
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindRestoreItemsChange(updates);
-                    }
-                });
+                scheduleCallbackTask(callbacks -> callbacks.bindRestoreItemsChange(updates));
             }
         }
     }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 7cd467e..dca4ec0 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -45,7 +45,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -92,9 +92,11 @@
 
         final String[] packages = mPackages;
         final int N = packages.length;
-        FlagOp flagOp = FlagOp.NO_OP;
+        final FlagOp flagOp;
         final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
-        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+        final ItemInfoMatcher matcher = mOp == OP_USER_AVAILABILITY_CHANGE
+                ? ItemInfoMatcher.ofUser(mUser) // We want to update all packages for this user
+                : ItemInfoMatcher.ofPackages(packageSet, mUser);
         final HashSet<ComponentName> removedComponents = new HashSet<>();
 
         switch (mOp) {
@@ -158,19 +160,22 @@
                 flagOp = ums.isUserQuiet(mUser)
                         ? FlagOp.addFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER)
                         : FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER);
-                // We want to update all packages for this user.
-                matcher = ItemInfoMatcher.ofUser(mUser);
                 appsList.updateDisabledFlags(matcher, flagOp);
 
                 // We are not synchronizing here, as int operations are atomic
                 appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
                 break;
             }
+            default:
+                flagOp = FlagOp.NO_OP;
+                break;
         }
 
         bindApplicationsIfNeeded();
 
-        final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
+        final IntSet removedShortcuts = new IntSet();
+        // Shortcuts to keep even if the corresponding app was removed
+        final IntSet forceKeepShortcuts = new IntSet();
 
         // Update shortcut infos
         if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
@@ -180,118 +185,118 @@
             // For system apps, package manager send OP_UPDATE when an app is enabled.
             final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
             synchronized (dataModel) {
-                for (ItemInfo info : dataModel.itemsIdMap) {
-                    if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
-                        WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                        boolean infoUpdated = false;
-                        boolean shortcutUpdated = false;
+                dataModel.forAllWorkspaceItemInfos(mUser, si -> {
 
-                        // Update shortcuts which use iconResource.
-                        if ((si.iconResource != null)
-                                && packageSet.contains(si.iconResource.packageName)) {
-                            LauncherIcons li = LauncherIcons.obtain(context);
-                            BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
-                            li.recycle();
-                            if (iconInfo != null) {
-                                si.bitmap = iconInfo;
-                                infoUpdated = true;
+                    boolean infoUpdated = false;
+                    boolean shortcutUpdated = false;
+
+                    // Update shortcuts which use iconResource.
+                    if ((si.iconResource != null)
+                            && packageSet.contains(si.iconResource.packageName)) {
+                        LauncherIcons li = LauncherIcons.obtain(context);
+                        BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
+                        li.recycle();
+                        if (iconInfo != null) {
+                            si.bitmap = iconInfo;
+                            infoUpdated = true;
+                        }
+                    }
+
+                    ComponentName cn = si.getTargetComponent();
+                    if (cn != null && matcher.matches(si, cn)) {
+                        String packageName = cn.getPackageName();
+
+                        if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
+                            forceKeepShortcuts.add(si.id);
+                            if (mOp == OP_REMOVE) {
+                                return;
                             }
                         }
 
-                        ComponentName cn = si.getTargetComponent();
-                        if (cn != null && matcher.matches(si, cn)) {
-                            String packageName = cn.getPackageName();
-
-                            if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
-                                removedShortcuts.put(si.id, false);
-                                if (mOp == OP_REMOVE) {
-                                    continue;
-                                }
-                            }
-
-                            if (si.isPromise() && isNewApkAvailable) {
-                                boolean isTargetValid = true;
-                                if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                                    List<ShortcutInfo> shortcut =
-                                            new ShortcutRequest(context, mUser)
-                                                    .forPackage(cn.getPackageName(),
-                                                            si.getDeepShortcutId())
-                                                    .query(ShortcutRequest.PINNED);
-                                    if (shortcut.isEmpty()) {
-                                        isTargetValid = false;
-                                    } else {
-                                        si.updateFromDeepShortcutInfo(shortcut.get(0), context);
-                                        infoUpdated = true;
-                                    }
-                                } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
-                                    isTargetValid = context.getSystemService(LauncherApps.class)
-                                            .isActivityEnabled(cn, mUser);
-                                }
-                                if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
-                                    if (updateWorkspaceItemIntent(context, si, packageName)) {
-                                        infoUpdated = true;
-                                    } else if (si.hasPromiseIconUi()) {
-                                        removedShortcuts.put(si.id, true);
-                                        continue;
-                                    }
-                                } else if (!isTargetValid) {
-                                    removedShortcuts.put(si.id, true);
-                                    FileLog.e(TAG, "Restored shortcut no longer valid "
-                                            + si.getIntent());
-                                    continue;
+                        if (si.isPromise() && isNewApkAvailable) {
+                            boolean isTargetValid = true;
+                            if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                                List<ShortcutInfo> shortcut =
+                                        new ShortcutRequest(context, mUser)
+                                                .forPackage(cn.getPackageName(),
+                                                        si.getDeepShortcutId())
+                                                .query(ShortcutRequest.PINNED);
+                                if (shortcut.isEmpty()) {
+                                    isTargetValid = false;
                                 } else {
-                                    si.status = WorkspaceItemInfo.DEFAULT;
+                                    si.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                     infoUpdated = true;
                                 }
-                            } else if (isNewApkAvailable && removedComponents.contains(cn)) {
+                            } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
+                                isTargetValid = context.getSystemService(LauncherApps.class)
+                                        .isActivityEnabled(cn, mUser);
+                            }
+                            if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
                                 if (updateWorkspaceItemIntent(context, si, packageName)) {
                                     infoUpdated = true;
+                                } else if (si.hasPromiseIconUi()) {
+                                    removedShortcuts.add(si.id);
+                                    return;
                                 }
-                            }
-
-                            if (isNewApkAvailable &&
-                                    si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
-                                iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                            } else if (!isTargetValid) {
+                                removedShortcuts.add(si.id);
+                                FileLog.e(TAG, "Restored shortcut no longer valid "
+                                        + si.getIntent());
+                                return;
+                            } else {
+                                si.status = WorkspaceItemInfo.DEFAULT;
                                 infoUpdated = true;
                             }
-
-                            int oldRuntimeFlags = si.runtimeStatusFlags;
-                            si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
-                            if (si.runtimeStatusFlags != oldRuntimeFlags) {
-                                shortcutUpdated = true;
+                        } else if (isNewApkAvailable && removedComponents.contains(cn)) {
+                            if (updateWorkspaceItemIntent(context, si, packageName)) {
+                                infoUpdated = true;
                             }
                         }
 
-                        if (infoUpdated || shortcutUpdated) {
-                            updatedWorkspaceItems.add(si);
+                        if (isNewApkAvailable
+                                && si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                            iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                            infoUpdated = true;
                         }
-                        if (infoUpdated) {
-                            getModelWriter().updateItemInDatabase(si);
-                        }
-                    } else if (info instanceof LauncherAppWidgetInfo && isNewApkAvailable) {
-                        LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
-                        if (mUser.equals(widgetInfo.user)
-                                && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                                && packageSet.contains(widgetInfo.providerName.getPackageName())) {
-                            widgetInfo.restoreStatus &=
-                                    ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
-                                            ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
 
-                            // adding this flag ensures that launcher shows 'click to setup'
-                            // if the widget has a config activity. In case there is no config
-                            // activity, it will be marked as 'restored' during bind.
-                            widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-                            widgets.add(widgetInfo);
-                            getModelWriter().updateItemInDatabase(widgetInfo);
+                        int oldRuntimeFlags = si.runtimeStatusFlags;
+                        si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
+                        if (si.runtimeStatusFlags != oldRuntimeFlags) {
+                            shortcutUpdated = true;
                         }
                     }
+
+                    if (infoUpdated || shortcutUpdated) {
+                        updatedWorkspaceItems.add(si);
+                    }
+                    if (infoUpdated && si.id != ItemInfo.NO_ID) {
+                        getModelWriter().updateItemInDatabase(si);
+                    }
+                });
+
+                for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
+                    if (mUser.equals(widgetInfo.user)
+                            && widgetInfo.hasRestoreFlag(
+                                    LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
+                            && packageSet.contains(widgetInfo.providerName.getPackageName())) {
+                        widgetInfo.restoreStatus &=
+                                ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
+                                        & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+                        // adding this flag ensures that launcher shows 'click to setup'
+                        // if the widget has a config activity. In case there is no config
+                        // activity, it will be marked as 'restored' during bind.
+                        widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+                        widgets.add(widgetInfo);
+                        getModelWriter().updateItemInDatabase(widgetInfo);
+                    }
                 }
             }
 
             bindUpdatedWorkspaceItems(updatedWorkspaceItems);
             if (!removedShortcuts.isEmpty()) {
-                deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts, false));
+                deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedShortcuts));
             }
 
             if (!widgets.isEmpty()) {
@@ -319,7 +324,7 @@
         if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
             ItemInfoMatcher removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
                     .or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
-                    .and(ItemInfoMatcher.ofItemIds(removedShortcuts, true));
+                    .and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
             deleteAndBindComponentsRemoved(removeMatch);
 
             // Remove any queued items from the install queue
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 1cbe5c2..88006ba 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -21,7 +21,6 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -58,14 +57,14 @@
         MultiHashMap<ShortcutKey, WorkspaceItemInfo> keyToShortcutInfo = new MultiHashMap<>();
         HashSet<String> allIds = new HashSet<>();
 
-        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
-            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
-                if (mPackageName.equals(si.getIntent().getPackage()) && si.user.equals(mUser)) {
+        synchronized (dataModel) {
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        && mPackageName.equals(si.getIntent().getPackage())) {
                     keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si);
                     allIds.add(si.getDeepShortcutId());
                 }
-            }
+            });
         }
 
         final ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 7ec884f..5048e13 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -23,7 +23,6 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -73,27 +72,27 @@
         ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
         HashSet<ShortcutKey> removedKeys = new HashSet<>();
 
-        for (ItemInfo itemInfo : dataModel.itemsIdMap) {
-            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                    && mUser.equals(itemInfo.user)) {
-                WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
-                if (mIsUserUnlocked) {
-                    ShortcutKey key = ShortcutKey.fromItemInfo(si);
-                    ShortcutInfo shortcut = pinnedShortcuts.get(key);
-                    // We couldn't verify the shortcut during loader. If its no longer available
-                    // (probably due to clear data), delete the workspace item as well
-                    if (shortcut == null) {
-                        removedKeys.add(key);
-                        continue;
+        synchronized (dataModel) {
+            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
+                if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                    if (mIsUserUnlocked) {
+                        ShortcutKey key = ShortcutKey.fromItemInfo(si);
+                        ShortcutInfo shortcut = pinnedShortcuts.get(key);
+                        // We couldn't verify the shortcut during loader. If its no longer available
+                        // (probably due to clear data), delete the workspace item as well
+                        if (shortcut == null) {
+                            removedKeys.add(key);
+                            return;
+                        }
+                        si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
+                        si.updateFromDeepShortcutInfo(shortcut, context);
+                        app.getIconCache().getShortcutIcon(si, shortcut);
+                    } else {
+                        si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                     }
-                    si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
-                    si.updateFromDeepShortcutInfo(shortcut, context);
-                    app.getIconCache().getShortcutIcon(si, shortcut);
-                } else {
-                    si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
+                    updatedWorkspaceItemInfos.add(si);
                 }
-                updatedWorkspaceItemInfos.add(si);
-            }
+            });
         }
         bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
         if (!removedKeys.isEmpty()) {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 38b3712..d4a132e 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.widget.WidgetsFullSheet;
 
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
@@ -92,6 +93,11 @@
                         l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
             }
 
+            case TestProtocol.REQUEST_WIDGETS_SCROLL_Y: {
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> WidgetsFullSheet.getWidgetsView(l).getCurrentScrollY());
+            }
+
             case TestProtocol.REQUEST_WINDOW_INSETS: {
                 return getUIProperty(Bundle::putParcelable, a -> {
                     WindowInsets insets = a.getWindow()
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 3ca08fd..2644db8 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -81,6 +81,7 @@
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
     public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
+    public static final String REQUEST_WIDGETS_SCROLL_Y = "widgets-scroll-y";
     public static final String REQUEST_WINDOW_INSETS = "window-insets";
     public static final String REQUEST_PID = "pid";
     public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
@@ -106,4 +107,5 @@
     public static final String PAUSE_NOT_DETECTED = "b/139891609";
     public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
     public static final String NO_SWIPE_TO_HOME = "b/158017601";
+    public static final String NO_SCROLL_END_WIDGETS = "b/160238801";
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 48c7734..c2bfb16 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.touch;
 
 import static android.widget.ListPopupWindow.WRAP_CONTENT;
+
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -33,6 +34,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -260,4 +262,10 @@
         }
         return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
     }
+
+    @SuppressWarnings("SuspiciousNameCombination")
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return rect.left;
+    }
 }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 65b1a7a..b650526 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -96,6 +96,7 @@
     int getTaskMenuWidth(View view);
     int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout);
     void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
+    int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
 
     /**
      * Maps the velocity from the coordinate plane of the foreground app to that
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 79e5c87..e87c887 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -32,6 +32,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -257,4 +258,9 @@
         }
         return new ChildBounds(childWidth, childHeight, childRight, childTop);
     }
+
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return dp.heightPx - rect.bottom;
+    }
 }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index d5ae2dc..e91f16d 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -18,9 +18,11 @@
 
 import android.content.res.Resources;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.view.Surface;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 
 public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
@@ -77,4 +79,9 @@
         view.setTranslationX(0);
         view.setTranslationY(translation);
     }
+
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return dp.widthPx - rect.right;
+    }
 }
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 4d5405d..e98af35 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -81,11 +81,10 @@
     }
 
     /**
-     * Returns a new matcher which returns the opposite boolean value of the provided
-     * {@param matcher}.
+     * Returns a new matcher with returns the opposite value of this.
      */
-    static ItemInfoMatcher not(ItemInfoMatcher matcher) {
-        return (info, cn) -> !matcher.matches(info, cn);
+    default ItemInfoMatcher negate() {
+        return (info, cn) -> !matches(info, cn);
     }
 
     static ItemInfoMatcher ofUser(UserHandle user) {
@@ -105,7 +104,10 @@
                         keys.contains(ShortcutKey.fromItemInfo(info));
     }
 
-    static ItemInfoMatcher ofItemIds(IntSparseArrayMap<Boolean> ids, Boolean matchDefault) {
-        return (info, cn) -> ids.get(info.id, matchDefault);
+    /**
+     * Returns a matcher for items with provided ids
+     */
+    static ItemInfoMatcher ofItemIds(IntSet ids) {
+        return (info, cn) -> ids.contains(info.id);
     }
 }
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index d4e074c..26313e5 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -64,7 +64,7 @@
 
     private static final Map<String, Integer> MAX_COUNTS;
     static {
-        Map<String, Integer> maxCounts = new ArrayMap<>(3);
+        Map<String, Integer> maxCounts = new ArrayMap<>(4);
         maxCounts.put(HOME_BOUNCE_COUNT, 3);
         maxCounts.put(SHELF_BOUNCE_COUNT, 3);
         maxCounts.put(ALL_APPS_COUNT, 5);
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
index 48aa02b..4b22429 100644
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -29,6 +29,11 @@
 
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
     public static final String NOTIFICATION_BADGING = "notification_badging";
+    /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+    public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+    /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+    public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+            "swipe_bottom_to_notification_enabled";
 
     private final ContentResolver mResolver;
     private final String mKeySetting;
@@ -79,4 +84,21 @@
         return new SecureSettingsObserver(
                 context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
     }
+
+    public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1);
+    }
+
+    /**
+     * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
+     * preference.
+     */
+    public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener,
+                ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
+    }
 }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index b010b4b..2c75c74 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -292,6 +292,9 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
+        }
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) {
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index df6e2c3..c9e80dc 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -20,8 +20,10 @@
 
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
 
 import java.util.ArrayList;
@@ -32,8 +34,8 @@
  * methods accordingly.
  */
 public class WidgetsDiffReporter {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "WidgetsDiffReporter";
+    private static final boolean DEBUG = Utilities.IS_RUNNING_IN_TEST_HARNESS; // b/160238801
+    private static final String TAG = TestProtocol.NO_SCROLL_END_WIDGETS;
 
     private final IconCache mIconCache;
     private final RecyclerView.Adapter mListener;
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 68a3ec5..ba55f5a 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -38,8 +39,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 
@@ -68,6 +71,14 @@
 
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 82d4110..8f81977 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -120,6 +120,9 @@
     public int getCurrentScrollY() {
         // Skip early if widgets are not bound.
         if (isModelNotReady() || getChildCount() == 0) {
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "getCurrentScrollY: -1");
+            }
             return -1;
         }
 
@@ -128,6 +131,10 @@
         int y = (child.getMeasuredHeight() * rowIndex);
         int offset = getLayoutManager().getDecoratedTop(child);
 
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
+                    "getCurrentScrollY: " + (getPaddingTop() + y - offset));
+        }
         return getPaddingTop() + y - offset;
     }
 
@@ -158,13 +165,23 @@
                     mScrollbar.isHitInParent(e.getX(), e.getY(), mFastScrollerOffset);
         }
         if (mTouchDownOnScroller) {
-            return mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+            final boolean result = mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 1 " + result);
+            }
+            return result;
+        }
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onInterceptTouchEvent 2 false");
         }
         return false;
     }
 
     @Override
     public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView.onTouchEvent");
+        }
         if (mTouchDownOnScroller) {
             mScrollbar.handleTouchEvent(e, mFastScrollerOffset);
         }
@@ -172,5 +189,31 @@
 
     @Override
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onRequestDisallowInterceptTouchEvent "
+                    + disallowIntercept);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        final boolean result = super.dispatchTouchEvent(ev);
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsRecyclerView: state: "
+                    + getScrollState()
+                    + " can scroll: " + getLayoutManager().canScrollVertically()
+                    + " result: " + result
+                    + " layout suppressed: " + isLayoutSuppressed()
+                    + " event: " + ev);
+        }
+        return result;
+    }
+
+    @Override
+    public void stopNestedScroll() {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "stopNestedScroll");
+        }
+        super.stopNestedScroll();
     }
 }
\ No newline at end of file
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 1c8f095..f243f27 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -29,6 +29,7 @@
 
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
+            android:exported="true"
             android:label="No Config">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -39,6 +40,7 @@
 
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
+            android:exported="true"
             android:label="Hidden widget">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -49,6 +51,7 @@
 
         <receiver
             android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
+            android:exported="true"
             android:label="With Config">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -58,12 +61,14 @@
         </receiver>
 
         <activity
-            android:name="com.android.launcher3.testcomponent.WidgetConfigActivity">
+            android:name="com.android.launcher3.testcomponent.WidgetConfigActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
             </intent-filter>
         </activity>
-        <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity">
+        <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.CREATE_SHORTCUT" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -72,6 +77,7 @@
         <activity
             android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
             android:icon="@drawable/test_drawable_pin_item"
+            android:exported="true"
             android:label="Test Pin Item">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -102,6 +108,7 @@
             android:stateNotNeeded="true"
             android:taskAffinity=""
             android:theme="@android:style/Theme.DeviceDefault.Light"
+            android:exported="true"
             android:windowSoftInputMode="adjustPan">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -114,6 +121,7 @@
         <activity
             android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
             android:label="LauncherTestApp"
+            android:exported="true"
             android:taskAffinity="com.android.launcher3.testcomponent.Affinity1">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -128,6 +136,7 @@
         </activity>
         <activity-alias android:name="Activity2"
                         android:label="TestActivity2"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -136,6 +145,7 @@
         </activity-alias>
         <activity-alias android:name="Activity3"
                         android:label="TestActivity3"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -144,6 +154,7 @@
         </activity-alias>
         <activity-alias android:name="Activity4"
                         android:label="TestActivity4"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -152,6 +163,7 @@
         </activity-alias>
         <activity-alias android:name="Activity5"
                         android:label="TestActivity5"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -160,6 +172,7 @@
         </activity-alias>
         <activity-alias android:name="Activity6"
                         android:label="TestActivity6"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -168,6 +181,7 @@
         </activity-alias>
         <activity-alias android:name="Activity7"
                         android:label="TestActivity7"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -176,6 +190,7 @@
         </activity-alias>
         <activity-alias android:name="Activity8"
                         android:label="TestActivity8"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -184,6 +199,7 @@
         </activity-alias>
         <activity-alias android:name="Activity9"
                         android:label="TestActivity9"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -192,6 +208,7 @@
         </activity-alias>
         <activity-alias android:name="Activity10"
                         android:label="TestActivity10"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -200,6 +217,7 @@
         </activity-alias>
         <activity-alias android:name="Activity11"
                         android:label="TestActivity11"
+                        android:exported="true"
                         android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
index f00138c..d5e2320 100644
--- a/tests/dummy_app/AndroidManifest.xml
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -26,6 +26,7 @@
         <activity
             android:name="Activity1"
             android:icon="@mipmap/ic_launcher1"
+            android:exported="true"
             android:label="Aardwolf">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
index 202dcb1..dd216c7 100644
--- a/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
+++ b/tests/src/com/android/launcher3/ui/ActivityLeakTracker.java
@@ -73,20 +73,12 @@
     }
 
     public boolean noLeakedActivities() {
-        int liveActivities = 0;
-        int destroyedActivities = 0;
-
         for (Activity activity : mActivities.keySet()) {
             if (activity.isDestroyed()) {
-                ++destroyedActivities;
-            } else {
-                ++liveActivities;
+                return false;
             }
         }
 
-        if (liveActivities > 2) return false;
-
-        // It's OK to have 1 leaked activity if no active activities exist.
-        return liveActivities == 0 ? destroyedActivities <= 1 : destroyedActivities == 0;
+        return mActivities.size() <= 2;
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f4274a8..bf079cb 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -635,9 +635,11 @@
     Parcelable executeAndWaitForEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
         try {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "executeAndWaitForEvent: before");
             final AccessibilityEvent event =
                     mInstrumentation.getUiAutomation().executeAndWaitForEvent(
                             command, eventFilter, WAIT_TIME_MS);
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "executeAndWaitForEvent: after");
             assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event);
             final Parcelable parcelableData = event.getParcelableData();
             event.recycle();
@@ -1094,7 +1096,10 @@
         executeAndWaitForEvent(
                 () -> linearGesture(
                         startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
-                event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
+                event -> {
+                    Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "scroll: received event: " + event);
+                    return TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName());
+                },
                 () -> "Didn't receive a scroll end message: " + startX + ", " + startY
                         + ", " + endX + ", " + endY);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 4440b82..ab6465c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -57,6 +57,8 @@
         while (true) {
             rawEvents = mLauncher.getTestInfo(TestProtocol.REQUEST_GET_TEST_EVENTS)
                     .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            if (rawEvents == null) return null;
+
             final int expectedCount = mExpectedEvents.entrySet()
                     .stream().mapToInt(e -> e.getValue().size()).sum();
             if (rawEvents.size() >= expectedCount
@@ -83,6 +85,7 @@
 
     String verify(long waitForExpectedCountMs, boolean successfulGesture) {
         final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs);
+        if (actualEvents == null) return "null event sequences because launcher likely died";
 
         final StringBuilder sb = new StringBuilder();
         boolean hasMismatches = false;
@@ -91,8 +94,7 @@
 
             List<String> actual = new ArrayList<>(actualEvents.getNonNull(sequence));
             final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
-            hasMismatches = hasMismatches
-                    || mismatchPosition != -1 && !ignoreMistatch(successfulGesture, sequence);
+            hasMismatches = hasMismatches || mismatchPosition != -1;
             formatSequenceWithMismatch(
                     sb,
                     sequence,
@@ -103,8 +105,7 @@
         // Check for unexpected event sequences in the actual data.
         for (String actualNamedSequence : actualEvents.keySet()) {
             if (!mExpectedEvents.containsKey(actualNamedSequence)) {
-                hasMismatches = hasMismatches
-                        || !ignoreMistatch(successfulGesture, actualNamedSequence);
+                hasMismatches = true;
                 formatSequenceWithMismatch(
                         sb,
                         actualNamedSequence,
@@ -117,13 +118,6 @@
         return hasMismatches ? "mismatching events: " + sb.toString() : null;
     }
 
-    // Workaround for b/154157191
-    private static boolean ignoreMistatch(boolean successfulGesture, String sequence) {
-        // b/156287114
-        return false;
-//        return TestProtocol.SEQUENCE_TIS.equals(sequence) && successfulGesture;
-    }
-
     // If the list of actual events matches the list of expected events, returns -1, otherwise
     // the position of the mismatch.
     private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 39ac645..49af616 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -28,6 +28,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
+import com.android.launcher3.testing.TestProtocol;
 
 import java.util.Collection;
 
@@ -90,6 +91,12 @@
         return LauncherInstrumentation.ContainerType.WIDGETS;
     }
 
+    private int getWidgetsScroll() {
+        return mLauncher.getTestInfo(
+                TestProtocol.REQUEST_WIDGETS_SCROLL_Y)
+                .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public Widget getWidget(String labelText) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
@@ -136,7 +143,13 @@
                 }
 
                 mLauncher.assertTrue("Too many attempts", ++i <= 40);
+                final int scroll = getWidgetsScroll();
                 mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
+                final int newScroll = getWidgetsScroll();
+                mLauncher.assertTrue(
+                        "Scrolled in a wrong direction in Widgets: from " + scroll + " to "
+                                + newScroll, newScroll >= scroll);
+                mLauncher.assertTrue("Unable to scroll to the widget", newScroll != scroll);
             }
         }
     }