Fix bug with half-black screen when folding or waking with a SHOW_WHEN_LOCKED app in splitscreen

This CL partially rewrites the logic for checking if we have a SHOW_WHEN_LOCKED app when folding or waking up the device.

It dictates how splits are dismissed in two circumstances:
1) User sets Settings > Display & touch > Continue using apps on fold > Never.
2) User folds or sleeps the device when a SHOW_WHEN_LOCKED activity is focused and part of a split pair.

- Implements a new check for keyguard occlusion, and breaks split if keyguard would be occluded by an app
- Tracks the active task on device sleep (in addition to on fold), so that dismiss is carried out properly on sleep > wake without folding
- Renames a variable and adds comments for clarity.

Fixes: 330731643
Fixes: 349886679
Test: Manually verified with Calculator + other app in a variety of cases: focused, unfocused, split left, split right, etc.
Flag: EXEMPT bugfix
Change-Id: I6675dd248cc2a69d46fd259affad768684aa3f1d
diff --git a/core/java/com/android/internal/policy/FoldLockSettingsObserver.java b/core/java/com/android/internal/policy/FoldLockSettingsObserver.java
new file mode 100644
index 0000000..c6fba8a
--- /dev/null
+++ b/core/java/com/android/internal/policy/FoldLockSettingsObserver.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 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.internal.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Set;
+
+/**
+ * A ContentObserver that listens for changes in the "Continue using apps on fold" setting. This
+ * setting determines a device's behavior when the user folds the device.
+ * @hide
+ *
+ * Keep the setting values in this class in sync with the values in
+ * {@link com.android.server.utils.FoldSettingProvider} and
+ * {@link com.android.settings.display.FoldLockBehaviorSettings}
+ */
+public class FoldLockSettingsObserver extends ContentObserver {
+    /** The setting for "stay awake on fold". */
+    public static final String SETTING_VALUE_STAY_AWAKE_ON_FOLD = "stay_awake_on_fold_key";
+    /** The setting for "swipe up to continue". */
+    public static final String SETTING_VALUE_SELECTIVE_STAY_AWAKE = "selective_stay_awake_key";
+    /** The setting for "always sleep on fold". */
+    public static final String SETTING_VALUE_SLEEP_ON_FOLD = "sleep_on_fold_key";
+    public static final String SETTING_VALUE_DEFAULT = SETTING_VALUE_SELECTIVE_STAY_AWAKE;
+    private static final Set<String> SETTING_VALUES = Set.of(SETTING_VALUE_STAY_AWAKE_ON_FOLD,
+            SETTING_VALUE_SELECTIVE_STAY_AWAKE, SETTING_VALUE_SLEEP_ON_FOLD);
+
+    private final Context mContext;
+
+    /** The cached value of the setting. */
+    @VisibleForTesting
+    String mFoldLockSetting;
+
+    public FoldLockSettingsObserver(Handler handler, Context context) {
+        super(handler);
+        mContext = context;
+    }
+
+    /** Registers the observer and updates the cache for the first time. */
+    public void register() {
+        final ContentResolver r = mContext.getContentResolver();
+        r.registerContentObserver(
+                Settings.System.getUriFor(Settings.System.FOLD_LOCK_BEHAVIOR),
+                false, this, UserHandle.USER_ALL);
+        requestAndCacheFoldLockSetting();
+    }
+
+    /** Unregisters the observer. */
+    public void unregister() {
+        mContext.getContentResolver().unregisterContentObserver(this);
+    }
+
+    /** Runs when settings changes. */
+    @Override
+    public void onChange(boolean selfChange) {
+        requestAndCacheFoldLockSetting();
+    }
+
+    /**
+     * Requests and caches the current FOLD_LOCK_BEHAVIOR setting, which should be one of three
+     * values: SETTING_VALUE_STAY_AWAKE_ON_FOLD, SETTING_VALUE_SELECTIVE_STAY_AWAKE,
+     * SETTING_VALUE_SLEEP_ON_FOLD. If null (not set), returns the system default.
+     */
+    @VisibleForTesting
+    void requestAndCacheFoldLockSetting() {
+        String currentSetting = request();
+
+        if (currentSetting == null || !SETTING_VALUES.contains(currentSetting)) {
+            currentSetting = SETTING_VALUE_DEFAULT;
+        }
+
+        setCurrentFoldSetting(currentSetting);
+    }
+
+    /**
+     * Makes a binder call to request the current FOLD_LOCK_BEHAVIOR setting.
+     */
+    @VisibleForTesting
+    @Nullable
+    String request() {
+        return Settings.System.getStringForUser(mContext.getContentResolver(),
+                Settings.System.FOLD_LOCK_BEHAVIOR, UserHandle.USER_CURRENT);
+    }
+
+    /** Caches the fold-lock behavior received from Settings. */
+    @VisibleForTesting
+    void setCurrentFoldSetting(String newSetting) {
+        mFoldLockSetting = newSetting;
+    }
+
+    /** Used by external requesters: checks if the current setting is "stay awake on fold". */
+    public boolean isStayAwakeOnFold() {
+        return mFoldLockSetting.equals(SETTING_VALUE_STAY_AWAKE_ON_FOLD);
+    }
+
+    /** Used by external requesters: checks if the current setting is "swipe up to continue". */
+    public boolean isSelectiveStayAwake() {
+        return mFoldLockSetting.equals(SETTING_VALUE_SELECTIVE_STAY_AWAKE);
+    }
+
+    /** Used by external requesters: checks if the current setting is "sleep on fold". */
+    public boolean isSleepOnFold() {
+        return mFoldLockSetting.equals(SETTING_VALUE_SLEEP_ON_FOLD);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java b/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java
new file mode 100644
index 0000000..537dd69
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/policy/FoldLockSettingsObserverTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 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.internal.policy;
+
+import static com.android.internal.policy.FoldLockSettingsObserver.SETTING_VALUE_DEFAULT;
+import static com.android.internal.policy.FoldLockSettingsObserver.SETTING_VALUE_SLEEP_ON_FOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link FoldLockSettingsObserver}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FoldLockSettingsObserverTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private ContentResolver mContentResolver;
+
+    private FoldLockSettingsObserver mFoldLockSettingsObserver;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mFoldLockSettingsObserver =
+                spy(new FoldLockSettingsObserver(mHandler, mContext));
+
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+        doReturn(SETTING_VALUE_DEFAULT).when(mFoldLockSettingsObserver).request();
+
+        mFoldLockSettingsObserver.register();
+    }
+
+    @Test
+    public void shouldRegister() {
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+
+        mFoldLockSettingsObserver.register();
+
+        verify(mContentResolver).registerContentObserver(
+                Settings.System.getUriFor(Settings.System.FOLD_LOCK_BEHAVIOR),
+                false /*notifyForDescendants */,
+                mFoldLockSettingsObserver,
+                UserHandle.USER_ALL
+        );
+    }
+
+    @Test
+    public void shouldUnregister() {
+        mFoldLockSettingsObserver.unregister();
+
+        verify(mContentResolver).unregisterContentObserver(mFoldLockSettingsObserver);
+    }
+
+    @Test
+    public void shouldCacheNewValue() {
+        // Reset the mock's behavior and call count to zero.
+        reset(mFoldLockSettingsObserver);
+        doReturn(SETTING_VALUE_SLEEP_ON_FOLD).when(mFoldLockSettingsObserver).request();
+
+        // Setting is DEFAULT at first.
+        assertEquals(SETTING_VALUE_DEFAULT, mFoldLockSettingsObserver.mFoldLockSetting);
+
+        // Cache new setting.
+        mFoldLockSettingsObserver.requestAndCacheFoldLockSetting();
+
+        // Check that setter was called once and change went through properly.
+        verify(mFoldLockSettingsObserver).setCurrentFoldSetting(anyString());
+        assertTrue(mFoldLockSettingsObserver.isSleepOnFold());
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 45feff5..48e1376 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -366,13 +366,14 @@
             Optional<WindowDecorViewModel> windowDecorViewModel,
             Optional<DesktopTasksController> desktopTasksController,
             MultiInstanceHelper multiInstanceHelper,
-            @ShellMainThread ShellExecutor mainExecutor) {
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
         return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
                 transactionPool, iconProvider, recentTasks, launchAdjacentController,
                 windowDecorViewModel, desktopTasksController, null /* stageCoordinator */,
-                multiInstanceHelper, mainExecutor);
+                multiInstanceHelper, mainExecutor, mainHandler);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 8df287d..06c57bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -113,6 +113,9 @@
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
+    /** Called when device starts going to sleep (screen off). */
+    void onStartedGoingToSleep();
+
     /** Called when requested to go to fullscreen from the current active split app. */
     void goToFullscreenFromSplit();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e659151..b857556 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -50,6 +50,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
