From c385032432eb381079e1604bca226088d7eef1e4 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Thu, 2 Mar 2023 02:12:08 +0000 Subject: [PATCH] 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 --- .../window/common/CommonFoldingFeature.java | 54 ++++++++++++---- ...iceStateManagerFoldingFeatureProducer.java | 63 ++++++++++++++++--- 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java index 68ff806c6765b..65955b1d9bcc3 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 static androidx.window.util.ExtensionHelper.isZero; 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.Objects; 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 final class CommonFoldingFeature { 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 final class CommonFoldingFeature { 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 @@ public final class CommonFoldingFeature { } 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 @@ public final class CommonFoldingFeature { @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) { @@ -230,14 +251,23 @@ public final class CommonFoldingFeature { && mRect.equals(that.mRect); } + @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 0bdf98c8680f1..66f27f517ab30 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 @@ package androidx.window.common; 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 @@ public final class DeviceStateManagerFoldingFeatureProducer 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 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 rawFoldSupplier) { mRawFoldSupplier = rawFoldSupplier; @@ -92,12 +134,8 @@ public final class DeviceStateManagerFoldingFeatureProducer } 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 @@ public final class DeviceStateManagerFoldingFeatureProducer } private List 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; } }