Support FoldingFeature for CONCURRENT state

Some emulated states such as CONCURRENT can be used in multiple
base/physical states (OPENED and HALF_OPENED).

This change adds support for the one-to-many mapping by:
  1) Adding COMMON_STATE_USE_BASE_STATE constant. An internal state
     where the CommonFoldingFeature.State should be derived from the
     base/physical device state.
  2) Updated DeviceStateCallback implementation which now listens to
     both onStateChanged (existing) and onBaseStateChanged (new).
     For emulated states configured as COMMON_STATE_USE_BASE_STATE,
     the base/physical state is sent to the client.

Slight code cleanup:
  1) Adds COMMON_STATE_NO_FOLDING_FEATURES which is used by some
     device configurations but not currently defined. This constant
     is used for device states where folding features do not exist.
  2) A few function/variable renames
  3) Added annotations

Bug: 268613897
Test: Using sample app, enter concurrent mode and then check
      folding features

Change-Id: I8e2ad9e52996d6e337b22a21312518bf839c5670
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 68ff806..65955b1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -33,7 +34,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-/** A representation of a folding feature for both Extension and Sidecar.
+/**
+ * A representation of a folding feature for both Extension and Sidecar.
  * For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
  * {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
  * {@link androidx.window.extensions.layout.FoldingFeature}.
@@ -67,10 +69,11 @@
     public static final int COMMON_STATE_UNKNOWN = -1;
 
     /**
-     * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
-     * and Extensions do not match exactly.
+     * A common state that contains no folding features. For example, an in-folding device in the
+     * "closed" device state.
      */
-    public static final int COMMON_STATE_FLAT = 3;
+    public static final int COMMON_STATE_NO_FOLDING_FEATURES = 1;
+
     /**
      * A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
      * Sidecar and Extensions do not match exactly.
@@ -78,9 +81,27 @@
     public static final int COMMON_STATE_HALF_OPENED = 2;
 
     /**
-     * The possible states for a folding hinge.
+     * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
+     * and Extensions do not match exactly.
      */
-    @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
+    public static final int COMMON_STATE_FLAT = 3;
+
+    /**
+     * A common state where the hinge state should be derived using the base state from
+     * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)} instead of the
+     * emulated state. This is an internal state and must not be passed to clients.
+     */
+    public static final int COMMON_STATE_USE_BASE_STATE = 1000;
+
+    /**
+     * The possible states for a folding hinge. Common in this context means normalized between
+     * extensions and sidecar.
+     */
+    @IntDef({COMMON_STATE_UNKNOWN,
+            COMMON_STATE_NO_FOLDING_FEATURES,
+            COMMON_STATE_HALF_OPENED,
+            COMMON_STATE_FLAT,
+            COMMON_STATE_USE_BASE_STATE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface State {
     }
@@ -167,7 +188,7 @@
             }
             String stateString = featureMatcher.group(6);
             stateString = stateString == null ? "" : stateString;
-            final int state;
+            @State final int state;
             switch (stateString) {
                 case PATTERN_STATE_FLAT:
                     state = COMMON_STATE_FLAT;
@@ -191,8 +212,8 @@
     @NonNull
     private final Rect mRect;
 
-    CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
-        assertValidState(state);
+    CommonFoldingFeature(int type, @State int state, @NonNull Rect rect) {
+        assertReportableState(state);
         this.mType = type;
         this.mState = state;
         if (rect.width() == 0 && rect.height() == 0) {
@@ -231,13 +252,22 @@
     }
 
     @Override
+    public String toString() {
+        return "CommonFoldingFeature=[Type: " + mType + ", state: " + mState + "]";
+    }
+
+    @Override
     public int hashCode() {
         return Objects.hash(mType, mState, mRect);
     }
 
-    private static void assertValidState(@Nullable Integer state) {
-        if (state != null && state != COMMON_STATE_FLAT
-                && state != COMMON_STATE_HALF_OPENED && state != COMMON_STATE_UNKNOWN) {
+    /**
+     * Checks if the provided folding feature state should be reported to clients. See
+     * {@link androidx.window.extensions.layout.FoldingFeature}
+     */
+    private static void assertReportableState(@State int state) {
+        if (state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED
+                && state != COMMON_STATE_UNKNOWN) {
             throw new IllegalArgumentException("Invalid state: " + state
                     + "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED");
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 0bdf98c..66f27f5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -19,6 +19,7 @@
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
 import static androidx.window.common.CommonFoldingFeature.parseListFromString;
 
 import android.annotation.NonNull;
@@ -52,13 +53,54 @@
             DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    /**
+     * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to
+     * {@link CommonFoldingFeature.State} map.
+     */
     private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
+    /**
+     * Emulated device state received via
+     * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+     * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with
+     * physical device states. They represent the state of the device when various software
+     * features and APIs are applied. The emulated states generally consist of all "base" states,
+     * but may have additional states such as "concurrent" or "rear display". Concurrent mode for
+     * example is activated via public API and can be active in both the "open" and "half folded"
+     * device states.
+     */
     private int mCurrentDeviceState = INVALID_DEVICE_STATE;
 
+    /**
+     * Base device state received via
+     * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
+     * "Base" in this context means the "physical" state of the device.
+     */
+    private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
+
     @NonNull
     private final BaseDataProducer<String> mRawFoldSupplier;
 
+    private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+        @Override
+        public void onStateChanged(int state) {
+            mCurrentDeviceState = state;
+            mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+                    .this::notifyFoldingFeatureChange);
+        }
+
+        @Override
+        public void onBaseStateChanged(int state) {
+            mCurrentBaseDeviceState = state;
+
+            if (mDeviceStateToPostureMap.get(mCurrentDeviceState)
+                    == COMMON_STATE_USE_BASE_STATE) {
+                mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+                        .this::notifyFoldingFeatureChange);
+            }
+        }
+    };
+
     public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
             @NonNull BaseDataProducer<String> rawFoldSupplier) {
         mRawFoldSupplier = rawFoldSupplier;
@@ -92,12 +134,8 @@
         }
 
         if (mDeviceStateToPostureMap.size() > 0) {
-            DeviceStateCallback deviceStateCallback = (state) -> {
-                mCurrentDeviceState = state;
-                mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
-            };
             Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
-                    .registerCallback(context.getMainExecutor(), deviceStateCallback);
+                    .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
         }
     }
 
@@ -178,11 +216,18 @@
     }
 
     private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
-        final int globalHingeState = globalHingeState();
-        return parseListFromString(displayFeaturesString, globalHingeState);
+        return parseListFromString(displayFeaturesString, currentHingeState());
     }
 
-    private int globalHingeState() {
-        return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+    @CommonFoldingFeature.State
+    private int currentHingeState() {
+        @CommonFoldingFeature.State
+        int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+
+        if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
+            posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN);
+        }
+
+        return posture;
     }
 }