@@ -180,6 +181,7 @@
     private final LauncherApps mLauncherApps;
     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler;
     private final SplitScreenImpl mImpl = new SplitScreenImpl();
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
@@ -227,7 +229,8 @@
             Optional<DesktopTasksController> desktopTasksController,
             @Nullable StageCoordinator stageCoordinator,
             MultiInstanceHelper multiInstanceHelper,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor,
+            Handler mainHandler) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         mTaskOrganizer = shellTaskOrganizer;
@@ -236,6 +239,7 @@
         mLauncherApps = context.getSystemService(LauncherApps.class);
         mRootTDAOrganizer = rootTDAOrganizer;
         mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
@@ -292,7 +296,7 @@
         return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
-                mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController,
+                mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController,
                 mWindowDecorViewModel);
     }
 
@@ -448,13 +452,17 @@
     @Override
     public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
             boolean animatingDismiss) {
-        mStageCoordinator.onKeyguardVisibilityChanged(visible);
+        mStageCoordinator.onKeyguardStateChanged(visible, occluded);
     }
 
     public void onFinishedWakingUp() {
         mStageCoordinator.onFinishedWakingUp();
     }
 
+    public void onStartedGoingToSleep() {
+        mStageCoordinator.onStartedGoingToSleep();
+    }
+
     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
     }
