Add DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT

Adds this new state which is the same as DEVICE_STATE_REAR_DISPLAY,
except with the additional property
PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT.

Note that this change/topic can be enabled even without the
System-UI-side change that actually displays a new dialog on the
inner display while in this mode. To the user, it will still be
exactly like the existing Rear Display Mode.

For the changes in this CL to take effect (regardless of the
SystemUI side), the corresponding changes must be done in the
device-specific overlays. Namely:

A) Add a new display layout that maps to the new device state (state 5)
   defined in BookStyleDeviceStatePolicy
B) Update the config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis
   overlays to include state 5
C) Update the config_rearDisplayDeviceStates overlay to include state 5
D) Update the config_device_state_postures overlay to include state 5
E) Enable the flag

Bug: 371095273

Flag: android.hardware.devicestate.feature.flags.device_state_rdm_v2

Test: demo app
Test: atest WindowAreaComponentImplTests
Test: atest ExtensionRearDisplayTest

BYPASS_LARGE_CHANGE_WARNING

Change-Id: I4a4559649df6c37dc183514a38010dcab698b14d
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index e583627..8b4d0da 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -172,6 +172,23 @@
      */
     public static final int PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT = 17;
 
+    /**
+     * Property that indicates that this state corresponds to the device state for rear display
+     * mode, where both the inner and outer displays are on. In this state, the outer display
+     * is the default display where the app is shown, and the inner display is used by the system to
+     * show a UI affordance for exiting the mode.
+     *
+     * Note that this value should generally not be used, and may be removed in the future (e.g.
+     * if or when it becomes the only type of rear display mode when
+     * {@link android.hardware.devicestate.feature.flags.Flags#deviceStateRdmV2} is removed).
+     *
+     * As such, clients should strongly consider relying on {@link #PROPERTY_FEATURE_REAR_DISPLAY}
+     * instead.
+     *
+     * @hide
+     */
+    public static final int PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT = 1001;
+
     /** @hide */
     @IntDef(prefix = {"PROPERTY_"}, flag = false, value = {
             PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
@@ -190,7 +207,8 @@
             PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE,
             PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY,
             PROPERTY_FEATURE_REAR_DISPLAY,
-            PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT
+            PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+            PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT
     })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 98ba9192..6230f4d 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -29,4 +29,13 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
