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

Commit 9388ab9f authored by Nick Chameyev's avatar Nick Chameyev Committed by Android (Google) Code Review
Browse files

Merge "Keep tent mode when there is a screen wakelock" into main

parents e1de2df4 97d4cfbb
Loading
Loading
Loading
Loading
+53 −3
Original line number Diff line number Diff line
@@ -34,8 +34,10 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.PowerManager;
import android.util.ArraySet;
import android.util.Dumpable;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -44,6 +46,7 @@ import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScr
import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
import com.android.server.policy.feature.flags.FeatureFlags;

import java.io.PrintWriter;
import java.util.ArrayList;
@@ -62,12 +65,19 @@ import java.util.function.Supplier;
public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
        DisplayManager.DisplayListener, Dumpable {

    private static final String TAG = "BookStyleClosedStatePredicate";

    private final BookStylePreferredScreenCalculator mClosedStateCalculator;
    private final Handler mHandler = new Handler();
    private final PostureEstimator mPostureEstimator;
    private final DisplayManager mDisplayManager;
    private final PowerManager mPowerManager;
    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
@@ -92,8 +102,11 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
    public BookStyleClosedStatePredicate(@NonNull Context context,
            @NonNull ClosedStateUpdatesListener updatesListener,
            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
            @NonNull List<StateTransition> stateTransitions) {
            @NonNull List<StateTransition> stateTransitions,
            @NonNull FeatureFlags featureFlags) {
        mFeatureFlags = featureFlags;
        mDisplayManager = context.getSystemService(DisplayManager.class);
        mPowerManager = context.getSystemService(PowerManager.class);
        mDisplayManager.registerDisplayListener(this, mHandler);

        mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions);
@@ -107,6 +120,23 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
                updatesListener::onClosedStateUpdated);
    }

    /**
     * Initialize the predicate, the predicate could subscribe to various data sources the data
     * from which could be used later when calling {@link BookStyleClosedStatePredicate#test}.
     */
    public void init() {
        if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
            try {
                mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY, Runnable::run,
                        new ScreenTimeoutPolicyListener());
            } 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");
                exception.printStackTrace();
            }
        }
    }

    /**
     * Based on the current sensor readings and current state, returns true if the device should use
     * 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open
@@ -119,13 +149,24 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt

        mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);

        final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode()
                || shouldForceTentOrWedgeMode();

        final PreferredScreen preferredScreen = mClosedStateCalculator.
                calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(),
                calculatePreferredScreen(hingeAngle, isLikelyTentOrWedgeMode,
                        mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle));

        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;
@@ -163,7 +204,7 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
    @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);
    }
@@ -172,6 +213,15 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
        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
     */
+10 −7
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
        mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();

        final DeviceStatePredicateWrapper[] configuration = createConfiguration(
                leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
                leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees, featureFlags);

        mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
                hallSensor, displayManager, configuration);
