diff options
| author | 2023-11-23 10:17:17 +0000 | |
|---|---|---|
| committer | 2023-11-23 10:17:17 +0000 | |
| commit | 7a4d10efe2ba03a87e7afe6bb0b6a4648810c6d9 (patch) | |
| tree | 1bda6f497bbabbd6eab0a85741cfb2af0b07fb9f | |
| parent | dd18a16dbfc05d1b8a8981ff005683c91f230ab4 (diff) | |
| parent | 26478200a00b77295567e922ce9100caa3cdf1d9 (diff) | |
Merge "Block dual display when an external one is added" into main
7 files changed, 355 insertions, 55 deletions
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index af33de0426b1..50ab3f8b8b6c 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -63,13 +63,27 @@ public interface DeviceStateProvider { */ int SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED = 5; + /** + * Indicating that the supported device states have changed because an external display was + * added. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED = 6; + + /** + * Indicating that the supported device states have changed because an external display was + * removed. + */ + int SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED = 7; + @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = { SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT, SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED, SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL, SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL, SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED, - SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED + SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED, + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED, + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED }) @Retention(RetentionPolicy.SOURCE) @interface SupportedStatesUpdatedReason {} diff --git a/services/foldables/devicestateprovider/Android.bp b/services/foldables/devicestateprovider/Android.bp index 34737eff8e6d..56daea772cfd 100644 --- a/services/foldables/devicestateprovider/Android.bp +++ b/services/foldables/devicestateprovider/Android.bp @@ -5,9 +5,12 @@ package { java_library { name: "foldable-device-state-provider", srcs: [ - "src/**/*.java" + "src/**/*.java", ], libs: [ "services", ], + static_libs: [ + "device_state_flags_lib", + ], } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index aea46d1ce329..d13b54304702 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -21,6 +21,7 @@ import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STA import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.TYPE_EXTERNAL; import android.annotation.IntRange; import android.annotation.NonNull; @@ -33,11 +34,14 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.os.PowerManager; import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager; import android.os.Trace; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import com.android.internal.annotations.GuardedBy; @@ -45,6 +49,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.devicestate.DeviceState; import com.android.server.devicestate.DeviceStateProvider; +import com.android.server.policy.feature.flags.FeatureFlags; +import com.android.server.policy.feature.flags.FeatureFlagsImpl; import java.util.ArrayList; import java.util.Arrays; @@ -55,14 +61,14 @@ import java.util.function.Function; /** * Device state provider for foldable devices. - * + * <p> * It is an implementation of {@link DeviceStateProvider} tailored specifically for * foldable devices and allows simple callback-based configuration with hall sensor * and hinge angle sensor values. */ public final class FoldableDeviceStateProvider implements DeviceStateProvider, SensorEventListener, PowerManager.OnThermalStatusChangedListener, - DisplayManager.DisplayListener { + DisplayManager.DisplayListener { private static final String TAG = "FoldableDeviceStateProvider"; private static final boolean DEBUG = false; @@ -77,6 +83,13 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, // are met for the device to be in the state. private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>(); + // Map of state identifier to a boolean supplier that returns true when the device state has all + // the conditions needed for availability. + private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>(); + + @GuardedBy("mLock") + private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray(); + private final Sensor mHingeAngleSensor; private final DisplayManager mDisplayManager; private final Sensor mHallSensor; @@ -99,7 +112,23 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @GuardedBy("mLock") private boolean mPowerSaveModeEnabled; - public FoldableDeviceStateProvider(@NonNull Context context, + private final boolean mIsDualDisplayBlockingEnabled; + + public FoldableDeviceStateProvider( + @NonNull Context context, + @NonNull SensorManager sensorManager, + @NonNull Sensor hingeAngleSensor, + @NonNull Sensor hallSensor, + @NonNull DisplayManager displayManager, + @NonNull DeviceStateConfiguration[] deviceStateConfigurations) { + this(new FeatureFlagsImpl(), context, sensorManager, hingeAngleSensor, hallSensor, + displayManager, deviceStateConfigurations); + } + + @VisibleForTesting + public FoldableDeviceStateProvider( + @NonNull FeatureFlags featureFlags, + @NonNull Context context, @NonNull SensorManager sensorManager, @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, @@ -112,6 +141,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, mHingeAngleSensor = hingeAngleSensor; mHallSensor = hallSensor; mDisplayManager = displayManager; + mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST); sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST); @@ -121,20 +151,15 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, final DeviceStateConfiguration configuration = deviceStateConfigurations[i]; mOrderedStates[i] = configuration.mDeviceState; - if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) { - throw new IllegalArgumentException("Device state configurations must have unique" - + " device state identifiers, found duplicated identifier: " + - configuration.mDeviceState.getIdentifier()); - } - - mStateConditions.put(configuration.mDeviceState.getIdentifier(), () -> - configuration.mPredicate.apply(this)); + assertUniqueDeviceStateIdentifier(configuration); + initialiseStateConditions(configuration); + initialiseStateAvailabilityConditions(configuration); } + Handler handler = new Handler(Looper.getMainLooper()); mDisplayManager.registerDisplayListener( /* listener = */ this, - /* handler= */ null, - /* eventsMask= */ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + /* handler= */ handler); Arrays.sort(mOrderedStates, Comparator.comparingInt(DeviceState::getIdentifier)); @@ -167,6 +192,26 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } } + private void assertUniqueDeviceStateIdentifier(DeviceStateConfiguration configuration) { + if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) { + throw new IllegalArgumentException("Device state configurations must have unique" + + " device state identifiers, found duplicated identifier: " + + configuration.mDeviceState.getIdentifier()); + } + } + + private void initialiseStateConditions(DeviceStateConfiguration configuration) { + mStateConditions.put(configuration.mDeviceState.getIdentifier(), () -> + configuration.mActiveStatePredicate.apply(this)); + } + + private void initialiseStateAvailabilityConditions(DeviceStateConfiguration configuration) { + if (configuration.mAvailabilityPredicate != null) { + mStateAvailabilityConditions.put(configuration.mDeviceState.getIdentifier(), () -> + configuration.mAvailabilityPredicate.apply(this)); + } + } + @Override public void setListener(Listener listener) { synchronized (mLock) { @@ -189,16 +234,9 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } listener = mListener; for (DeviceState deviceState : mOrderedStates) { - if (isThermalStatusCriticalOrAbove(mThermalStatus) - && deviceState.hasFlag( - DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { - continue; - } - if (mPowerSaveModeEnabled && deviceState.hasFlag( - DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { - continue; + if (isStateSupported(deviceState)) { + supportedStates.add(deviceState); } - supportedStates.add(deviceState); } } @@ -206,6 +244,26 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, supportedStates.toArray(new DeviceState[supportedStates.size()]), reason); } + @GuardedBy("mLock") + private boolean isStateSupported(DeviceState deviceState) { + if (isThermalStatusCriticalOrAbove(mThermalStatus) + && deviceState.hasFlag( + DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) { + return false; + } + if (mPowerSaveModeEnabled && deviceState.hasFlag( + DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) { + return false; + } + if (mIsDualDisplayBlockingEnabled + && mStateAvailabilityConditions.contains(deviceState.getIdentifier())) { + return mStateAvailabilityConditions + .get(deviceState.getIdentifier()) + .getAsBoolean(); + } + return true; + } + /** Computes the current device state and notifies the listener of a change, if needed. */ void notifyDeviceStateChangedIfNeeded() { int stateToReport = INVALID_DEVICE_STATE; @@ -294,7 +352,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, private void dumpSensorValues() { Slog.i(TAG, "Sensor values:"); dumpSensorValues("Hall Sensor", mHallSensor, mLastHallSensorEvent); - dumpSensorValues("Hinge Angle Sensor",mHingeAngleSensor, mLastHingeAngleSensorEvent); + dumpSensorValues("Hinge Angle Sensor", mHingeAngleSensor, mLastHingeAngleSensorEvent); Slog.i(TAG, "isScreenOn: " + isScreenOn()); } @@ -307,12 +365,35 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, @Override public void onDisplayAdded(int displayId) { - + // TODO(b/312397262): consider virtual displays cases + synchronized (mLock) { + if (mIsDualDisplayBlockingEnabled + && !mExternalDisplaysConnected.get(displayId, false) + && mDisplayManager.getDisplay(displayId).getType() == TYPE_EXTERNAL) { + mExternalDisplaysConnected.put(displayId, true); + + // Only update the supported state when going from 0 external display to 1 + if (mExternalDisplaysConnected.size() == 1) { + notifySupportedStatesChanged( + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED); + } + } + } } @Override public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + if (mIsDualDisplayBlockingEnabled && mExternalDisplaysConnected.get(displayId, false)) { + mExternalDisplaysConnected.delete(displayId); + // Only update the supported states when going from 1 external display to 0 + if (mExternalDisplaysConnected.size() == 0) { + notifySupportedStatesChanged( + SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED); + } + } + } } @Override @@ -338,12 +419,22 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, */ public static class DeviceStateConfiguration { private final DeviceState mDeviceState; - private final Function<FoldableDeviceStateProvider, Boolean> mPredicate; + private final Function<FoldableDeviceStateProvider, Boolean> mActiveStatePredicate; + private final Function<FoldableDeviceStateProvider, Boolean> mAvailabilityPredicate; - private DeviceStateConfiguration(DeviceState deviceState, + private DeviceStateConfiguration( + DeviceState deviceState, Function<FoldableDeviceStateProvider, Boolean> predicate) { + this(deviceState, predicate, null); + } + + private DeviceStateConfiguration( + DeviceState deviceState, + Function<FoldableDeviceStateProvider, Boolean> activeStatePredicate, + Function<FoldableDeviceStateProvider, Boolean> availabilityPredicate) { mDeviceState = deviceState; - mPredicate = predicate; + mActiveStatePredicate = activeStatePredicate; + mAvailabilityPredicate = availabilityPredicate; } public static DeviceStateConfiguration createConfig( @@ -365,21 +456,33 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, predicate); } + /** Create a configuration with availability predicate **/ + public static DeviceStateConfiguration createConfig( + @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, + @NonNull String name, + @DeviceState.DeviceStateFlags int flags, + Function<FoldableDeviceStateProvider, Boolean> predicate, + Function<FoldableDeviceStateProvider, Boolean> availabilityPredicate + ) { + return new DeviceStateConfiguration(new DeviceState(identifier, name, flags), + predicate, availabilityPredicate); + } + /** * Creates a device state configuration for a closed tent-mode aware state. - * + * <p> * During tent mode: * - The inner display is OFF * - The outer display is ON * - The device is partially unfolded (left and right edges could be on the table) * In this mode the device the device so it could be used in a posture where both left * and right edges of the unfolded device are on the table. - * + * <p> * The predicate returns false after the hinge angle reaches * {@code tentModeSwitchAngleDegrees}. Then it switches back only when the hinge angle * becomes less than {@code maxClosedAngleDegrees}. Hinge angle is 0 degrees when the device * is fully closed and 180 degrees when it is fully unfolded. - * + * <p> * For example, when tentModeSwitchAngleDegrees = 90 and maxClosedAngleDegrees = 5 degrees: * - when unfolding the device from fully closed posture (last state == closed or it is * undefined yet) this state will become not matching after reaching the angle @@ -435,6 +538,15 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } /** + * @return Whether there is an external connected display. + */ + public boolean hasNoConnectedExternalDisplay() { + synchronized (mLock) { + return mExternalDisplaysConnected.size() == 0; + } + } + + /** * @return Whether the screen is on. */ public boolean isScreenOn() { @@ -442,6 +554,7 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, return mIsScreenOn; } } + /** * @return current hinge angle value of a foldable device */ diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java index 5f2cf3cb5060..ddac88ac0ec7 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/TentModeDeviceStatePolicy.java @@ -33,6 +33,8 @@ import android.hardware.display.DisplayManager; import com.android.server.devicestate.DeviceStatePolicy; import com.android.server.devicestate.DeviceStateProvider; import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; +import com.android.server.policy.feature.flags.FeatureFlags; +import com.android.server.policy.feature.flags.FeatureFlagsImpl; /** * Device state policy for a foldable device that supports tent mode: a mode when the device @@ -55,6 +57,8 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { private final DeviceStateProvider mProvider; + private final boolean mIsDualDisplayBlockingEnabled; + /** * Creates TentModeDeviceStatePolicy * @@ -67,6 +71,12 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { */ public TentModeDeviceStatePolicy(@NonNull Context context, @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, int closeAngleDegrees) { + this(new FeatureFlagsImpl(), context, hingeAngleSensor, hallSensor, closeAngleDegrees); + } + + public TentModeDeviceStatePolicy(@NonNull FeatureFlags featureFlags, @NonNull Context context, + @NonNull Sensor hingeAngleSensor, @NonNull Sensor hallSensor, + int closeAngleDegrees) { super(context); final SensorManager sensorManager = mContext.getSystemService(SensorManager.class); @@ -74,8 +84,10 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { final DeviceStateConfiguration[] configuration = createConfiguration(closeAngleDegrees); - mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor, - hallSensor, displayManager, configuration); + mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking(); + + mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, + hingeAngleSensor, hallSensor, displayManager, configuration); } private DeviceStateConfiguration[] createConfiguration(int closeAngleDegrees) { @@ -83,24 +95,27 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { createClosedConfiguration(closeAngleDegrees), createConfig(DEVICE_STATE_HALF_OPENED, /* name= */ "HALF_OPENED", - (provider) -> { + /* activeStatePredicate= */ (provider) -> { final float hingeAngle = provider.getHingeAngle(); return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES; }), createConfig(DEVICE_STATE_OPENED, /* name= */ "OPENED", - (provider) -> true), + /* activeStatePredicate= */ (provider) -> true), createConfig(DEVICE_STATE_REAR_DISPLAY_STATE, /* name= */ "REAR_DISPLAY_STATE", /* flags= */ FLAG_EMULATED_ONLY, - (provider) -> false), + /* activeStatePredicate= */ (provider) -> false), createConfig(DEVICE_STATE_CONCURRENT_INNER_DEFAULT, /* name= */ "CONCURRENT_INNER_DEFAULT", /* flags= */ FLAG_EMULATED_ONLY | FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP | FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL | FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE, - (provider) -> false) + /* activeStatePredicate= */ (provider) -> false, + /* availabilityPredicate= */ + provider -> !mIsDualDisplayBlockingEnabled + || provider.hasNoConnectedExternalDisplay()) }; } @@ -111,7 +126,7 @@ public class TentModeDeviceStatePolicy extends DeviceStatePolicy { DEVICE_STATE_CLOSED, /* name= */ "CLOSED", /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS, - (provider) -> { + /* activeStatePredicate= */ (provider) -> { final float hingeAngle = provider.getHingeAngle(); return hingeAngle <= closeAngleDegrees; } diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp new file mode 100644 index 000000000000..6ad8d790485c --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "device_state_flags", + package: "com.android.server.policy.feature.flags", + srcs: [ + "device_state_flags.aconfig", + ], +} + +java_aconfig_library { + name: "device_state_flags_lib", + aconfig_declarations: "device_state_flags", +} diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig new file mode 100644 index 000000000000..47c2a1b079f8 --- /dev/null +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.policy.feature.flags" + +flag { + name: "enable_dual_display_blocking" + namespace: "display_manager" + description: "Feature flag for dual display blocking" + bug: "278667199" +}
\ No newline at end of file diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java index 8fa4ce592777..ddf4a089e76e 100644 --- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java +++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java @@ -17,18 +17,21 @@ package com.android.server.policy; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_OFF; +import static android.view.Display.STATE_ON; +import static android.view.Display.TYPE_EXTERNAL; +import static android.view.Display.TYPE_INTERNAL; + +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED; +import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL; import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL; -import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Display.STATE_OFF; -import static android.view.Display.STATE_ON; - import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -36,12 +39,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.nullable; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,20 +53,21 @@ import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.hardware.display.DisplayManager; import android.hardware.input.InputSensorInfo; -import android.os.PowerManager; import android.os.Handler; +import android.os.PowerManager; import android.testing.AndroidTestingRunner; import android.view.Display; import com.android.server.devicestate.DeviceState; -import com.android.server.devicestate.DeviceStateProvider; import com.android.server.devicestate.DeviceStateProvider.Listener; +import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; +import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl; +import com.android.server.policy.feature.flags.Flags; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -95,10 +98,16 @@ public final class FoldableDeviceStateProviderTest { @Mock private DisplayManager mDisplayManager; private FoldableDeviceStateProvider mProvider; + @Mock + private Display mDefaultDisplay; + @Mock + private Display mExternalDisplay; + private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl(); @Before public void setup() { MockitoAnnotations.initMocks(this); + mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true); mHallSensor = new Sensor(mInputSensorInfo); mHingeAngleSensor = new Sensor(mInputSensorInfo); @@ -473,6 +482,133 @@ public final class FoldableDeviceStateProviderTest { assertThat(mProvider.isScreenOn()).isFalse(); } + @Test + public void test_dualScreenDisabledWhenExternalScreenIsConnected() throws Exception { + when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL); + + createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED", + (c) -> c.getHingeAngle() < 5f), + createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED", + (c) -> c.getHingeAngle() < 90f), + createConfig(/* identifier= */ 3, /* name= */ "OPENED", + (c) -> c.getHingeAngle() < 180f), + createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0, + (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)); + + Listener listener = mock(Listener.class); + mProvider.setListener(listener); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder(); + + clearInvocations(listener); + + when(mDisplayManager.getDisplays()) + .thenReturn(new Display[]{mDefaultDisplay, mExternalDisplay}); + when(mDisplayManager.getDisplay(1)).thenReturn(mExternalDisplay); + when(mExternalDisplay.getType()).thenReturn(TYPE_EXTERNAL); + + // The DUAL_DISPLAY state should be disabled. + mProvider.onDisplayAdded(1); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */)}).inOrder(); + clearInvocations(listener); + + // The DUAL_DISPLAY state should be re-enabled. + when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + mProvider.onDisplayRemoved(1); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_REMOVED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder(); + } + + @Test + public void test_notifySupportedStatesChangedCalledOnlyOnInitialExternalScreenAddition() { + when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay}); + when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL); + + createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED", + (c) -> c.getHingeAngle() < 5f), + createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED", + (c) -> c.getHingeAngle() < 90f), + createConfig(/* identifier= */ 3, /* name= */ "OPENED", + (c) -> c.getHingeAngle() < 180f), + createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0, + (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)); + + Listener listener = mock(Listener.class); + mProvider.setListener(listener); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED)); + assertThat(mDeviceStateArrayCaptor.getValue()).asList().containsExactly( + new DeviceState[]{ + new DeviceState(1, "CLOSED", 0 /* flags */), + new DeviceState(2, "HALF_OPENED", 0 /* flags */), + new DeviceState(3, "OPENED", 0 /* flags */), + new DeviceState(4, "DUAL_DISPLAY", 0 /* flags */)}).inOrder(); + + clearInvocations(listener); + + addExternalDisplay(1); + verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED)); + addExternalDisplay(2); + addExternalDisplay(3); + addExternalDisplay(4); + verify(listener, times(1)) + .onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(), + eq(SUPPORTED_DEVICE_STATES_CHANGED_EXTERNAL_DISPLAY_ADDED)); + } + + @Test + public void hasNoConnectedDisplay_afterExternalDisplayAdded_returnsFalse() { + createProvider( + createConfig( + /* identifier= */ 1, /* name= */ "ONE", + /* flags= */0, (c) -> true, + FoldableDeviceStateProvider::hasNoConnectedExternalDisplay) + ); + + addExternalDisplay(/* displayId */ 1); + + assertThat(mProvider.hasNoConnectedExternalDisplay()).isFalse(); + } + + @Test + public void hasNoConnectedDisplay_afterExternalDisplayAddedAndRemoved_returnsTrue() { + createProvider( + createConfig( + /* identifier= */ 1, /* name= */ "ONE", + /* flags= */0, (c) -> true, + FoldableDeviceStateProvider::hasNoConnectedExternalDisplay) + ); + + addExternalDisplay(/* displayId */ 1); + mProvider.onDisplayRemoved(1); + + assertThat(mProvider.hasNoConnectedExternalDisplay()).isTrue(); + } + private void addExternalDisplay(int displayId) { + when(mDisplayManager.getDisplay(displayId)).thenReturn(mExternalDisplay); + when(mExternalDisplay.getType()).thenReturn(TYPE_EXTERNAL); + mProvider.onDisplayAdded(displayId); + } private void setScreenOn(boolean isOn) { Display mockDisplay = mock(Display.class); int state = isOn ? STATE_ON : STATE_OFF; @@ -508,12 +644,11 @@ public final class FoldableDeviceStateProviderTest { } private void createProvider(DeviceStateConfiguration... configurations) { - mProvider = new FoldableDeviceStateProvider(mContext, mSensorManager, mHingeAngleSensor, - mHallSensor, mDisplayManager, configurations); + mProvider = new FoldableDeviceStateProvider(mFakeFeatureFlags, mContext, mSensorManager, + mHingeAngleSensor, mHallSensor, mDisplayManager, configurations); verify(mDisplayManager) .registerDisplayListener( mDisplayListenerCaptor.capture(), - nullable(Handler.class), - anyLong()); + nullable(Handler.class)); } } |