diff options
| -rw-r--r-- | services/core/java/com/android/server/power/FaceDownDetector.java | 71 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java (renamed from services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java) | 148 |
2 files changed, 163 insertions, 56 deletions
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java index 2442079bec8b..fe9663aaabe5 100644 --- a/services/core/java/com/android/server/power/FaceDownDetector.java +++ b/services/core/java/com/android/server/power/FaceDownDetector.java @@ -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; @@ -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; @@ -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; @@ -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) { - 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; + private void updateActiveState() { + final long currentTime = SystemClock.uptimeMillis(); + 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); } } @@ -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); @@ -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 @@ -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(); } } } diff --git a/services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java index ef20ee7e6ecd..8ecb07158564 100644 --- a/services/tests/servicestests/src/com/android/server/power/FaceDownDetectorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java @@ -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); @@ -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++) { @@ -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); } /** @@ -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; } @@ -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(); + } } |