@@ -122,10 +122,10 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements

    private DeviceStatePredicateWrapper[] createConfiguration(
            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
            Integer closeAngleDegrees) {
            Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
        return new DeviceStatePredicateWrapper[]{
                createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
                        closeAngleDegrees),
                        closeAngleDegrees, featureFlags),
                createConfig(getHalfOpenedDeviceState(), /* activeStatePredicate= */
                        (provider) -> {
                            final float hingeAngle = provider.getHingeAngle();
@@ -147,7 +147,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements

    private DeviceStatePredicateWrapper createClosedConfiguration(
            @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
            @Nullable Integer closeAngleDegrees) {
            @Nullable Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {

        if (closeAngleDegrees != null) {
            // Switch displays at closeAngleDegrees in both ways (folding and unfolding)
@@ -161,9 +161,12 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
        if (mEnablePostureBasedClosedState) {
            // Use smart closed state predicate that will use different switch angles
            // based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
            return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
                    new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor,
                            rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS));
            final BookStyleClosedStatePredicate predicate = new BookStyleClosedStatePredicate(
                    mContext, this, leftAccelerometerSensor, rightAccelerometerSensor,
                    DEFAULT_STATE_TRANSITIONS, featureFlags);
            return createConfig(getClosedDeviceState(),
                    /* activeStatePredicate= */ predicate,
                    /* initializer= */ predicate::init);
        }

        // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
+40 −1
Original line number Diff line number Diff line
@@ -200,6 +200,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
        }
    }

    @Override
    public void onSystemReady() {
        for (int i = 0; i < mConfigurations.length; i++) {
            final DeviceStatePredicateWrapper configuration = mConfigurations[i];
            if (configuration.mInitializer != null) {
                configuration.mInitializer.run();
            }
        }
    }

    private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) {
        if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
            throw new IllegalArgumentException("Device state configurations must have unique"
@@ -461,11 +471,12 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
        private final DeviceState mDeviceState;
        private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
        private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
        private final Runnable mInitializer;

        private DeviceStatePredicateWrapper(
                @NonNull DeviceState deviceState,
                @NonNull Predicate<FoldableDeviceStateProvider> predicate) {
            this(deviceState, predicate, ALLOWED);
            this(deviceState, predicate, ALLOWED, /* initializer= */ null);
        }

        /** Create a configuration with availability and availability predicate **/
@@ -473,10 +484,28 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
                @NonNull DeviceState deviceState,
                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
                @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
            this(deviceState, activeStatePredicate, availabilityPredicate, /* initializer= */ null);
        }

        /**
         * Create a configuration with availability and availability predicate.
         * @param deviceState specifies device state for this configuration
         * @param activeStatePredicate predicate that should return 'true' when this device state
         *                             wants to be and can be active
         * @param availabilityPredicate predicate that should return 'true' only when this device
         *                              state is allowed
         * @param initializer callback that will be called when the system is booted and ready
         */
        private DeviceStatePredicateWrapper(
                @NonNull DeviceState deviceState,
                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
                @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate,
                @Nullable Runnable initializer) {

            mDeviceState = deviceState;
            mActiveStatePredicate = activeStatePredicate;
            mAvailabilityPredicate = availabilityPredicate;
            mInitializer = initializer;
        }

        /** Create a configuration with an active state predicate **/
@@ -487,6 +516,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
            return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate);
        }

        /** Create a configuration with an active state predicate and an initializer **/
        public static DeviceStatePredicateWrapper createConfig(
                @NonNull DeviceState deviceState,
                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
                @Nullable Runnable initializer
        ) {
            return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate, ALLOWED,
                    initializer);
        }

        /** Create a configuration with availability and active state predicate **/
        public static DeviceStatePredicateWrapper createConfig(
                @NonNull DeviceState deviceState,
+10 −0
Original line number Diff line number Diff line
@@ -8,6 +8,16 @@ flag {
    bug: "278667199"
}

flag {
    name: "force_foldables_tent_mode_with_screen_wakelock"
    namespace: "windowing_frontend"
    description: "Switching displays on a foldable device later if screen wakelock is present"
    bug: "363174979"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "enable_foldables_posture_based_closed_state"
    namespace: "windowing_frontend"
+74 −2
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputSensorInfo;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.ScreenTimeoutPolicyListener;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.view.Display;
@@ -98,6 +100,8 @@ public final class BookStyleDeviceStatePolicyTest {
    @Mock
    DisplayManager mDisplayManager;
    @Mock
    PowerManager mPowerManager;
    @Mock
    private Display mDisplay;

    private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
@@ -118,6 +122,7 @@ public final class BookStyleDeviceStatePolicyTest {

    private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>();
    private DeviceStateProvider mProvider;
    private BookStyleDeviceStatePolicy mPolicy;

    @Before
    public void setup() {
@@ -125,6 +130,7 @@ public final class BookStyleDeviceStatePolicyTest {

        mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true);
        mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);

        when(mInputSensorInfo.getName()).thenReturn("hall-effect");
        mHallSensor = new Sensor(mInputSensorInfo);
@@ -146,6 +152,7 @@ public final class BookStyleDeviceStatePolicyTest {

        when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay);
        mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
        mContext.addMockSystemService(PowerManager.class, mPowerManager);

        mContext.ensureTestableResources();
        when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration);
@@ -591,6 +598,62 @@ public final class BookStyleDeviceStatePolicyTest {
        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
    }

    @Test
    public void test_unfoldTo85Degrees_screenWakeLockExists_forceTentModeWithWakeLockEnabled()
            throws Exception {
        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
        mPolicy.getDeviceStateProvider().onSystemReady();
        sendHingeAngle(0f);
        final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON);
        mProvider.setListener(mListener);
        assertLatestReportedState(DEVICE_STATE_CLOSED);
        sendHingeAngle(180f);
        assertLatestReportedState(DEVICE_STATE_OPENED);
        sendHingeAngle(0f);
        sendHingeAngle(15f);
        assertLatestReportedState(DEVICE_STATE_CLOSED);

        sendHingeAngle(85f);

        // Keeps 'closed' state meaning that it is in 'tent' mode as we have a screen wakelock
        assertLatestReportedState(DEVICE_STATE_CLOSED);
    }

    @Test
    public void test_unfoldTo85Degrees_noScreenWakelock_forceTentModeWithWakeLockEnabled()
            throws Exception {
        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
        mPolicy.getDeviceStateProvider().onSystemReady();
        sendHingeAngle(0f);
        final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_ACTIVE);
        mProvider.setListener(mListener);
        assertLatestReportedState(DEVICE_STATE_CLOSED);
        sendHingeAngle(180f);
        assertLatestReportedState(DEVICE_STATE_OPENED);
        sendHingeAngle(0f);
        assertLatestReportedState(DEVICE_STATE_CLOSED);

        sendHingeAngle(85f);

        // Switches to half-opened state as we don't have a screen wakelock
        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);
        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());

        mPolicy.getDeviceStateProvider().onSystemReady();

        verify(mPowerManager, never()).addScreenTimeoutPolicyListener(anyInt(), any(), any());
    }

    @Test
    public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() {
        sendHingeAngle(180f);
@@ -751,8 +814,17 @@ public final class BookStyleDeviceStatePolicyTest {
    }

    private DeviceStateProvider createProvider() {
        return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
        mPolicy = new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
                mHallSensor, mLeftAccelerometer, mRightAccelerometer,
                /* closeAngleDegrees= */ null).getDeviceStateProvider();
                /* closeAngleDegrees= */ null);
        return mPolicy.getDeviceStateProvider();
    }

    private ScreenTimeoutPolicyListener captureScreenTimeoutPolicyListener() {
        final ArgumentCaptor<ScreenTimeoutPolicyListener> captor = ArgumentCaptor
                .forClass(ScreenTimeoutPolicyListener.class);
        verify(mPowerManager, atLeastOnce())
                .addScreenTimeoutPolicyListener(anyInt(), any(), captor.capture());
        return captor.getValue();
    }
}