diff options
author | Cyan_Hsieh <cyanhsieh@google.com> | 2023-02-17 11:51:58 +0800 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-02-20 12:04:03 +0000 |
commit | 1a7fb35c86a57b72c577163099818e2fc1d36a3d (patch) | |
tree | d44eb7ece3510540da0af8906fcd35be3ddb8757 | |
parent | 3e0b68a41f97461d0ef19d814ccca1a3d597f391 (diff) | |
download | base-1a7fb35c86a57b72c577163099818e2fc1d36a3d.tar.gz |
Revert "[Submit only to device branch] Add custom device state provider"
This reverts commit 57766bb95afe69797b9e14a8d89a593a65c63236.
reason for revert: replace with new patch
bug:253490171
Change-Id: I95148bc2a76efbe2292f5ae0e0416f3e8a41aa5d
(cherry picked from commit 4ad1c8a5d58de265f1d56d3b20209f3e409b3825)
Merged-In: I95148bc2a76efbe2292f5ae0e0416f3e8a41aa5d
7 files changed, 20 insertions, 1114 deletions
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java index c876a8b76dc1..5c4e2f3426ee 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java +++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.text.TextUtils; -import android.util.Slog; import com.android.server.policy.DeviceStatePolicyImpl; @@ -93,16 +92,11 @@ public abstract class DeviceStatePolicy { try { return (DeviceStatePolicy.Provider) Class.forName(name).newInstance(); - } catch (ClassCastException e) { + } catch (ReflectiveOperationException | ClassCastException e) { throw new IllegalStateException("Couldn't instantiate class " + name + " for config_deviceSpecificDeviceStatePolicyProvider:" + " make sure it has a public zero-argument constructor" - + " and implements DeviceStatePolicy.Provider"); - } catch (ReflectiveOperationException e) { - Slog.e("DeviceStatePolicy", "Couldn't instantiate class " + name - + " for config_deviceSpecificDeviceStatePolicyProvider:" - + " using default provider", e); - return new DeviceStatePolicy.DefaultProvider(); + + " and implements DeviceStatePolicy.Provider", e); } } } diff --git a/services/core/java/com/android/server/policy/FoldableDeviceStateProvider.java b/services/core/java/com/android/server/policy/FoldableDeviceStateProvider.java deleted file mode 100644 index 0575df5b0fe6..000000000000 --- a/services/core/java/com/android/server/policy/FoldableDeviceStateProvider.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import static android.hardware.SensorManager.SENSOR_DELAY_FASTEST; -import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; -import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; -import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; -import com.android.server.devicestate.DeviceState; -import com.android.server.devicestate.DeviceStateProvider; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.function.BooleanSupplier; -import java.util.function.Function; - -/** - * Device state provider for foldable devices. - * - * 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 { - - private static final String TAG = "FoldableDeviceStateProvider"; - private static final boolean DEBUG = false; - - // Lock for internal state. - private final Object mLock = new Object(); - - // List of supported states in ascending order based on their identifier. - private final DeviceState[] mOrderedStates; - - // Map of state identifier to a boolean supplier that returns true when all required conditions - // are met for the device to be in the state. - private final SparseArray<BooleanSupplier> mStateConditions = new SparseArray<>(); - - private final Sensor mHingeAngleSensor; - private final Sensor mHallSensor; - - @Nullable - @GuardedBy("mLock") - private Listener mListener = null; - @GuardedBy("mLock") - private int mLastReportedState = INVALID_DEVICE_STATE; - @GuardedBy("mLock") - private SensorEvent mLastHingeAngleSensorEvent = null; - @GuardedBy("mLock") - private SensorEvent mLastHallSensorEvent = null; - - public FoldableDeviceStateProvider(@NonNull SensorManager sensorManager, - @NonNull Sensor hingeAngleSensor, - @NonNull Sensor hallSensor, - @NonNull DeviceStateConfiguration[] deviceStateConfigurations) { - - Preconditions.checkArgument(deviceStateConfigurations.length > 0, - "Device state configurations array must not be empty"); - - mHingeAngleSensor = hingeAngleSensor; - mHallSensor = hallSensor; - - sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST); - sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST); - - mOrderedStates = new DeviceState[deviceStateConfigurations.length]; - for (int i = 0; i < deviceStateConfigurations.length; i++) { - 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)); - } - - Arrays.sort(mOrderedStates, Comparator.comparingInt(DeviceState::getIdentifier)); - } - - @Override - public void setListener(Listener listener) { - synchronized (mLock) { - if (mListener != null) { - throw new RuntimeException("Provider already has a listener set."); - } - mListener = listener; - } - notifySupportedStatesChanged(); - notifyDeviceStateChangedIfNeeded(); - } - - /** Notifies the listener that the set of supported device states has changed. */ - private void notifySupportedStatesChanged() { - DeviceState[] supportedStates; - Listener listener; - synchronized (mLock) { - if (mListener == null) { - return; - } - - listener = mListener; - supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length); - } - - listener.onSupportedDeviceStatesChanged(supportedStates); - } - - /** Computes the current device state and notifies the listener of a change, if needed. */ - void notifyDeviceStateChangedIfNeeded() { - int stateToReport = INVALID_DEVICE_STATE; - Listener listener; - synchronized (mLock) { - if (mListener == null) { - return; - } - - listener = mListener; - - int newState = INVALID_DEVICE_STATE; - for (int i = 0; i < mOrderedStates.length; i++) { - int state = mOrderedStates[i].getIdentifier(); - if (DEBUG) { - Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "(" - + i + ")"); - } - boolean conditionSatisfied; - try { - conditionSatisfied = mStateConditions.get(state).getAsBoolean(); - } catch (IllegalStateException e) { - // Failed to compute the current state based on current available data. Continue - // with the expectation that notifyDeviceStateChangedIfNeeded() will be called - // when a callback with the missing data is triggered. May trigger another state - // change if another state is satisfied currently. - Slog.w(TAG, "Unable to check current state = " + state, e); - dumpSensorValues(); - continue; - } - - if (conditionSatisfied) { - if (DEBUG) { - Slog.d(TAG, "Device State conditions satisfied, transition to " + state); - } - newState = state; - break; - } - } - if (newState == INVALID_DEVICE_STATE) { - Slog.e(TAG, "No declared device states match any of the required conditions."); - dumpSensorValues(); - } - - if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) { - mLastReportedState = newState; - stateToReport = newState; - } - } - - if (stateToReport != INVALID_DEVICE_STATE) { - listener.onStateChanged(stateToReport); - } - } - - @Override - public void onSensorChanged(SensorEvent event) { - synchronized (mLock) { - if (event.sensor == mHallSensor) { - mLastHallSensorEvent = event; - } else if (event.sensor == mHingeAngleSensor) { - mLastHingeAngleSensorEvent = event; - } - } - notifyDeviceStateChangedIfNeeded(); - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // Do nothing. - } - - private float getSensorValue(@Nullable SensorEvent sensorEvent) { - if (sensorEvent == null) { - throw new IllegalStateException("Have not received sensor event."); - } - - if (sensorEvent.values.length < 1) { - throw new IllegalStateException("Values in the sensor event are empty"); - } - - return sensorEvent.values[0]; - } - - @GuardedBy("mLock") - private void dumpSensorValues() { - Slog.i(TAG, "Sensor values:"); - dumpSensorValues(mHallSensor, mLastHallSensorEvent); - dumpSensorValues(mHingeAngleSensor, mLastHingeAngleSensorEvent); - } - - @GuardedBy("mLock") - private void dumpSensorValues(@NonNull Sensor sensor, @Nullable SensorEvent event) { - if (event != null) { - Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(event.values)); - } else { - Slog.i(TAG, sensor.getName() + ": null"); - } - } - - /** - * Configuration for a single device state, contains information about the state like - * identifier, name, flags and a predicate that should return true if the state should - * be selected. - */ - public static class DeviceStateConfiguration { - private final DeviceState mDeviceState; - private final Function<FoldableDeviceStateProvider, Boolean> mPredicate; - - private DeviceStateConfiguration(DeviceState deviceState, - Function<FoldableDeviceStateProvider, Boolean> predicate) { - mDeviceState = deviceState; - mPredicate = 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 - ) { - return new DeviceStateConfiguration(new DeviceState(identifier, name, flags), - predicate); - } - - public static DeviceStateConfiguration createConfig( - @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, - @NonNull String name, - Function<FoldableDeviceStateProvider, Boolean> predicate - ) { - return new DeviceStateConfiguration(new DeviceState(identifier, name, /* flags= */ 0), - predicate); - } - - /** - * Creates a device state configuration for a closed tent-mode aware state. - * This is useful to create a behavior when the device could be used in a tent mode: a mode - * on a foldable device where we keep the outer display on after partially unfolding - * the device so it could be used in a posture where both left and right edges of - * the unfolded device are on the table. - * - * The predicate returns false when 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. - * - * 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 when reaching the angle - * of 90 degrees, it allows the device to switch the outer display to the inner display - * only when reaching this threshold - * - when folding (last state != 'closed') this state will become matching when reaching - * the angle less than 5 degrees and when hall sensor detected that the device is closed, - * so the switch from the inner display to the outer will become only when the device - * is fully closed. - * - * @param identifier state identifier - * @param name state name - * @param flags state flags - * @param minClosedAngleDegrees minimum (inclusive) hinge angle value for the closed state - * @param maxClosedAngleDegrees maximum (non-inclusive) hinge angle value for the closed - * state - * @param tentModeSwitchAngleDegrees the angle when this state should switch when unfolding - * @return device state configuration - */ - public static DeviceStateConfiguration createTentModeClosedState( - @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier, - @NonNull String name, - @DeviceState.DeviceStateFlags int flags, - int minClosedAngleDegrees, - int maxClosedAngleDegrees, - int tentModeSwitchAngleDegrees - ) { - return new DeviceStateConfiguration(new DeviceState(identifier, name, flags), - (stateContext) -> { - final boolean hallSensorClosed = stateContext.isHallSensorClosed(); - final float hingeAngle = stateContext.getHingeAngle(); - final int lastState = stateContext.getLastReportedDeviceState(); - - final int closedDeviceState = identifier; - final boolean isLastStateClosed = lastState == closedDeviceState - || lastState == INVALID_DEVICE_STATE; - - final boolean shouldBeClosedBecauseTentMode = isLastStateClosed - && hingeAngle >= minClosedAngleDegrees - && hingeAngle < tentModeSwitchAngleDegrees; - - final boolean shouldBeClosedBecauseFullyShut = hallSensorClosed - && hingeAngle >= minClosedAngleDegrees - && hingeAngle < maxClosedAngleDegrees; - - return shouldBeClosedBecauseFullyShut || shouldBeClosedBecauseTentMode; - }); - } - } - - /** - * @return current hinge angle value of a foldable device - */ - public float getHingeAngle() { - synchronized (mLock) { - return getSensorValue(mLastHingeAngleSensorEvent); - } - } - - /** - * @return true if hall sensor detected that the device is closed (fully shut) - */ - public boolean isHallSensorClosed() { - synchronized (mLock) { - return getSensorValue(mLastHallSensorEvent) > 0f; - } - } - - /** - * @return last reported device state - */ - public int getLastReportedDeviceState() { - synchronized (mLock) { - return mLastReportedState; - } - } -} diff --git a/services/core/java/com/android/server/policy/TentModeDeviceStatePolicy.java b/services/core/java/com/android/server/policy/TentModeDeviceStatePolicy.java deleted file mode 100644 index abba0194cd52..000000000000 --- a/services/core/java/com/android/server/policy/TentModeDeviceStatePolicy.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; -import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY; -import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig; -import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState; - -import android.annotation.NonNull; -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorManager; - -import com.android.internal.util.CollectionUtils; -import com.android.server.devicestate.DeviceStatePolicy; -import com.android.server.devicestate.DeviceStateProvider; -import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; - -import java.util.List; -import java.util.Objects; - -/** - * Device state policy for a foldable device that supports tent mode: a mode when the device - * keeps the outer display on until reaching a certain hinge angle threshold. - * - * Contains configuration for {@link FoldableDeviceStateProvider}. - */ -public class TentModeDeviceStatePolicy extends DeviceStatePolicy { - - private static final int DEVICE_STATE_CLOSED = 0; - private static final int DEVICE_STATE_HALF_OPENED = 1; - private static final int DEVICE_STATE_OPENED = 2; - private static final int DEVICE_STATE_REAR_DISPLAY_STATE = 3; - - private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90; - private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125; - private static final int MIN_CLOSED_ANGLE_DEGREES = 0; - private static final int MAX_CLOSED_ANGLE_DEGREES = 5; - - private final DeviceStateProvider mProvider; - - protected TentModeDeviceStatePolicy(@NonNull Context context) { - super(context); - - final SensorManager sensorManager = mContext.getSystemService(SensorManager.class); - final Sensor hingeAngleSensor = - sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE, /* wakeUp= */ true); - - final List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); - final Sensor hallSensor = CollectionUtils.find(sensors, - (sensor) -> Objects.equals(sensor.getStringType(), - "com.google.sensor.hall_effect")); - - mProvider = new FoldableDeviceStateProvider(sensorManager, hingeAngleSensor, hallSensor, - createConfiguration()); - } - - private DeviceStateConfiguration[] createConfiguration() { - return new DeviceStateConfiguration[]{ - createTentModeClosedState(DEVICE_STATE_CLOSED, - /* name= */ "CLOSED", - /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS, - MIN_CLOSED_ANGLE_DEGREES, - MAX_CLOSED_ANGLE_DEGREES, - TENT_MODE_SWITCH_ANGLE_DEGREES), - createConfig(DEVICE_STATE_HALF_OPENED, - /* name= */ "HALF_OPENED", - (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), - createConfig(DEVICE_STATE_REAR_DISPLAY_STATE, - /* name= */ "REAR_DISPLAY_STATE", - /* flags= */ FLAG_EMULATED_ONLY, - (provider) -> false) - }; - } - - @Override - public DeviceStateProvider getDeviceStateProvider() { - return mProvider; - } - - @Override - public void configureDeviceForState(int state, @NonNull Runnable onComplete) { - onComplete.run(); - } - - public static class Provider implements DeviceStatePolicy.Provider { - - @Override - public DeviceStatePolicy instantiate(@NonNull Context context) { - return new TentModeDeviceStatePolicy(context); - } - } -} diff --git a/services/proguard.flags b/services/proguard.flags index 255157b4acc8..c648f7d3ac45 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -110,8 +110,6 @@ public <init>(...); } -keep,allowoptimization,allowaccessmodification class android.hardware.usb.gadget.** { *; } --keep,allowoptimization,allowaccessmodification class com.android.server.policy.TentModeDeviceStatePolicy { *; } --keep,allowoptimization,allowaccessmodification class com.android.server.policy.TentModeDeviceStatePolicy$Provider { *; } # Needed when optimizations enabled # TODO(b/210510433): Revisit and fix with @Keep. diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java index 4ec0fa14937c..0bd81b78ac97 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java @@ -16,8 +16,6 @@ package com.android.server.devicestate; -import static org.hamcrest.Matchers.instanceOf; -import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -26,6 +24,8 @@ import android.content.Context; import android.content.res.Resources; import android.platform.test.annotations.Presubmit; +import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Test; /** @@ -39,35 +39,37 @@ public class DeviceStatePolicyProviderTest { @Test public void test_emptyPolicyProvider() { - assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")), - instanceOf(DeviceStatePolicy.DefaultProvider.class)); + Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")), + Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class)); } @Test public void test_nullPolicyProvider() { - assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)), - instanceOf(DeviceStatePolicy.DefaultProvider.class)); + Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)), + Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class)); } @Test public void test_customPolicyProvider() { - assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider( - TestProvider.class.getName())), - instanceOf(TestProvider.class)); + Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider( + TestProvider.class.getName())), + Matchers.instanceOf(TestProvider.class)); } @Test public void test_badPolicyProvider_notImplementingProviderInterface() { - assertThrows(IllegalStateException.class, () -> - DeviceStatePolicy.Provider.fromResources(resourcesWithProvider( - Object.class.getName()))); + assertThrows(IllegalStateException.class, () -> { + DeviceStatePolicy.Provider.fromResources(resourcesWithProvider( + Object.class.getName())); + }); } @Test - public void test_badPolicyProvider_returnsDefault() { - assertThat(DeviceStatePolicy.Provider.fromResources( - resourcesWithProvider("com.android.devicestate.nonexistent.policy")), - instanceOf(DeviceStatePolicy.DefaultProvider.class)); + public void test_badPolicyProvider_doesntExist() { + assertThrows(IllegalStateException.class, () -> { + DeviceStatePolicy.Provider.fromResources(resourcesWithProvider( + "com.android.devicestate.nonexistent.policy")); + }); } private static Resources resourcesWithProvider(String provider) { diff --git a/services/tests/servicestests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/tests/servicestests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java deleted file mode 100644 index 066300c7d7f9..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - - -import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorManager; -import android.hardware.input.InputSensorInfo; - -import com.android.server.devicestate.DeviceState; -import com.android.server.devicestate.DeviceStateProvider.Listener; -import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration; - -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.internal.util.reflection.FieldSetter; - -/** - * Unit tests for {@link FoldableDeviceStateProvider}. - * <p/> - * Run with <code>atest FoldableDeviceStateProviderTest</code>. - */ -public final class FoldableDeviceStateProviderTest { - - private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass( - DeviceState[].class); - private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class); - - private final SensorManager mSensorManager = mock(SensorManager.class); - private final Sensor mHallSensor = new Sensor(mock(InputSensorInfo.class)); - private final Sensor mHingeAngleSensor = new Sensor(mock(InputSensorInfo.class)); - - private FoldableDeviceStateProvider mProvider; - - @Test - public void create_emptyConfiguration_throwsException() { - assertThrows(IllegalArgumentException.class, this::createProvider); - } - - @Test - public void create_duplicatedDeviceStateIdentifiers_throwsException() { - assertThrows(IllegalArgumentException.class, - () -> createProvider( - createConfig( - /* identifier= */ 0, /* name= */ "ONE", (c) -> true), - createConfig( - /* identifier= */ 0, /* name= */ "TWO", (c) -> true) - )); - } - - @Test - public void create_allMatchingStatesDefaultsToTheFirstIdentifier() { - createProvider( - createConfig( - /* identifier= */ 1, /* name= */ "ONE", (c) -> true), - createConfig( - /* identifier= */ 2, /* name= */ "TWO", (c) -> true), - createConfig( - /* identifier= */ 3, /* name= */ "THREE", (c) -> true) - ); - - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - - verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); - final DeviceState[] expectedStates = new DeviceState[]{ - new DeviceState(1, "ONE", /* flags= */ 0), - new DeviceState(2, "TWO", /* flags= */ 0), - new DeviceState(3, "THREE", /* flags= */ 0), - }; - assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue()); - - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(1, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void create_multipleMatchingStatesDefaultsToTheLowestIdentifier() { - createProvider( - createConfig( - /* identifier= */ 1, /* name= */ "ONE", (c) -> false), - createConfig( - /* identifier= */ 3, /* name= */ "THREE", (c) -> false), - createConfig( - /* identifier= */ 4, /* name= */ "FOUR", (c) -> true), - createConfig( - /* identifier= */ 2, /* name= */ "TWO", (c) -> true) - ); - - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hingeAngleUpdatedFirstTime_switchesToMatchingState() throws Exception { - createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE", - (c) -> c.getHingeAngle() < 90f), - createConfig(/* identifier= */ 2, /* name= */ "TWO", - (c) -> c.getHingeAngle() >= 90f)); - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - verify(listener, never()).onStateChanged(anyInt()); - clearInvocations(listener); - - sendSensorEvent(mHingeAngleSensor, /* value= */ 100f); - - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hallSensorUpdatedFirstTime_switchesToMatchingState() throws Exception { - createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE", - (c) -> !c.isHallSensorClosed()), - createConfig(/* identifier= */ 2, /* name= */ "TWO", - FoldableDeviceStateProvider::isHallSensorClosed)); - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - verify(listener, never()).onStateChanged(anyInt()); - clearInvocations(listener); - - // Hall sensor value '1f' is for the closed state - sendSensorEvent(mHallSensor, /* value= */ 1f); - - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hingeAngleUpdatedSecondTime_switchesToMatchingState() throws Exception { - createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE", - (c) -> c.getHingeAngle() < 90f), - createConfig(/* identifier= */ 2, /* name= */ "TWO", - (c) -> c.getHingeAngle() >= 90f)); - sendSensorEvent(mHingeAngleSensor, /* value= */ 30f); - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(1, mIntegerCaptor.getValue().intValue()); - clearInvocations(listener); - - sendSensorEvent(mHingeAngleSensor, /* value= */ 100f); - - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hallSensorUpdatedSecondTime_switchesToMatchingState() throws Exception { - createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE", - (c) -> !c.isHallSensorClosed()), - createConfig(/* identifier= */ 2, /* name= */ "TWO", - FoldableDeviceStateProvider::isHallSensorClosed)); - sendSensorEvent(mHallSensor, /* value= */ 0f); - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(1, mIntegerCaptor.getValue().intValue()); - clearInvocations(listener); - - // Hall sensor value '1f' is for the closed state - sendSensorEvent(mHallSensor, /* value= */ 1f); - - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_invalidSensorValues_onStateChangedIsNotTriggered() throws Exception { - createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE", - (c) -> c.getHingeAngle() < 90f), - createConfig(/* identifier= */ 2, /* name= */ "TWO", - (c) -> c.getHingeAngle() >= 90f)); - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - clearInvocations(listener); - - // First, switch to a non-default state. - sendSensorEvent(mHingeAngleSensor, /* value= */ 100f); - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - - clearInvocations(listener); - - // Then, send an invalid sensor event, verify that onStateChanged() is not triggered. - sendInvalidSensorEvent(mHingeAngleSensor); - - verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture()); - verify(listener, never()).onStateChanged(mIntegerCaptor.capture()); - } - - @Test - public void test_previousStateBasedPredicate() { - // Create a configuration where state TWO could be matched only if - // the previous state was 'THREE' - createProvider( - createConfig( - /* identifier= */ 1, /* name= */ "ONE", (c) -> c.getHingeAngle() < 30f), - createConfig( - /* identifier= */ 2, /* name= */ "TWO", - (c) -> c.getLastReportedDeviceState() == 3 && c.getHingeAngle() > 120f), - createConfig( - /* identifier= */ 3, /* name= */ "THREE", - (c) -> c.getHingeAngle() > 90f) - ); - sendSensorEvent(mHingeAngleSensor, /* value= */ 0f); - Listener listener = mock(Listener.class); - mProvider.setListener(listener); - - // Check that the initial state is 'ONE' - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(1, mIntegerCaptor.getValue().intValue()); - clearInvocations(listener); - - // Should not match state 'TWO', it should match only state 'THREE' - // (because the previous state is not 'THREE', it is 'ONE') - sendSensorEvent(mHingeAngleSensor, /* value= */ 180f); - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(3, mIntegerCaptor.getValue().intValue()); - clearInvocations(listener); - - // Now it should match state 'TWO' - // (because the previous state is 'THREE' now) - sendSensorEvent(mHingeAngleSensor, /* value= */ 180f); - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(2, mIntegerCaptor.getValue().intValue()); - } - - private void sendSensorEvent(Sensor sensor, float value) { - SensorEvent event = mock(SensorEvent.class); - event.sensor = sensor; - try { - FieldSetter.setField(event, event.getClass().getField("values"), - new float[]{value}); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - - mProvider.onSensorChanged(event); - } - - private void sendInvalidSensorEvent(Sensor sensor) { - SensorEvent event = mock(SensorEvent.class); - event.sensor = sensor; - try { - // Set empty values array to make the event invalid - FieldSetter.setField(event, event.getClass().getField("values"), - new float[]{}); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - mProvider.onSensorChanged(event); - } - - private void createProvider(DeviceStateConfiguration... configurations) { - mProvider = new FoldableDeviceStateProvider(mSensorManager, mHingeAngleSensor, mHallSensor, - configurations); - } -} diff --git a/services/tests/servicestests/src/com/android/server/policy/TentModeDeviceStateProviderTest.java b/services/tests/servicestests/src/com/android/server/policy/TentModeDeviceStateProviderTest.java deleted file mode 100644 index 43c55508ebc7..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/TentModeDeviceStateProviderTest.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.hardware.input.InputSensorInfo; - -import com.android.server.devicestate.DeviceStateProvider; -import com.android.server.devicestate.DeviceStateProvider.Listener; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.internal.util.reflection.FieldSetter; - -import java.util.ArrayList; -import java.util.List; - -/** - * Unit tests for {@link TentModeDeviceStatePolicy.Provider}. - * <p/> - * Run with <code>atest TentModeDeviceStateProviderTest</code>. - */ -public final class TentModeDeviceStateProviderTest { - - private static final int DEVICE_STATE_CLOSED = 0; - private static final int DEVICE_STATE_HALF_OPENED = 1; - private static final int DEVICE_STATE_OPENED = 2; - - private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class); - - private final Context mContext = mock(Context.class); - private final SensorManager mSensorManager = mock(SensorManager.class); - private final Sensor mHallSensor = new Sensor(mock(InputSensorInfo.class)); - private final Sensor mHingeAngleSensor = new Sensor(mock(InputSensorInfo.class)); - private final Listener mListener = mock(Listener.class); - - private SensorEventListener mSensorEventListener; - private DeviceStateProvider mProvider; - - @Before - public void setup() { - when(mContext.getSystemServiceName(SensorManager.class)).thenReturn(Context.SENSOR_SERVICE); - when(mContext.getSystemService(Context.SENSOR_SERVICE)).thenReturn(mSensorManager); - - when(mSensorManager.getDefaultSensor(eq(Sensor.TYPE_HINGE_ANGLE), eq(true))) - .thenReturn(mHingeAngleSensor); - - final List<Sensor> sensors = new ArrayList<>(); - sensors.add(mHallSensor); - sensors.add(mHingeAngleSensor); - - when(mSensorManager.registerListener(any(), any(), anyInt())).thenAnswer(invocation -> { - mSensorEventListener = invocation.getArgument(0); - return true; - }); - - try { - FieldSetter.setField(mHallSensor, mHallSensor.getClass() - .getDeclaredField("mStringType"), "com.google.sensor.hall_effect"); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - - when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))) - .thenReturn(sensors); - - mProvider = new TentModeDeviceStatePolicy.Provider().instantiate(mContext) - .getDeviceStateProvider(); - } - - @Test - public void test_noSensorEventsYet_reportOpenedState() { - mProvider.setListener(mListener); - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_OPENED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_deviceClosedSensorEventsBecameAvailable_reportsClosedState() { - mProvider.setListener(mListener); - clearInvocations(mListener); - - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_CLOSED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hallSensorClosedAndHingeAngleClosed_reportsClosedState() { - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - - mProvider.setListener(mListener); - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_CLOSED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hallSensorClosedAndHingeAngleFullyOpened_reportsOpenedState() { - sendClosedHallSensorEvent(); - sendHingeAngle(180f); - - mProvider.setListener(mListener); - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_OPENED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_hallSensorOpenedAndHingeAngleClosed_reportsClosedState() { - sendOpenedHallSensorEvent(); - sendHingeAngle(0f); - - mProvider.setListener(mListener); - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_CLOSED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_unfoldingFromClosedToFullyOpened_reportsOpenedEvent() { - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - mProvider.setListener(mListener); - clearInvocations(mListener); - - sendOpenedHallSensorEvent(); - sendHingeAngle(180f); - - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_OPENED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_unfoldingFromClosedToTentMode_keepsClosedState() { - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - mProvider.setListener(mListener); - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_CLOSED, mIntegerCaptor.getValue().intValue()); - clearInvocations(mListener); - - sendOpenedHallSensorEvent(); - sendHingeAngle(60f); - - verify(mListener, never()).onStateChanged(mIntegerCaptor.capture()); - } - - @Test - public void test_foldingFromFullyOpenToAlmostClosed_movesToHalfOpenedState() { - sendOpenedHallSensorEvent(); - sendHingeAngle(180f); - mProvider.setListener(mListener); - clearInvocations(mListener); - - sendHingeAngle(15f); - - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - // Assert that we don't go into tent mode (OPENED state) and switch to HALF_OPENED state - assertEquals(DEVICE_STATE_HALF_OPENED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_foldingFromFullyOpenToFullyClosed_movesToClosedState() { - sendOpenedHallSensorEvent(); - sendHingeAngle(180f); - - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - - mProvider.setListener(mListener); - verify(mListener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(DEVICE_STATE_CLOSED, mIntegerCaptor.getValue().intValue()); - } - - @Test - public void test_slowUnfolding_reportsEventsInOrder() { - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - mProvider.setListener(mListener); - - sendHingeAngle(2f); - sendOpenedHallSensorEvent(); - sendHingeAngle(10f); - sendHingeAngle(60f); - sendHingeAngle(100f); - sendHingeAngle(180f); - - verify(mListener, atLeastOnce()).onStateChanged(mIntegerCaptor.capture()); - assertThat(mIntegerCaptor.getAllValues()).containsExactly( - DEVICE_STATE_CLOSED, - DEVICE_STATE_HALF_OPENED, - DEVICE_STATE_OPENED - ); - } - - @Test - public void test_slowFolding_reportsEventsInOrder() { - sendOpenedHallSensorEvent(); - sendHingeAngle(180f); - mProvider.setListener(mListener); - - sendHingeAngle(180f); - sendHingeAngle(100f); - sendHingeAngle(60f); - sendHingeAngle(10f); - sendClosedHallSensorEvent(); - sendHingeAngle(2f); - - verify(mListener, atLeastOnce()).onStateChanged(mIntegerCaptor.capture()); - assertThat(mIntegerCaptor.getAllValues()).containsExactly( - DEVICE_STATE_OPENED, - DEVICE_STATE_HALF_OPENED, - DEVICE_STATE_CLOSED - ); - } - - @Test - public void test_slowUnfoldingAndFolding_reportsEventsInOrder() { - sendClosedHallSensorEvent(); - sendHingeAngle(0f); - mProvider.setListener(mListener); - assertLatestReportedState(DEVICE_STATE_CLOSED); - - // Started unfolding - sendHingeAngle(2f); - sendOpenedHallSensorEvent(); - sendHingeAngle(30f); - assertLatestReportedState(DEVICE_STATE_CLOSED); - sendHingeAngle(60f); - assertLatestReportedState(DEVICE_STATE_CLOSED); - sendHingeAngle(100f); - assertLatestReportedState(DEVICE_STATE_HALF_OPENED); - sendHingeAngle(180f); - assertLatestReportedState(DEVICE_STATE_OPENED); - - // Started folding - sendHingeAngle(100f); - assertLatestReportedState(DEVICE_STATE_HALF_OPENED); - sendHingeAngle(60f); - assertLatestReportedState(DEVICE_STATE_HALF_OPENED); - sendHingeAngle(30f); - assertLatestReportedState(DEVICE_STATE_HALF_OPENED); - sendClosedHallSensorEvent(); - sendHingeAngle(2f); - assertLatestReportedState(DEVICE_STATE_CLOSED); - - verify(mListener, atLeastOnce()).onStateChanged(mIntegerCaptor.capture()); - assertThat(mIntegerCaptor.getAllValues()).containsExactly( - DEVICE_STATE_CLOSED, - DEVICE_STATE_HALF_OPENED, - DEVICE_STATE_OPENED, - DEVICE_STATE_HALF_OPENED, - DEVICE_STATE_CLOSED - ); - } - - private void assertLatestReportedState(int state) { - final ArgumentCaptor<Integer> integerCaptor = ArgumentCaptor.forClass(Integer.class); - verify(mListener, atLeastOnce()).onStateChanged(integerCaptor.capture()); - assertEquals(state, integerCaptor.getValue().intValue()); - } - - private void sendSensorEvent(Sensor sensor, float value) { - SensorEvent event = mock(SensorEvent.class); - event.sensor = sensor; - try { - FieldSetter.setField(event, event.getClass().getField("values"), - new float[]{value}); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } - - mSensorEventListener.onSensorChanged(event); - } - - private void sendClosedHallSensorEvent() { - // Hall sensor value '1f' is for the closed state - sendSensorEvent(mHallSensor, /* value= */ 1f); - } - - private void sendOpenedHallSensorEvent() { - // Hall sensor value '0f' is for the opened state - sendSensorEvent(mHallSensor, /* value= */ 0f); - } - - private void sendHingeAngle(float angle) { - sendSensorEvent(mHingeAngleSensor, /* value= */ angle); - } -} |