@@ -1201,6 +1209,11 @@
         }
 
         @Override
+        public void onStartedGoingToSleep() {
+            mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep);
+        }
+
+        @Override
         public void goToFullscreenFromSplit() {
             mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9bcd9b0..dd7228a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -92,6 +92,7 @@
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
 import android.os.Debug;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -119,6 +120,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
+import com.android.internal.policy.FoldLockSettingsObserver;
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.launcher3.icons.IconProvider;
@@ -191,7 +193,7 @@
     private SplitLayout mSplitLayout;
     private ValueAnimator mDividerFadeInAnimator;
     private boolean mDividerVisible;
-    private boolean mKeyguardShowing;
+    private boolean mKeyguardActive;
     private boolean mShowDecorImmediately;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -205,6 +207,7 @@
     private SplitScreenTransitions mSplitTransitions;
     private final SplitscreenEventLogger mLogger;
     private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler;
     // Cache live tile tasks while entering recents, evict them from stages in finish transaction
     // if user is opening another task(s).
     private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
@@ -233,7 +236,10 @@
     private boolean mIsExiting;
     private boolean mIsRootTranslucent;
     @VisibleForTesting
-    int mTopStageAfterFoldDismiss;
+    @StageType int mLastActiveStage;
+    private boolean mBreakOnNextWake;
+    /** Used to get the Settings value for "Continue using apps on fold". */
+    private FoldLockSettingsObserver mFoldLockSettingsObserver;
 
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
@@ -313,9 +319,8 @@
             ShellTaskOrganizer taskOrganizer, DisplayController displayController,
             DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, Transitions transitions,
-            TransactionPool transactionPool,
-            IconProvider iconProvider, ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks,
+            TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor,
+            Handler mainHandler, Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
@@ -324,6 +329,7 @@
         mTaskOrganizer = taskOrganizer;
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
         mRecentTasks = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
@@ -366,6 +372,9 @@
         // With shell transition, we should update recents tile each callback so set this to true by
         // default.
         mShouldUpdateRecents = ENABLE_SHELL_TRANSITIONS;
+        mFoldLockSettingsObserver =
+                new FoldLockSettingsObserver(mainHandler, context);
+        mFoldLockSettingsObserver.register();
     }
 
     @VisibleForTesting
@@ -373,9 +382,8 @@
             ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
             DisplayController displayController, DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
-            Transitions transitions, TransactionPool transactionPool,
-            ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks,
+            Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
+            Handler mainHandler, Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
@@ -393,6 +401,7 @@
                 this::onTransitionAnimationComplete, this);
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
         mRecentTasks = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
@@ -400,6 +409,9 @@
         transitions.addHandler(this);
         mSplitUnsupportedToast = Toast.makeText(mContext,
                 R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
+        mFoldLockSettingsObserver =
+                new FoldLockSettingsObserver(context.getMainThreadHandler(), context);
+        mFoldLockSettingsObserver.register();
     }
 
     public void setMixedHandler(DefaultMixedHandler mixedHandler) {
@@ -1504,51 +1516,80 @@
         }
     }
 
