Re-evaluate tent/wedge mode on display rotation/wakelock changes

Currently, we re-evaluate if the device is likely in tent/wedge
mode on accelerometer/device rotation sensor changes.

We didn't do this whenever display rotates or when screen
wakelock state changes. It worked only because it piggybacked
on sensor events: when new sensor events received we also
took into account display/wakelock conditions.

But in case there are no sensor events, these conditions won't work.
Added explicit re-evaluation requests for these cases as well.

Bug: 419294518
Test: BookStyleDeviceStatePolicyTest
Flag: EXEMPT bugfix
Change-Id: I2c6f70048ae220d31ad3a3a1cbabaaa790b4aade
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index 55d23585..87ed18f 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -34,7 +34,10 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.PowerManager;
+import android.os.PowerManager.ScreenTimeoutPolicy;
+import android.os.PowerManager.ScreenTimeoutPolicyListener;
 import android.util.ArraySet;
 import android.util.Dumpable;
 import android.util.Slog;
@@ -75,9 +78,6 @@
     private final FeatureFlags mFeatureFlags;
     private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
 
-    @PowerManager.ScreenTimeoutPolicy
-    private volatile int mScreenTimeoutPolicy;
-
     /**
      * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
      * of accelerometer sensors (one for each movable part of the device), see parameter
@@ -115,7 +115,7 @@
         final Sensor orientationSensor = sensorManager.getDefaultSensor(
                 Sensor.TYPE_DEVICE_ORIENTATION);
 
-        mPostureEstimator = new PostureEstimator(mHandler, sensorManager,
+        mPostureEstimator = new PostureEstimator(mHandler, mFeatureFlags, sensorManager,
                 leftAccelerometerSensor, rightAccelerometerSensor, orientationSensor,
                 updatesListener::onClosedStateUpdated);
     }
@@ -127,8 +127,8 @@
     public void init() {
         if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
             try {
-                mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY, Runnable::run,
-                        new ScreenTimeoutPolicyListener());
+                mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY,
+                        new HandlerExecutor(mHandler), mPostureEstimator);
             } catch (IllegalStateException exception) {
                 // TODO: b/389613319 - remove after removing the screen timeout policy API flagging
                 Slog.e(TAG, "Error subscribing to the screen timeout policy changes");
@@ -149,8 +149,7 @@
 
         mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);
 
-        final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode()
-                || shouldForceTentOrWedgeMode();
+        final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode();
 
         final PreferredScreen preferredScreen = mClosedStateCalculator.
                 calculatePreferredScreen(hingeAngle, isLikelyTentOrWedgeMode,
@@ -159,14 +158,6 @@
         return preferredScreen == OUTER;
     }
 
-    private boolean shouldForceTentOrWedgeMode() {
-        if (!mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
-            return false;
-        }
-
-        return mScreenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON;
-    }
-
     private HingeAngle hingeAngleFromFloat(float hingeAngle) {
         if (hingeAngle == 0f) {
             return ANGLE_0;
@@ -204,7 +195,6 @@
     @Override
     public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
         writer.println("  " + getDumpableName());
-        writer.println("  mScreenTimeoutPolicy=" + mScreenTimeoutPolicy);
         mPostureEstimator.dump(writer, args);
         mClosedStateCalculator.dump(writer, args);
     }
@@ -213,19 +203,11 @@
         void onClosedStateUpdated();
     }
 
-    private class ScreenTimeoutPolicyListener implements
-            PowerManager.ScreenTimeoutPolicyListener {
-        @Override
-        public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) {
-            // called from the binder thread
-            mScreenTimeoutPolicy = screenTimeoutPolicy;
-        }
-    }
-
     /**
      * Estimates if the device is going to enter wedge/tent mode based on the sensor data
      */
