Loading services/core/java/com/android/server/power/FaceDownDetector.java +44 −27 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; import android.provider.DeviceConfig; import android.util.Slog; Loading Loading @@ -66,7 +67,7 @@ public class FaceDownDetector implements SensorEventListener { private static final float MOVING_AVERAGE_WEIGHT = 0.5f; /** DeviceConfig flag name, if {@code true}, enables Face Down features. */ private static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off"; static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off"; /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_FEATURE_ENABLED = true; Loading Loading @@ -139,6 +140,7 @@ public class FaceDownDetector implements SensorEventListener { new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT); private boolean mFaceDown = false; private boolean mInteractive = false; private boolean mActive = false; private float mPrevAcceleration = 0; Loading @@ -149,62 +151,64 @@ public class FaceDownDetector implements SensorEventListener { private final Handler mHandler; private final Runnable mUserActivityRunnable; private final BroadcastReceiver mScreenReceiver; private Context mContext; public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) { mOnFlip = Objects.requireNonNull(onFlip); mHandler = new Handler(Looper.getMainLooper()); mScreenReceiver = new ScreenStateReceiver(); mUserActivityRunnable = () -> { if (mFaceDown) { exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime); checkAndUpdateActiveState(false); updateActiveState(); } }; } /** Initializes the FaceDownDetector and all necessary listeners. */ public void systemReady(Context context) { mContext = context; mSensorManager = context.getSystemService(SensorManager.class); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); readValuesFromDeviceConfig(); checkAndUpdateActiveState(true); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE, ActivityThread.currentApplication().getMainExecutor(), (properties) -> onDeviceConfigChange(properties.getKeyset())); updateActiveState(); } private void registerScreenReceiver(Context context) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); context.registerReceiver(new ScreenStateReceiver(), intentFilter); context.registerReceiver(mScreenReceiver, intentFilter); } /** * Sets the active state of the detector. If false, we will not process accelerometer changes. */ private void checkAndUpdateActiveState(boolean active) { if (mIsEnabled && mActive != active) { private void updateActiveState() { final long currentTime = SystemClock.uptimeMillis(); // Don't make active if there was recently a user interaction while face down. if (active && mPreviousResultType == USER_INTERACTION && currentTime - mPreviousResultTime < mUserInteractionBackoffMillis) { return; } if (DEBUG) Slog.d(TAG, "Update active - " + active); mActive = active; if (!active) { if (mFaceDown && mPreviousResultTime != USER_INTERACTION) { mPreviousResultType = SCREEN_OFF_RESULT; mPreviousResultTime = currentTime; final boolean sawRecentInteraction = mPreviousResultType == USER_INTERACTION && currentTime - mPreviousResultTime < mUserInteractionBackoffMillis; final boolean shouldBeActive = mInteractive && mIsEnabled && !sawRecentInteraction; if (mActive != shouldBeActive) { if (shouldBeActive) { mSensorManager.registerListener( this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); if (mPreviousResultType == SCREEN_OFF_RESULT) { logScreenOff(); } } else { mSensorManager.unregisterListener(this); mFaceDown = false; mOnFlip.accept(false); } else { if (mPreviousResultType == SCREEN_OFF_RESULT) { logScreenOff(); } mSensorManager.registerListener( this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); } mActive = shouldBeActive; if (DEBUG) Slog.d(TAG, "Update active - " + shouldBeActive); } } Loading Loading @@ -389,6 +393,7 @@ public class FaceDownDetector implements SensorEventListener { case KEY_TIME_THRESHOLD_MILLIS: case KEY_FEATURE_ENABLED: readValuesFromDeviceConfig(); updateActiveState(); return; default: Slog.i(TAG, "Ignoring change on " + key); Loading @@ -401,8 +406,18 @@ public class FaceDownDetector implements SensorEventListener { mZAccelerationThreshold = getZAccelerationThreshold(); mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f; mTimeThreshold = getTimeThreshold(); mIsEnabled = isEnabled(); mUserInteractionBackoffMillis = getUserInteractionBackoffMillis(); final boolean oldEnabled = mIsEnabled; mIsEnabled = isEnabled(); if (oldEnabled != mIsEnabled) { if (!mIsEnabled) { mContext.unregisterReceiver(mScreenReceiver); mInteractive = false; } else { registerScreenReceiver(mContext); mInteractive = mContext.getSystemService(PowerManager.class).isInteractive(); } } Slog.i(TAG, "readValuesFromDeviceConfig():" + "\nmAccelerationThreshold=" + mAccelerationThreshold Loading @@ -423,9 +438,11 @@ public class FaceDownDetector implements SensorEventListener { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { checkAndUpdateActiveState(false); mInteractive = false; updateActiveState(); } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { checkAndUpdateActiveState(true); mInteractive = true; updateActiveState(); } } } Loading services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java→services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java +119 −29 Original line number Diff line number Diff line Loading @@ -16,59 +16,74 @@ package com.android.server.power; import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static com.android.server.power.FaceDownDetector.KEY_FEATURE_ENABLED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.os.PowerManager; import android.provider.DeviceConfig; import android.testing.TestableContext; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.display.TestUtils; import com.android.server.testables.TestableDeviceConfig; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class FaceDownDetectorTest { @ClassRule public static final TestableContext sContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Rule public TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); private final FaceDownDetector mFaceDownDetector = new FaceDownDetector(this::onFlip); private final FaceDownDetector mFaceDownDetector = new FaceDownDetector(this::onFlip); @Mock private SensorManager mSensorManager; @Mock private PowerManager mPowerManager; private long mCurrentTime; private int mOnFaceDownCalls = 0; private int mOnFaceDownExitCalls = 0; private Duration mCurrentTime; private int mOnFaceDownCalls; private int mOnFaceDownExitCalls; @Before public void setup() { public void setup() throws Exception { MockitoAnnotations.initMocks(this); sContext.addMockSystemService(SensorManager.class, mSensorManager); mCurrentTime = 0; sContext.addMockSystemService(PowerManager.class, mPowerManager); doReturn(true).when(mPowerManager).isInteractive(); DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED, "true", false); mCurrentTime = Duration.ZERO; mOnFaceDownCalls = 0; mOnFaceDownExitCalls = 0; } @Test public void faceDownFor2Seconds_triggersFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); // Face up // Using 0.5 on x to simulate constant acceleration, such as a sloped surface. mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f)); for (int i = 0; i < 200; i++) { advanceTime(Duration.ofMillis(20)); mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f)); } triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(1); assertThat(mOnFaceDownExitCalls).isEqualTo(0); Loading Loading @@ -111,15 +126,7 @@ public class FaceDownDetectorTest { public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception { mFaceDownDetector.systemReady(sContext); // Face up // Using 0.5 on x to simulate constant acceleration, such as a sloped surface. mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f)); // Trigger face down for (int i = 0; i < 100; i++) { advanceTime(Duration.ofMillis(20)); mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f)); } triggerFaceDown(); // Phone flips for (int i = 0; i < 10; i++) { Loading @@ -131,8 +138,71 @@ public class FaceDownDetectorTest { assertThat(mOnFaceDownExitCalls).isEqualTo(1); } @Test public void notInteractive_doesNotTriggerFaceDown() throws Exception { doReturn(false).when(mPowerManager).isInteractive(); mFaceDownDetector.systemReady(sContext); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(0); assertThat(mOnFaceDownExitCalls).isEqualTo(0); } @Test public void afterDisablingFeature_doesNotTriggerFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); setEnabled(false); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(0); } @Test public void afterReenablingWhileNonInteractive_doesNotTriggerFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); setEnabled(false); doReturn(false).when(mPowerManager).isInteractive(); setEnabled(true); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(0); } @Test public void afterReenablingWhileInteractive_doesTriggerFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); setEnabled(false); setEnabled(true); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(1); } private void triggerFaceDown() throws Exception { // Face up // Using 0.5 on x to simulate constant acceleration, such as a sloped surface. mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f)); for (int i = 0; i < 200; i++) { advanceTime(Duration.ofMillis(20)); mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f)); } } private void setEnabled(Boolean enabled) throws Exception { DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED, enabled.toString(), false); waitForListenerToHandle(); } private void advanceTime(Duration duration) { mCurrentTime += duration.toNanos(); mCurrentTime = mCurrentTime.plus(duration); } /** Loading @@ -146,12 +216,11 @@ public class FaceDownDetectorTest { SensorEvent.class.getDeclaredConstructor(int.class); constructor.setAccessible(true); final SensorEvent event = constructor.newInstance(3); event.sensor = TestUtils.createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER); event.sensor = createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER); event.values[0] = x; event.values[1] = y; event.values[2] = gravity; event.timestamp = mCurrentTime; event.timestamp = mCurrentTime.toNanos(); return event; } Loading @@ -162,4 +231,25 @@ public class FaceDownDetectorTest { mOnFaceDownExitCalls++; } } private Sensor createSensor(int type, String strType) throws Exception { Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); constr.setAccessible(true); Sensor sensor = constr.newInstance(); Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE); setter.setAccessible(true); setter.invoke(sensor, type); if (strType != null) { Field f = sensor.getClass().getDeclaredField("mStringType"); f.setAccessible(true); f.set(sensor, strType); } return sensor; } private void waitForListenerToHandle() throws Exception { final CountDownLatch latch = new CountDownLatch(1); sContext.getMainExecutor().execute(latch::countDown); assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); } } Loading
services/core/java/com/android/server/power/FaceDownDetector.java +44 −27 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; import android.provider.DeviceConfig; import android.util.Slog; Loading Loading @@ -66,7 +67,7 @@ public class FaceDownDetector implements SensorEventListener { private static final float MOVING_AVERAGE_WEIGHT = 0.5f; /** DeviceConfig flag name, if {@code true}, enables Face Down features. */ private static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off"; static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off"; /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_FEATURE_ENABLED = true; Loading Loading @@ -139,6 +140,7 @@ public class FaceDownDetector implements SensorEventListener { new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT); private boolean mFaceDown = false; private boolean mInteractive = false; private boolean mActive = false; private float mPrevAcceleration = 0; Loading @@ -149,62 +151,64 @@ public class FaceDownDetector implements SensorEventListener { private final Handler mHandler; private final Runnable mUserActivityRunnable; private final BroadcastReceiver mScreenReceiver; private Context mContext; public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) { mOnFlip = Objects.requireNonNull(onFlip); mHandler = new Handler(Looper.getMainLooper()); mScreenReceiver = new ScreenStateReceiver(); mUserActivityRunnable = () -> { if (mFaceDown) { exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime); checkAndUpdateActiveState(false); updateActiveState(); } }; } /** Initializes the FaceDownDetector and all necessary listeners. */ public void systemReady(Context context) { mContext = context; mSensorManager = context.getSystemService(SensorManager.class); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); readValuesFromDeviceConfig(); checkAndUpdateActiveState(true); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE, ActivityThread.currentApplication().getMainExecutor(), (properties) -> onDeviceConfigChange(properties.getKeyset())); updateActiveState(); } private void registerScreenReceiver(Context context) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); context.registerReceiver(new ScreenStateReceiver(), intentFilter); context.registerReceiver(mScreenReceiver, intentFilter); } /** * Sets the active state of the detector. If false, we will not process accelerometer changes. */ private void checkAndUpdateActiveState(boolean active) { if (mIsEnabled && mActive != active) { private void updateActiveState() { final long currentTime = SystemClock.uptimeMillis(); // Don't make active if there was recently a user interaction while face down. if (active && mPreviousResultType == USER_INTERACTION && currentTime - mPreviousResultTime < mUserInteractionBackoffMillis) { return; } if (DEBUG) Slog.d(TAG, "Update active - " + active); mActive = active; if (!active) { if (mFaceDown && mPreviousResultTime != USER_INTERACTION) { mPreviousResultType = SCREEN_OFF_RESULT; mPreviousResultTime = currentTime; final boolean sawRecentInteraction = mPreviousResultType == USER_INTERACTION && currentTime - mPreviousResultTime < mUserInteractionBackoffMillis; final boolean shouldBeActive = mInteractive && mIsEnabled && !sawRecentInteraction; if (mActive != shouldBeActive) { if (shouldBeActive) { mSensorManager.registerListener( this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); if (mPreviousResultType == SCREEN_OFF_RESULT) { logScreenOff(); } } else { mSensorManager.unregisterListener(this); mFaceDown = false; mOnFlip.accept(false); } else { if (mPreviousResultType == SCREEN_OFF_RESULT) { logScreenOff(); } mSensorManager.registerListener( this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); } mActive = shouldBeActive; if (DEBUG) Slog.d(TAG, "Update active - " + shouldBeActive); } } Loading Loading @@ -389,6 +393,7 @@ public class FaceDownDetector implements SensorEventListener { case KEY_TIME_THRESHOLD_MILLIS: case KEY_FEATURE_ENABLED: readValuesFromDeviceConfig(); updateActiveState(); return; default: Slog.i(TAG, "Ignoring change on " + key); Loading @@ -401,8 +406,18 @@ public class FaceDownDetector implements SensorEventListener { mZAccelerationThreshold = getZAccelerationThreshold(); mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f; mTimeThreshold = getTimeThreshold(); mIsEnabled = isEnabled(); mUserInteractionBackoffMillis = getUserInteractionBackoffMillis(); final boolean oldEnabled = mIsEnabled; mIsEnabled = isEnabled(); if (oldEnabled != mIsEnabled) { if (!mIsEnabled) { mContext.unregisterReceiver(mScreenReceiver); mInteractive = false; } else { registerScreenReceiver(mContext); mInteractive = mContext.getSystemService(PowerManager.class).isInteractive(); } } Slog.i(TAG, "readValuesFromDeviceConfig():" + "\nmAccelerationThreshold=" + mAccelerationThreshold Loading @@ -423,9 +438,11 @@ public class FaceDownDetector implements SensorEventListener { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { checkAndUpdateActiveState(false); mInteractive = false; updateActiveState(); } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { checkAndUpdateActiveState(true); mInteractive = true; updateActiveState(); } } } Loading
services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java→services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java +119 −29 Original line number Diff line number Diff line Loading @@ -16,59 +16,74 @@ package com.android.server.power; import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static com.android.server.power.FaceDownDetector.KEY_FEATURE_ENABLED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.os.PowerManager; import android.provider.DeviceConfig; import android.testing.TestableContext; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.display.TestUtils; import com.android.server.testables.TestableDeviceConfig; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class FaceDownDetectorTest { @ClassRule public static final TestableContext sContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Rule public TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); private final FaceDownDetector mFaceDownDetector = new FaceDownDetector(this::onFlip); private final FaceDownDetector mFaceDownDetector = new FaceDownDetector(this::onFlip); @Mock private SensorManager mSensorManager; @Mock private PowerManager mPowerManager; private long mCurrentTime; private int mOnFaceDownCalls = 0; private int mOnFaceDownExitCalls = 0; private Duration mCurrentTime; private int mOnFaceDownCalls; private int mOnFaceDownExitCalls; @Before public void setup() { public void setup() throws Exception { MockitoAnnotations.initMocks(this); sContext.addMockSystemService(SensorManager.class, mSensorManager); mCurrentTime = 0; sContext.addMockSystemService(PowerManager.class, mPowerManager); doReturn(true).when(mPowerManager).isInteractive(); DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED, "true", false); mCurrentTime = Duration.ZERO; mOnFaceDownCalls = 0; mOnFaceDownExitCalls = 0; } @Test public void faceDownFor2Seconds_triggersFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); // Face up // Using 0.5 on x to simulate constant acceleration, such as a sloped surface. mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f)); for (int i = 0; i < 200; i++) { advanceTime(Duration.ofMillis(20)); mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f)); } triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(1); assertThat(mOnFaceDownExitCalls).isEqualTo(0); Loading Loading @@ -111,15 +126,7 @@ public class FaceDownDetectorTest { public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception { mFaceDownDetector.systemReady(sContext); // Face up // Using 0.5 on x to simulate constant acceleration, such as a sloped surface. mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f)); // Trigger face down for (int i = 0; i < 100; i++) { advanceTime(Duration.ofMillis(20)); mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f)); } triggerFaceDown(); // Phone flips for (int i = 0; i < 10; i++) { Loading @@ -131,8 +138,71 @@ public class FaceDownDetectorTest { assertThat(mOnFaceDownExitCalls).isEqualTo(1); } @Test public void notInteractive_doesNotTriggerFaceDown() throws Exception { doReturn(false).when(mPowerManager).isInteractive(); mFaceDownDetector.systemReady(sContext); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(0); assertThat(mOnFaceDownExitCalls).isEqualTo(0); } @Test public void afterDisablingFeature_doesNotTriggerFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); setEnabled(false); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(0); } @Test public void afterReenablingWhileNonInteractive_doesNotTriggerFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); setEnabled(false); doReturn(false).when(mPowerManager).isInteractive(); setEnabled(true); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(0); } @Test public void afterReenablingWhileInteractive_doesTriggerFaceDown() throws Exception { mFaceDownDetector.systemReady(sContext); setEnabled(false); setEnabled(true); triggerFaceDown(); assertThat(mOnFaceDownCalls).isEqualTo(1); } private void triggerFaceDown() throws Exception { // Face up // Using 0.5 on x to simulate constant acceleration, such as a sloped surface. mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f)); for (int i = 0; i < 200; i++) { advanceTime(Duration.ofMillis(20)); mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f)); } } private void setEnabled(Boolean enabled) throws Exception { DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED, enabled.toString(), false); waitForListenerToHandle(); } private void advanceTime(Duration duration) { mCurrentTime += duration.toNanos(); mCurrentTime = mCurrentTime.plus(duration); } /** Loading @@ -146,12 +216,11 @@ public class FaceDownDetectorTest { SensorEvent.class.getDeclaredConstructor(int.class); constructor.setAccessible(true); final SensorEvent event = constructor.newInstance(3); event.sensor = TestUtils.createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER); event.sensor = createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER); event.values[0] = x; event.values[1] = y; event.values[2] = gravity; event.timestamp = mCurrentTime; event.timestamp = mCurrentTime.toNanos(); return event; } Loading @@ -162,4 +231,25 @@ public class FaceDownDetectorTest { mOnFaceDownExitCalls++; } } private Sensor createSensor(int type, String strType) throws Exception { Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); constr.setAccessible(true); Sensor sensor = constr.newInstance(); Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE); setter.setAccessible(true); setter.invoke(sensor, type); if (strType != null) { Field f = sensor.getClass().getDeclaredField("mStringType"); f.setAccessible(true); f.set(sensor, strType); } return sensor; } private void waitForListenerToHandle() throws Exception { final CountDownLatch latch = new CountDownLatch(1); sContext.getMainExecutor().execute(latch::countDown); assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); } }