+}
+
+flag {
+    name: "device_state_rdm_v2"
+    is_exported: true
+    namespace: "windowing_sdk"
+    description: "Enables Rear Display Mode V2, where the inner display shows the user a UI affordance for exiting the state"
+    bug: "372486634"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 4d7be39..76eb207 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -19,6 +19,7 @@
 import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@@ -104,6 +105,30 @@
     @GuardedBy("mLock")
     private int mLastReportedRearDisplayPresentationStatus;
 
+    @VisibleForTesting
+    static int getRdmV1Identifier(List<DeviceState> currentSupportedDeviceStates) {
+        for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+            DeviceState state = currentSupportedDeviceStates.get(i);
+            if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)
+                    && !state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+                return state.getIdentifier();
+            }
+        }
+        return INVALID_DEVICE_STATE_IDENTIFIER;
+    }
+
+    @VisibleForTesting
+    static int getRdmV2Identifier(List<DeviceState> currentSupportedDeviceStates) {
+        for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+            DeviceState state = currentSupportedDeviceStates.get(i);
+            if (state.hasProperties(PROPERTY_FEATURE_REAR_DISPLAY,
+                    PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+                return state.getIdentifier();
+            }
+        }
+        return INVALID_DEVICE_STATE_IDENTIFIER;
+    }
+
     public WindowAreaComponentImpl(@NonNull Context context) {
         mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
         mDisplayManager = context.getSystemService(DisplayManager.class);
@@ -112,12 +137,10 @@
         mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedDeviceStates();
 
         if (Flags.deviceStatePropertyMigration()) {
-            for (int i = 0; i < mCurrentSupportedDeviceStates.size(); i++) {
-                DeviceState state = mCurrentSupportedDeviceStates.get(i);
-                if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)) {
-                    mRearDisplayState = state.getIdentifier();
-                    break;
-                }
+            if (Flags.deviceStateRdmV2()) {
+                mRearDisplayState = getRdmV2Identifier(mCurrentSupportedDeviceStates);
+            } else {
+                mRearDisplayState = getRdmV1Identifier(mCurrentSupportedDeviceStates);
             }
         } else {
             mFoldedDeviceStates = context.getResources().getIntArray(
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
index ccb4ebe..d677fef 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
@@ -16,8 +16,13 @@
 
 package androidx.window.extensions.area;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+
 import static org.junit.Assert.assertEquals;
 
+import android.hardware.devicestate.DeviceState;
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.view.Surface;
@@ -29,11 +34,34 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WindowAreaComponentImplTests {
 
+    private static final DeviceState REAR_DISPLAY_STATE_V1 = new DeviceState(
+            new DeviceState.Configuration.Builder(1, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FEATURE_REAR_DISPLAY))
+                    .build());
+    private static final DeviceState REAR_DISPLAY_STATE_V2 = new DeviceState(
+            new DeviceState.Configuration.Builder(2, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FEATURE_REAR_DISPLAY,
+                                    PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+                    .build());
+    // The PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT state must be present together with the
+    // PROPERTY_FEATURE_REAR_DISPLAY state in order to be a valid state.
+    private static final DeviceState INVALID_REAR_DISPLAY_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(2, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+                    .build());
+
     private final DisplayMetrics mTestDisplayMetrics = new DisplayMetrics();
 
     @Before
@@ -93,4 +121,37 @@
                 Surface.ROTATION_270, Surface.ROTATION_0, mTestDisplayMetrics);
         assertEquals(expectedMetrics, mTestDisplayMetrics);
     }
+
+    @Test
+    public void testRdmV1Identifier() {
+        final List<DeviceState> supportedStates = new ArrayList<>();
+        supportedStates.add(REAR_DISPLAY_STATE_V2);
+        assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+                WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+
+        supportedStates.add(REAR_DISPLAY_STATE_V1);
+        assertEquals(REAR_DISPLAY_STATE_V1.getIdentifier(),
+                WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+    }
+
+    @Test
+    public void testRdmV2Identifier_whenStateIsImproperlyConfigured() {
+        final List<DeviceState> supportedStates = new ArrayList<>();
+        supportedStates.add(INVALID_REAR_DISPLAY_STATE);
+        assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+                WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+    }
+
+    @Test
+    public void testRdmV2Identifier_whenStateIsProperlyConfigured() {
+        final List<DeviceState> supportedStates = new ArrayList<>();
+
+        supportedStates.add(REAR_DISPLAY_STATE_V1);
+        assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+                WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+
+        supportedStates.add(REAR_DISPLAY_STATE_V2);
+        assertEquals(REAR_DISPLAY_STATE_V2.getIdentifier(),
+                WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+    }
 }
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index cc5573b..f34ec72 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -19,6 +19,7 @@
 import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
@@ -71,6 +72,7 @@
     private static final int DEVICE_STATE_OPENED = 2;
     private static final int DEVICE_STATE_REAR_DISPLAY = 3;
     private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
+    private static final int DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT = 5;
     private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
     private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
     private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
@@ -130,14 +132,17 @@
                             return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
                                     && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
                         }),
-                createConfig(getOpenedDeviceState(), /* activeStatePredicate= */
-                        ALLOWED),
-                createConfig(getRearDisplayDeviceState(), /* activeStatePredicate= */
-                        NOT_ALLOWED),
-                createConfig(getDualDisplayDeviceState(), /* activeStatePredicate= */
-                        NOT_ALLOWED, /* availabilityPredicate= */
-                        provider -> !mIsDualDisplayBlockingEnabled
-                                || provider.hasNoConnectedExternalDisplay())};
+                createConfig(getOpenedDeviceState(),
+                        /* activeStatePredicate= */ ALLOWED),
+                createConfig(getRearDisplayDeviceState(),
+                        /* activeStatePredicate= */ NOT_ALLOWED),
+                createConfig(getDualDisplayDeviceState(),
+                        /* activeStatePredicate= */ NOT_ALLOWED,
+                        /* availabilityPredicate= */ provider -> !mIsDualDisplayBlockingEnabled
+                                || provider.hasNoConnectedExternalDisplay()),
+                createConfig(getRearDisplayOuterDefaultState(),
+                        /* activeStatePredicate= */ NOT_ALLOWED)
+        };
     }
 
     private DeviceStatePredicateWrapper createClosedConfiguration(
@@ -266,4 +271,24 @@
                 .setSystemProperties(systemProperties)
                 .build());
     }
+
+    /**
+     * Returns the {link DeviceState.Configuration} that represents the new rear display state
+     * where the inner display is also enabled, showing a system affordance to exit the state.
+     */
+    @NonNull
+    private DeviceState getRearDisplayOuterDefaultState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_EMULATED_ONLY,
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                        PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+                        PROPERTY_FEATURE_REAR_DISPLAY,
+                        PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT));
+
+        return new DeviceState(new DeviceState.Configuration.Builder(
+                DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT,
+                "REAR_DISPLAY_OUTER_DEFAULT")
+                .setSystemProperties(systemProperties)
+                .build());
+    }
 }