-    private static class PostureEstimator implements SensorEventListener, Dumpable {
+    private static class PostureEstimator implements SensorEventListener,
+            ScreenTimeoutPolicyListener, Dumpable {
 
         private static final String FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY
                 = "persist.foldable_postures.wedge_inclination_threshold_degrees";
@@ -245,9 +227,10 @@
         @Nullable
         private final Sensor mRightAccelerometerSensor;
         private final Sensor mOrientationSensor;
-        private final Runnable mOnSensorUpdatedListener;
+        private final Runnable mOnEstimationChanged;
 
         private final ConditionSensorListener mConditionedSensorListener;
+        private final FeatureFlags mFeatureFlags;
 
         @Nullable
         private float[] mRightGravityVector;
@@ -261,17 +244,22 @@
         @Nullable
         private SensorEvent mLastDeviceOrientationSensorEvent = null;
 
+        @ScreenTimeoutPolicy
+        private int mScreenTimeoutPolicy = PowerManager.SCREEN_TIMEOUT_ACTIVE;
+
         private boolean mScreenTurnedOn = false;
         private boolean mDeviceClosed = false;
 
-        public PostureEstimator(Handler handler, SensorManager sensorManager,
-                @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
-                Sensor orientationSensor, Runnable onSensorUpdated) {
+        public PostureEstimator(Handler handler, FeatureFlags featureFlags,
+                SensorManager sensorManager, @Nullable Sensor leftAccelerometerSensor,
+                @Nullable Sensor rightAccelerometerSensor, Sensor orientationSensor,
+                Runnable onEstimationChanged) {
             mLeftAccelerometerSensor = leftAccelerometerSensor;
             mRightAccelerometerSensor = rightAccelerometerSensor;
             mOrientationSensor = orientationSensor;
 
-            mOnSensorUpdatedListener = onSensorUpdated;
+            mFeatureFlags = featureFlags;
+            mOnEstimationChanged = onEstimationChanged;
 
             final List<SensorSubscription> sensorSubscriptions = new ArrayList<>();
             if (mLeftAccelerometerSensor != null) {
@@ -320,7 +308,7 @@
                 mLastDeviceOrientationSensorEvent = event;
             }
 
-            mOnSensorUpdatedListener.run();
+            mOnEstimationChanged.run();
         }
 
         @Override
@@ -328,6 +316,16 @@
 
         }
 
+        /**
+         * Called from {@link BookStyleClosedStatePredicate#mHandler}'s thread
+         * (system server's main thread)
+         */
+        @Override
+        public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) {
+            mScreenTimeoutPolicy = screenTimeoutPolicy;
+            mOnEstimationChanged.run();
+        }
+
         private void setNewValueWithHighPassFilter(float[] output, float[] newValues) {
             final float alpha = GRAVITY_VECTOR_LOW_PASS_ALPHA_VALUE;
             output[0] = alpha * output[0] + (1 - alpha) * newValues[0];
@@ -345,9 +343,12 @@
         }
 
         /**
-         * Returns true if the phone is likely in tent or wedge mode when unfolding. Tent mode
-         * is detected by checking if the phone is in seascape position, screen is rotated to
-         * landscape or seascape, or if the right side of the device is mostly flat.
+         * Returns true if the phone is likely in tent or wedge mode when unfolding.
+         * Tent/wedge mode is detected by checking if:
+         *  - the phone is in seascape position
+         *  - screen is rotated to landscape or seascape
+         *  - if the right side of the device is mostly flat
+         *  - if there is an active screen wake lock
          */
         public boolean isLikelyTentOrWedgeMode() {
             boolean isScreenLandscapeOrSeascape = Objects.equals(mLastScreenRotation,
@@ -369,6 +370,11 @@
                 return true;
             }
 
+            if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()
+                    && mScreenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON) {
+                return true;
+            }
+
             return false;
         }
 
@@ -414,6 +420,7 @@
          */
         public void onDisplayRotationChanged(int rotation) {
             mLastScreenRotation = rotation;
+            mOnEstimationChanged.run();
         }
 
         /**
@@ -430,6 +437,7 @@
             writer.println("      isLikelyTentOrWedgeMode = " + isLikelyTentOrWedgeMode());
             writer.println("      mScreenTurnedOn = " + mScreenTurnedOn);
             writer.println("      mLastScreenRotation = " + mLastScreenRotation);
+            writer.println("      mScreenTimeoutPolicy=" + mScreenTimeoutPolicy);
             writer.println("      mDeviceClosed = " + mDeviceClosed);
             writer.println("      mLeftGravityVector = " + Arrays.toString(mLeftGravityVector));
             writer.println("      mRightGravityVector = " + Arrays.toString(mRightGravityVector));
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
index c25d5ef..d8c0104 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -476,6 +476,21 @@
     }
 
     @Test
+    public void test_unfoldTo30Degrees_becomesLandscapeScreenRotation_keepsClosedState() {
+        sendHingeAngle(0f);
+        sendRightSideFlatSensorEvent(false);
+        sendScreenRotation(Surface.ROTATION_0);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendScreenRotation(Surface.ROTATION_90);
+        clearInvocations(mListener);
+
+        sendHingeAngle(30f);
+
+        verify(mListener, never()).onStateChanged(mDeviceStateCaptor.capture());
+    }
+
+    @Test
     public void test_unfoldTo30Degrees_seascapeScreenRotation_keepsClosedState() {
         sendHingeAngle(0f);
         sendRightSideFlatSensorEvent(false);
@@ -644,6 +659,47 @@
     }
 
     @Test
+    public void test_unfoldTo85Degrees_afterScreenWakeLockBecomesActive_keepsClosedDeviceState()
+            throws Exception {
+        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+        mPolicy.getDeviceStateProvider().onSystemReady();
+        sendHingeAngle(0f);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON);
+
+        sendHingeAngle(15f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        sendHingeAngle(85f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+    }
+
+    @Test
+    public void test_unfoldTo85Degrees_screenWakeLockPresentAndThenRemoved_movesToHalfOpenedState()
+            throws Exception {
+        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+        mPolicy.getDeviceStateProvider().onSystemReady();
+        sendHingeAngle(0f);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON);
+        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_ACTIVE);
+
+        sendHingeAngle(15f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+
+        sendHingeAngle(85f);
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+    }
+
+    @Test
     public void test_unfoldTo85Degrees_notSubscribedToWakeLocks_forceTentModeWithWakeLockDisabled()
             throws Exception {
         mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, false);