Flexiglass: refactor StatusBarState calculation

We keep running into bugs where we forget that we're on the keyguard or
the locked shade because the deviceUnlockStatus said we were unlocked,
so centralize the calculation into the method and be aggressive about
mapping states where we're on/over the lockscreen to KEYGUARD and
LOCKED_SHADE.

Bug: 359530769
Test: presubmit
Flag: com.android.systemui.scene_container
Change-Id: I1fa8c2e51719f0ae5ef10fe5f0e0d43db3b8a441
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index eab2762..5d14be8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -69,14 +69,11 @@
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
-import com.google.common.base.Preconditions;
-
 import dagger.Lazy;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -698,21 +695,75 @@
             SceneStack backStack,
             boolean isOccluded) {
         SceneContainerFlag.isUnexpectedlyInLegacyMode();
-        if (currentScene.equals(Scenes.Lockscreen)) {
-            if (currentOverlays.contains(Overlays.NotificationsShade) || currentOverlays.contains(
-                    Overlays.QuickSettingsShade)) {
-                return StatusBarState.SHADE_LOCKED;
+
+        final boolean onBouncer = currentScene.equals(Scenes.Bouncer);
+        final boolean onCommunal = currentScene.equals(Scenes.Communal);
+        final boolean onGone = currentScene.equals(Scenes.Gone);
+        final boolean onLockscreen = currentScene.equals(Scenes.Lockscreen);
+        final boolean onQuickSettings = currentScene.equals(Scenes.QuickSettings);
+        final boolean onShade = currentScene.equals(Scenes.Shade);
+
+        final boolean overCommunal = SceneStackKt.contains(backStack, Scenes.Communal);
+        final boolean overLockscreen = SceneStackKt.contains(backStack, Scenes.Lockscreen);
+        final boolean overShade = SceneStackKt.contains(backStack, Scenes.Shade);
+
+        final boolean overlaidShade = currentOverlays.contains(Overlays.NotificationsShade);
+        final boolean overlaidQuickSettings = currentOverlays.contains(Overlays.QuickSettingsShade);
+
+        final boolean isUnlocked = deviceUnlockStatus.isUnlocked();
+
+        final String inputLogString = "currentScene=" + currentScene.getTestTag()
+                + " currentOverlays=" + currentOverlays + " backStack=" + backStack
+                + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded;
+
+        int newState;
+
+        // When the device unlocks, several things happen 'at once':
+        // 1. deviceUnlockStatus.isUnlocked changes from false to true.
+        // 2. Lockscreen changes to Gone, either in currentScene or in backStack.
+        // 3. Bouncer is removed from currentScene or backStack, if it was present.
+        //
+        // From this function's perspective, though, deviceUnlockStatus, currentScene, and backStack
+        // each update separately, and the relative order of those updates is not well-defined. This
+        // doesn't work well for clients of this class (like remote input) that expect the device to
+        // be fully and properly unlocked when the state changes to SHADE.
+        //
+        // Therefore, we calculate the device to be in a locked-ish state (KEYGUARD or SHADE_LOCKED,
+        // but not SHADE) if *any* of these are still true:
+        // 1. deviceUnlockStatus.isUnlocked is false.
+        // 2. We are on (currentScene equals) a locked-ish scene (Lockscreen, Bouncer, or Communal).
+        // 3. We are over (backStack contains) a locked-ish scene (Lockscreen or Communal).
+
+        if (isOccluded) {
+            // Occlusion is special; even though the device is still technically on the lockscreen,
+            // the UI behaves as if it is unlocked.
+            newState = StatusBarState.SHADE;
+        } else if (onLockscreen || onBouncer || onCommunal || overLockscreen || overCommunal) {
+            // We get here if we are on or over a locked-ish scene, even if isUnlocked is true; we
+            // want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a
+            // locked-ish scene.
+            if (onShade || onQuickSettings || overShade || overlaidShade || overlaidQuickSettings) {
+                newState = StatusBarState.SHADE_LOCKED;
+            } else {
+                newState = StatusBarState.KEYGUARD;
             }
-            return StatusBarState.KEYGUARD;
+        } else if (isUnlocked || onGone) {
+            newState = StatusBarState.SHADE;
+        } else if (onShade || onQuickSettings) {
+            // We get here if deviceUnlockStatus.isUnlocked is false but we are no longer on or over
+            // a locked-ish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
+            newState = StatusBarState.SHADE_LOCKED;
+        } else {
+            throw new IllegalArgumentException(
+                    "unhandled input to calculateStateFromSceneFramework: " + inputLogString);
         }
-        if (currentScene.equals(Scenes.Shade)
-                && SceneStackKt.contains(backStack, Scenes.Lockscreen)) {
-            return StatusBarState.SHADE_LOCKED;
+
+        if (Compile.IS_DEBUG) {
+            Log.v(TAG, "calculateStateFromSceneFramework: "
+                    + inputLogString + " -> " + StatusBarState.toString(newState));
         }
-        if (deviceUnlockStatus.isUnlocked() || isOccluded) {
-            return StatusBarState.SHADE;
-        }
-        return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene));
+
+        return newState;
     }
 
     /** Notifies that the {@link StatusBarState} has changed to the given new state. */
@@ -726,15 +777,6 @@
         updateStateAndNotifyListeners(newState);
     }
 
-    private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
-            Scenes.Lockscreen, StatusBarState.KEYGUARD,
-            Scenes.Bouncer, StatusBarState.KEYGUARD,
-            Scenes.Communal, StatusBarState.KEYGUARD,
-            Scenes.Shade, StatusBarState.SHADE_LOCKED,
-            Scenes.QuickSettings, StatusBarState.SHADE_LOCKED,
-            Scenes.Gone, StatusBarState.SHADE
-    );
-
     /**
      * For keeping track of our previous state to help with debugging
      */