Merge "Add handling for disabling flip to screen off feature." into sc-dev

This commit is contained in:
Brian Quezada
2021-03-10 15:09:35 +00:00
committed by Android (Google) Code Review
2 changed files with 164 additions and 57 deletions

View File

@@ -30,6 +30,7 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock; import android.os.SystemClock;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
import android.util.Slog; import android.util.Slog;
@@ -66,7 +67,7 @@ public class FaceDownDetector implements SensorEventListener {
private static final float MOVING_AVERAGE_WEIGHT = 0.5f; private static final float MOVING_AVERAGE_WEIGHT = 0.5f;
/** DeviceConfig flag name, if {@code true}, enables Face Down features. */ /** 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. */ /** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_FEATURE_ENABLED = true; private static final boolean DEFAULT_FEATURE_ENABLED = true;
@@ -139,6 +140,7 @@ public class FaceDownDetector implements SensorEventListener {
new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT); new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT);
private boolean mFaceDown = false; private boolean mFaceDown = false;
private boolean mInteractive = false;
private boolean mActive = false; private boolean mActive = false;
private float mPrevAcceleration = 0; private float mPrevAcceleration = 0;
@@ -149,62 +151,64 @@ public class FaceDownDetector implements SensorEventListener {
private final Handler mHandler; private final Handler mHandler;
private final Runnable mUserActivityRunnable; private final Runnable mUserActivityRunnable;
private final BroadcastReceiver mScreenReceiver;
private Context mContext;
public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) { public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) {
mOnFlip = Objects.requireNonNull(onFlip); mOnFlip = Objects.requireNonNull(onFlip);
mHandler = new Handler(Looper.getMainLooper()); mHandler = new Handler(Looper.getMainLooper());
mScreenReceiver = new ScreenStateReceiver();
mUserActivityRunnable = () -> { mUserActivityRunnable = () -> {
if (mFaceDown) { if (mFaceDown) {
exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime); exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime);
checkAndUpdateActiveState(false); updateActiveState();
} }
}; };
} }
/** Initializes the FaceDownDetector and all necessary listeners. */ /** Initializes the FaceDownDetector and all necessary listeners. */
public void systemReady(Context context) { public void systemReady(Context context) {
mContext = context;
mSensorManager = context.getSystemService(SensorManager.class); mSensorManager = context.getSystemService(SensorManager.class);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
readValuesFromDeviceConfig(); readValuesFromDeviceConfig();
checkAndUpdateActiveState(true);
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE, DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
ActivityThread.currentApplication().getMainExecutor(), ActivityThread.currentApplication().getMainExecutor(),
(properties) -> onDeviceConfigChange(properties.getKeyset())); (properties) -> onDeviceConfigChange(properties.getKeyset()));
updateActiveState();
}
private void registerScreenReceiver(Context context) {
IntentFilter intentFilter = new IntentFilter(); IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 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. * Sets the active state of the detector. If false, we will not process accelerometer changes.
*/ */
private void checkAndUpdateActiveState(boolean active) { private void updateActiveState() {
if (mIsEnabled && mActive != active) {
final long currentTime = SystemClock.uptimeMillis(); final long currentTime = SystemClock.uptimeMillis();
// Don't make active if there was recently a user interaction while face down. final boolean sawRecentInteraction = mPreviousResultType == USER_INTERACTION
if (active && mPreviousResultType == USER_INTERACTION && currentTime - mPreviousResultTime < mUserInteractionBackoffMillis;
&& currentTime - mPreviousResultTime < mUserInteractionBackoffMillis) { final boolean shouldBeActive = mInteractive && mIsEnabled && !sawRecentInteraction;
return; if (mActive != shouldBeActive) {
} if (shouldBeActive) {
if (DEBUG) Slog.d(TAG, "Update active - " + active); mSensorManager.registerListener(
mActive = active; this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
if (!active) {
if (mFaceDown && mPreviousResultTime != USER_INTERACTION) {
mPreviousResultType = SCREEN_OFF_RESULT;
mPreviousResultTime = currentTime;
}
mSensorManager.unregisterListener(this);
mFaceDown = false;
mOnFlip.accept(false);
} else {
if (mPreviousResultType == SCREEN_OFF_RESULT) { if (mPreviousResultType == SCREEN_OFF_RESULT) {
logScreenOff(); logScreenOff();
} }
mSensorManager.registerListener( } else {
this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); mSensorManager.unregisterListener(this);
mFaceDown = false;
mOnFlip.accept(false);
} }
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_TIME_THRESHOLD_MILLIS:
case KEY_FEATURE_ENABLED: case KEY_FEATURE_ENABLED:
readValuesFromDeviceConfig(); readValuesFromDeviceConfig();
updateActiveState();
return; return;
default: default:
Slog.i(TAG, "Ignoring change on " + key); Slog.i(TAG, "Ignoring change on " + key);
@@ -401,8 +406,18 @@ public class FaceDownDetector implements SensorEventListener {
mZAccelerationThreshold = getZAccelerationThreshold(); mZAccelerationThreshold = getZAccelerationThreshold();
mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f; mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f;
mTimeThreshold = getTimeThreshold(); mTimeThreshold = getTimeThreshold();
mIsEnabled = isEnabled();
mUserInteractionBackoffMillis = getUserInteractionBackoffMillis(); 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():" Slog.i(TAG, "readValuesFromDeviceConfig():"
+ "\nmAccelerationThreshold=" + mAccelerationThreshold + "\nmAccelerationThreshold=" + mAccelerationThreshold
@@ -423,9 +438,11 @@ public class FaceDownDetector implements SensorEventListener {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
checkAndUpdateActiveState(false); mInteractive = false;
updateActiveState();
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
checkAndUpdateActiveState(true); mInteractive = true;
updateActiveState();
} }
} }
} }

View File

@@ -16,59 +16,74 @@
package com.android.server.power; 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 com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import android.hardware.Sensor; import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.testing.TestableContext; import android.testing.TestableContext;
import androidx.test.platform.app.InstrumentationRegistry; 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.Before;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class FaceDownDetectorTest { public class FaceDownDetectorTest {
@ClassRule @ClassRule
public static final TestableContext sContext = new TestableContext( public static final TestableContext sContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null); InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
public TestableDeviceConfig.TestableDeviceConfigRule
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
private final FaceDownDetector mFaceDownDetector = private final FaceDownDetector mFaceDownDetector = new FaceDownDetector(this::onFlip);
new FaceDownDetector(this::onFlip);
@Mock private SensorManager mSensorManager; @Mock private SensorManager mSensorManager;
@Mock private PowerManager mPowerManager;
private long mCurrentTime; private Duration mCurrentTime;
private int mOnFaceDownCalls = 0; private int mOnFaceDownCalls;
private int mOnFaceDownExitCalls = 0; private int mOnFaceDownExitCalls;
@Before @Before
public void setup() { public void setup() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
sContext.addMockSystemService(SensorManager.class, mSensorManager); 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 @Test
public void faceDownFor2Seconds_triggersFaceDown() throws Exception { public void faceDownFor2Seconds_triggersFaceDown() throws Exception {
mFaceDownDetector.systemReady(sContext); mFaceDownDetector.systemReady(sContext);
// Face up triggerFaceDown();
// 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));
}
assertThat(mOnFaceDownCalls).isEqualTo(1); assertThat(mOnFaceDownCalls).isEqualTo(1);
assertThat(mOnFaceDownExitCalls).isEqualTo(0); assertThat(mOnFaceDownExitCalls).isEqualTo(0);
@@ -111,15 +126,7 @@ public class FaceDownDetectorTest {
public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception { public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception {
mFaceDownDetector.systemReady(sContext); mFaceDownDetector.systemReady(sContext);
// Face up triggerFaceDown();
// 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));
}
// Phone flips // Phone flips
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
@@ -131,8 +138,71 @@ public class FaceDownDetectorTest {
assertThat(mOnFaceDownExitCalls).isEqualTo(1); 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) { private void advanceTime(Duration duration) {
mCurrentTime += duration.toNanos(); mCurrentTime = mCurrentTime.plus(duration);
} }
/** /**
@@ -146,12 +216,11 @@ public class FaceDownDetectorTest {
SensorEvent.class.getDeclaredConstructor(int.class); SensorEvent.class.getDeclaredConstructor(int.class);
constructor.setAccessible(true); constructor.setAccessible(true);
final SensorEvent event = constructor.newInstance(3); final SensorEvent event = constructor.newInstance(3);
event.sensor = event.sensor = createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER);
TestUtils.createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER);
event.values[0] = x; event.values[0] = x;
event.values[1] = y; event.values[1] = y;
event.values[2] = gravity; event.values[2] = gravity;
event.timestamp = mCurrentTime; event.timestamp = mCurrentTime.toNanos();
return event; return event;
} }
@@ -162,4 +231,25 @@ public class FaceDownDetectorTest {
mOnFaceDownExitCalls++; 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();
}
} }