-    void onKeyguardVisibilityChanged(boolean showing) {
-        mKeyguardShowing = showing;
+    /**
+     * Runs when keyguard state changes. The booleans here are a bit complicated, so for reference:
+     * @param active {@code true} if we are in a state where the keyguard *should* be shown
+     *                           -- still true when keyguard is "there" but is behind an app, or
+     *                           screen is off.
+     * @param occludingTaskRunning {@code true} when there is a running task that has
+     *                                         FLAG_SHOW_WHEN_LOCKED -- also true when the task is
+     *                                         just running on its own and keyguard is not active
+     *                                         at all.
+     */
+    void onKeyguardStateChanged(boolean active, boolean occludingTaskRunning) {
+        mKeyguardActive = active;
         if (!mMainStage.isActive()) {
             return;
         }
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing);
-        setDividerVisibility(!mKeyguardShowing, null);
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+                "onKeyguardVisibilityChanged: active=%b occludingTaskRunning=%b",
+                active, occludingTaskRunning);
+        setDividerVisibility(!mKeyguardActive, null);
+
+        if (active && occludingTaskRunning) {
+            dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        }
     }
 
     void onFinishedWakingUp() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
-        if (!mMainStage.isActive()) {
+        if (mBreakOnNextWake) {
+            dismissSplitKeepingLastActiveStage(EXIT_REASON_DEVICE_FOLDED);
+        }
+    }
+
+    void onStartedGoingToSleep() {
+        recordLastActiveStage();
+    }
+
+    /**
+     * Records the user's last focused stage -- main stage or side stage. Used to determine which
+     * stage of a split pair should be kept, in cases where system focus has moved elsewhere.
+     */
+    void recordLastActiveStage() {
+        if (!isSplitActive() || !isSplitScreenVisible()) {
+            mLastActiveStage = STAGE_TYPE_UNDEFINED;
+        } else if (mMainStage.isFocused()) {
+            mLastActiveStage = STAGE_TYPE_MAIN;
+        } else if (mSideStage.isFocused()) {
+            mLastActiveStage = STAGE_TYPE_SIDE;
+        }
+    }
+
+    /**
+     * Dismisses split, keeping the app that the user focused last in split screen. If the user was
+     * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we
+     * will do a no-op.
+     */
+    void dismissSplitKeepingLastActiveStage(@ExitReason int reason) {
+        if (!mMainStage.isActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) {
+            // no-op
             return;
         }
 
-        // Check if there's only one stage visible while keyguard occluded.
-        final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
-        final boolean oneStageVisible =
-                mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
-        if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
-            // Dismiss split because there's show-when-locked activity showing on top of keyguard.
-            // Also make sure the task contains show-when-locked activity remains on top after split
-            // dismissed.
-            final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
-            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        if (ENABLE_SHELL_TRANSITIONS) {
+            // Need manually clear here due to this transition might be aborted due to keyguard
+            // on top and lead to no visible change.
+            clearSplitPairedInRecents(reason);
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            prepareExitSplitScreen(mLastActiveStage, wct);
+            mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
+            setSplitsVisible(false);
+        } else {
+            exitSplitScreen(mLastActiveStage == STAGE_TYPE_MAIN ? mMainStage : mSideStage, reason);
         }
 
-        // Dismiss split if the flag record any side of stages.
-        if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
-            if (ENABLE_SHELL_TRANSITIONS) {
-                // Need manually clear here due to this transition might be aborted due to keyguard
-                // on top and lead to no visible change.
-                clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
-                final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
-                mSplitTransitions.startDismissTransition(wct, this,
-                        mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
-                setSplitsVisible(false);
-            } else {
-                exitSplitScreen(
-                        mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
-                        EXIT_REASON_DEVICE_FOLDED);
-            }
-            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        }
+        mBreakOnNextWake = false;
     }
 
     void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -2223,11 +2264,11 @@
 
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                 "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s",
-                visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller());
+                visible, mKeyguardActive, mIsDividerRemoteAnimating, Debug.getCaller());
 
         // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
         // dismissing animation.
-        if (visible && mKeyguardShowing) {
+        if (visible && mKeyguardActive) {
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                     "   Defer showing divider bar due to keyguard showing.");
             return;
@@ -2597,21 +2638,24 @@
     @VisibleForTesting
     void onFoldedStateChanged(boolean folded) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded);
-        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        if (!folded) return;
 
