Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3850324 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

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
parent f19df858
Loading
Loading
Loading
Loading
+42 −12
Original line number Diff line number Diff line
@@ -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");
        }
+54 −9
Original line number Diff line number Diff line
@@ -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<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 @@ 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<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
        final int globalHingeState = globalHingeState();
        return parseListFromString(displayFeaturesString, globalHingeState);
        return parseListFromString(displayFeaturesString, currentHingeState());
    }

    @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);
        }

    private int globalHingeState() {
        return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
        return posture;
    }
}