summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/power/FaceDownDetector.java71
-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();
+ }
}