-        if (!isSplitActive() || !isSplitScreenVisible()) return;
-
-        // To avoid split dismiss when user fold the device and unfold to use later, we only
-        // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
-        // when user interact on phone folded.
-        if (mMainStage.isFocused()) {
-            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
-        } else if (mSideStage.isFocused()) {
-            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+        if (folded) {
+            recordLastActiveStage();
+            // If user folds and has the setting "Continue using apps on fold = NEVER", we assume
+            // they don't want to continue using split on the outer screen (i.e. we break split if
+            // they wake the device in its folded state).
+            mBreakOnNextWake = willSleepOnFold();
+        } else {
+            mBreakOnNextWake = false;
         }
     }
 
+    /** Returns true if the phone will sleep when it folds. */
+    @VisibleForTesting
+    boolean willSleepOnFold() {
+        return mFoldLockSettingsObserver != null && mFoldLockSettingsObserver.isSleepOnFold();
+    }
+
     private Rect getSideStageBounds() {
         return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
                 ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index e330f3a..b65e978 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -33,7 +33,6 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
@@ -88,7 +87,8 @@
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, null, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
-                Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor);
+                Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor,
+                mainHandler);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 7947691..81ca48f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -56,7 +56,7 @@
             SystemWindows systemWindows) {
         super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
-                mainExecutor, recentTasks, launchAdjacentController, Optional.empty());
+                mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty());
 
         mTvSplitMenuController = new TvSplitMenuController(context, this,
                 systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5b95b15..1c5d5e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -50,7 +50,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
 import android.window.IWindowContainerToken;
 import android.window.WindowContainerToken;
 
@@ -104,6 +104,7 @@
     @Mock SyncTransactionQueue mSyncQueue;
     @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     @Mock ShellExecutor mMainExecutor;
+    @Mock Handler mMainHandler;
     @Mock DisplayController mDisplayController;
     @Mock DisplayImeController mDisplayImeController;
     @Mock DisplayInsetsController mDisplayInsetsController;
@@ -134,7 +135,7 @@
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
                 mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController,
                 Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController),
-                mStageCoordinator, mMultiInstanceHelper, mMainExecutor));
+                mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index a3009a5..29d3fb4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
@@ -77,13 +78,13 @@
                 DisplayController displayController, DisplayImeController imeController,
                 DisplayInsetsController insetsController, SplitLayout splitLayout,
                 Transitions transitions, TransactionPool transactionPool,
-                ShellExecutor mainExecutor,
+                ShellExecutor mainExecutor, Handler mainHandler,
                 Optional<RecentTasksController> recentTasks,
                 LaunchAdjacentController launchAdjacentController,
                 Optional<WindowDecorViewModel> windowDecorViewModel) {
             super(context, displayId, syncQueue, taskOrganizer, mainStage,
                     sideStage, displayController, imeController, insetsController, splitLayout,
-                    transitions, transactionPool, mainExecutor, recentTasks,
+                    transitions, transactionPool, mainExecutor, mainHandler, recentTasks,
                     launchAdjacentController, windowDecorViewModel);
 
             // Prepare root task for testing.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 34b2eeb..37ef788 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -49,6 +49,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.os.Handler;
 import android.os.IBinder;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
@@ -107,6 +108,7 @@
     @Mock private IconProvider mIconProvider;
     @Mock private WindowDecorViewModel mWindowDecorViewModel;
     @Mock private ShellExecutor mMainExecutor;
+    @Mock private Handler mMainHandler;
     @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
     @Mock private SplitScreen.SplitInvocationListener mInvocationListener;
@@ -140,7 +142,7 @@
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
                 mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
-                mTransactionPool, mMainExecutor, Optional.empty(),
+                mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(),
                 mLaunchAdjacentController, Optional.empty());
         mStageCoordinator.setMixedHandler(mMixedHandler);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d18fec2..315332f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -138,7 +138,8 @@
         mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
-                mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty()));
+                mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController,
+                Optional.empty()));
         mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
@@ -359,10 +360,11 @@
         mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
         when(mStageCoordinator.isSplitActive()).thenReturn(true);
         when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
+        when(mStageCoordinator.willSleepOnFold()).thenReturn(true);
 
         mStageCoordinator.onFoldedStateChanged(true);
 
-        assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+        assertEquals(mStageCoordinator.mLastActiveStage, STAGE_TYPE_MAIN);
 
         mStageCoordinator.onFinishedWakingUp();
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec9b5cf..a714351 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -263,6 +263,11 @@
             public void onFinishedWakingUp() {
                 splitScreen.onFinishedWakingUp();
             }
+
+            @Override
+            public void onStartedGoingToSleep() {
+                splitScreen.onStartedGoingToSleep();
+            }
         });
         mCommandQueue.addCallback(new CommandQueue.Callbacks() {
             @Override