Merge "Add a final lux method to ALSProbe." into tm-qpr-dev am: 9541293118
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20190474 Change-Id: I81c3e58d2ea8df676a4f33bd2a8680f7a942de9f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -30,7 +30,10 @@ import android.util.Slog;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.biometrics.sensors.BaseClientMonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Probe for ambient light. */
|
||||
final class ALSProbe implements Probe {
|
||||
@@ -47,12 +50,18 @@ final class ALSProbe implements Probe {
|
||||
|
||||
private boolean mEnabled = false;
|
||||
private boolean mDestroyed = false;
|
||||
private boolean mDestroyRequested = false;
|
||||
private boolean mDisableRequested = false;
|
||||
private volatile NextConsumer mNextConsumer = null;
|
||||
private volatile float mLastAmbientLux = -1;
|
||||
|
||||
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
mLastAmbientLux = event.values[0];
|
||||
if (mNextConsumer != null) {
|
||||
completeNextConsumer(mLastAmbientLux);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,29 +111,84 @@ final class ALSProbe implements Probe {
|
||||
|
||||
@Override
|
||||
public synchronized void enable() {
|
||||
if (!mDestroyed) {
|
||||
if (!mDestroyed && !mDestroyRequested) {
|
||||
mDisableRequested = false;
|
||||
enableLightSensorLoggingLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void disable() {
|
||||
if (!mDestroyed) {
|
||||
mDisableRequested = true;
|
||||
|
||||
// if a final consumer is set it will call destroy/disable on the next value if requested
|
||||
if (!mDestroyed && mNextConsumer == null) {
|
||||
disableLightSensorLoggingLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void destroy() {
|
||||
disable();
|
||||
mDestroyed = true;
|
||||
mDestroyRequested = true;
|
||||
|
||||
// if a final consumer is set it will call destroy/disable on the next value if requested
|
||||
if (!mDestroyed && mNextConsumer == null) {
|
||||
disable();
|
||||
mDestroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** The most recent lux reading. */
|
||||
public float getCurrentLux() {
|
||||
public float getMostRecentLux() {
|
||||
return mLastAmbientLux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener for the next available ALS reading, which will be reported to the given
|
||||
* consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value
|
||||
* is available.
|
||||
*
|
||||
* This method is intended to be used for event logs that occur when the screen may be
|
||||
* off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn
|
||||
* on the sensor (if needed), fetch & report the first value, and then destroy or disable this
|
||||
* probe (if needed).
|
||||
*
|
||||
* @param consumer consumer to notify when the data is available
|
||||
* @param handler handler for notifying the consumer, or null
|
||||
*/
|
||||
public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer,
|
||||
@Nullable Handler handler) {
|
||||
final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
|
||||
final float current = mLastAmbientLux;
|
||||
if (current > 0) {
|
||||
nextConsumer.consume(current);
|
||||
} else if (mDestroyed) {
|
||||
nextConsumer.consume(-1f);
|
||||
} else if (mNextConsumer != null) {
|
||||
mNextConsumer.add(nextConsumer);
|
||||
} else {
|
||||
mNextConsumer = nextConsumer;
|
||||
enableLightSensorLoggingLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void completeNextConsumer(float value) {
|
||||
Slog.v(TAG, "Finishing next consumer");
|
||||
|
||||
final NextConsumer consumer = mNextConsumer;
|
||||
mNextConsumer = null;
|
||||
|
||||
if (mDestroyRequested) {
|
||||
destroy();
|
||||
} else if (mDisableRequested) {
|
||||
disable();
|
||||
}
|
||||
|
||||
if (consumer != null) {
|
||||
consumer.consume(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void enableLightSensorLoggingLocked() {
|
||||
if (!mEnabled) {
|
||||
mEnabled = true;
|
||||
@@ -160,4 +224,30 @@ final class ALSProbe implements Probe {
|
||||
+ mLightSensorListener.hashCode());
|
||||
disable();
|
||||
}
|
||||
|
||||
private static class NextConsumer {
|
||||
@NonNull private final Consumer<Float> mConsumer;
|
||||
@Nullable private final Handler mHandler;
|
||||
@NonNull private final List<NextConsumer> mOthers = new ArrayList<>();
|
||||
|
||||
private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) {
|
||||
mConsumer = consumer;
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
public void consume(float value) {
|
||||
if (mHandler != null) {
|
||||
mHandler.post(() -> mConsumer.accept(value));
|
||||
} else {
|
||||
mConsumer.accept(value);
|
||||
}
|
||||
for (NextConsumer c : mOthers) {
|
||||
c.consume(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(NextConsumer consumer) {
|
||||
mOthers.add(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,7 @@ public class BiometricFrameworkStatsLogger {
|
||||
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
|
||||
public void authenticate(OperationContext operationContext,
|
||||
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
|
||||
int authState, boolean requireConfirmation,
|
||||
int targetUserId, float ambientLightLux) {
|
||||
int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
|
||||
statsModality,
|
||||
targetUserId,
|
||||
@@ -80,6 +79,16 @@ public class BiometricFrameworkStatsLogger {
|
||||
operationContext.isAod);
|
||||
}
|
||||
|
||||
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
|
||||
public void authenticate(OperationContext operationContext,
|
||||
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
|
||||
int authState, boolean requireConfirmation, int targetUserId, ALSProbe alsProbe) {
|
||||
alsProbe.awaitNextLux((ambientLightLux) -> {
|
||||
authenticate(operationContext, statsModality, statsAction, statsClient, isDebug,
|
||||
latency, authState, requireConfirmation, targetUserId, ambientLightLux);
|
||||
}, null /* handler */);
|
||||
}
|
||||
|
||||
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
|
||||
public void enroll(int statsModality, int statsAction, int statsClient,
|
||||
int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) {
|
||||
|
||||
@@ -220,7 +220,7 @@ public class BiometricLogger {
|
||||
+ ", RequireConfirmation: " + requireConfirmation
|
||||
+ ", State: " + authState
|
||||
+ ", Latency: " + latency
|
||||
+ ", Lux: " + mALSProbe.getCurrentLux());
|
||||
+ ", Lux: " + mALSProbe.getMostRecentLux());
|
||||
} else {
|
||||
Slog.v(TAG, "Authentication latency: " + latency);
|
||||
}
|
||||
@@ -231,7 +231,7 @@ public class BiometricLogger {
|
||||
|
||||
mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
|
||||
Utils.isDebugEnabled(context, targetUserId),
|
||||
latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux());
|
||||
latency, authState, requireConfirmation, targetUserId, mALSProbe);
|
||||
}
|
||||
|
||||
/** Log enrollment outcome. */
|
||||
@@ -245,7 +245,7 @@ public class BiometricLogger {
|
||||
+ ", User: " + targetUserId
|
||||
+ ", Client: " + mStatsClient
|
||||
+ ", Latency: " + latency
|
||||
+ ", Lux: " + mALSProbe.getCurrentLux()
|
||||
+ ", Lux: " + mALSProbe.getMostRecentLux()
|
||||
+ ", Success: " + enrollSuccessful);
|
||||
} else {
|
||||
Slog.v(TAG, "Enroll latency: " + latency);
|
||||
@@ -256,7 +256,7 @@ public class BiometricLogger {
|
||||
}
|
||||
|
||||
mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
|
||||
targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux());
|
||||
targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux());
|
||||
}
|
||||
|
||||
/** Report unexpected enrollment reported by the HAL. */
|
||||
|
||||
@@ -333,6 +333,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
|
||||
mALSProbeCallback.getProbe().disable();
|
||||
}
|
||||
});
|
||||
if (getBiometricContext().isAwake()) {
|
||||
mALSProbeCallback.getProbe().enable();
|
||||
}
|
||||
|
||||
if (session.hasContextMethods()) {
|
||||
return session.getSession().authenticateWithContext(mOperationId, opContext);
|
||||
|
||||
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -50,6 +51,9 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Presubmit
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@@ -93,7 +97,7 @@ public class ALSProbeTest {
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 2, new float[]{value}));
|
||||
|
||||
assertThat(mProbe.getCurrentLux()).isEqualTo(value);
|
||||
assertThat(mProbe.getMostRecentLux()).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,13 +125,17 @@ public class ALSProbeTest {
|
||||
mProbe.destroy();
|
||||
mProbe.enable();
|
||||
|
||||
AtomicInteger lux = new AtomicInteger(10);
|
||||
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
|
||||
|
||||
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
assertThat(lux.get()).isLessThan(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisabledReportsNegativeValue() {
|
||||
assertThat(mProbe.getCurrentLux()).isLessThan(0f);
|
||||
assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
|
||||
|
||||
mProbe.enable();
|
||||
verify(mSensorManager).registerListener(
|
||||
@@ -136,7 +144,7 @@ public class ALSProbeTest {
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
|
||||
mProbe.disable();
|
||||
|
||||
assertThat(mProbe.getCurrentLux()).isLessThan(0f);
|
||||
assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -150,7 +158,7 @@ public class ALSProbeTest {
|
||||
|
||||
verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
assertThat(mProbe.getCurrentLux()).isLessThan(0f);
|
||||
assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -166,7 +174,148 @@ public class ALSProbeTest {
|
||||
|
||||
verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
assertThat(mProbe.getCurrentLux()).isLessThan(0f);
|
||||
assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
|
||||
testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextLuxWhenAlreadyEnabledAndAvailable() {
|
||||
testNextLuxWhenAlreadyEnabled(true /* dataIsAvailable */);
|
||||
}
|
||||
|
||||
private void testNextLuxWhenAlreadyEnabled(boolean dataIsAvailable) {
|
||||
final List<Integer> values = List.of(1, 2, 3, 4, 6);
|
||||
mProbe.enable();
|
||||
|
||||
verify(mSensorManager).registerListener(
|
||||
mSensorEventListenerCaptor.capture(), any(), anyInt());
|
||||
|
||||
if (dataIsAvailable) {
|
||||
for (int v : values) {
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
|
||||
}
|
||||
}
|
||||
AtomicInteger lux = new AtomicInteger(-1);
|
||||
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
|
||||
if (!dataIsAvailable) {
|
||||
for (int v : values) {
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
|
||||
}
|
||||
}
|
||||
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{200f}));
|
||||
|
||||
// should remain enabled
|
||||
assertThat(lux.get()).isEqualTo(values.get(dataIsAvailable ? values.size() - 1 : 0));
|
||||
verify(mSensorManager, never()).unregisterListener(any(SensorEventListener.class));
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
|
||||
final int anotherValue = 12;
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{12}));
|
||||
assertThat(mProbe.getMostRecentLux()).isEqualTo(anotherValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextLuxWhenNotEnabled() {
|
||||
testNextLuxWhenNotEnabled(false /* enableWhileWaiting */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextLuxWhenNotEnabledButEnabledLater() {
|
||||
testNextLuxWhenNotEnabled(true /* enableWhileWaiting */);
|
||||
}
|
||||
|
||||
private void testNextLuxWhenNotEnabled(boolean enableWhileWaiting) {
|
||||
final List<Integer> values = List.of(1, 2, 3, 4, 6);
|
||||
mProbe.disable();
|
||||
|
||||
AtomicInteger lux = new AtomicInteger(-1);
|
||||
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
|
||||
|
||||
if (enableWhileWaiting) {
|
||||
mProbe.enable();
|
||||
}
|
||||
|
||||
verify(mSensorManager).registerListener(
|
||||
mSensorEventListenerCaptor.capture(), any(), anyInt());
|
||||
for (int v : values) {
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{v}));
|
||||
}
|
||||
|
||||
// should restore the disabled state
|
||||
assertThat(lux.get()).isEqualTo(values.get(0));
|
||||
verify(mSensorManager, enableWhileWaiting ? never() : times(1)).unregisterListener(
|
||||
any(SensorEventListener.class));
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextLuxIsNotCanceledByDisableOrDestroy() {
|
||||
final int value = 7;
|
||||
AtomicInteger lux = new AtomicInteger(-1);
|
||||
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
|
||||
|
||||
verify(mSensorManager).registerListener(
|
||||
mSensorEventListenerCaptor.capture(), any(), anyInt());
|
||||
|
||||
mProbe.destroy();
|
||||
mProbe.disable();
|
||||
|
||||
assertThat(lux.get()).isEqualTo(-1);
|
||||
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{value}));
|
||||
|
||||
assertThat(lux.get()).isEqualTo(value);
|
||||
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{value + 1}));
|
||||
|
||||
// should remain destroyed
|
||||
mProbe.enable();
|
||||
|
||||
assertThat(lux.get()).isEqualTo(value);
|
||||
verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleNextConsumers() {
|
||||
final int value = 7;
|
||||
AtomicInteger lux = new AtomicInteger(-1);
|
||||
AtomicInteger lux2 = new AtomicInteger(-1);
|
||||
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
|
||||
mProbe.awaitNextLux((v) -> lux2.set(Math.round(v)), null /* handler */);
|
||||
|
||||
verify(mSensorManager).registerListener(
|
||||
mSensorEventListenerCaptor.capture(), any(), anyInt());
|
||||
mSensorEventListenerCaptor.getValue().onSensorChanged(
|
||||
new SensorEvent(mLightSensor, 1, 1, new float[]{value}));
|
||||
|
||||
assertThat(lux.get()).isEqualTo(value);
|
||||
assertThat(lux2.get()).isEqualTo(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNextLuxWhenDestroyed() {
|
||||
mProbe.destroy();
|
||||
|
||||
AtomicInteger lux = new AtomicInteger(-20);
|
||||
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
|
||||
|
||||
assertThat(lux.get()).isEqualTo(-1);
|
||||
verify(mSensorManager, never()).registerListener(
|
||||
mSensorEventListenerCaptor.capture(), any(), anyInt());
|
||||
verifyNoMoreInteractions(mSensorManager);
|
||||
}
|
||||
|
||||
private void moveTimeBy(long millis) {
|
||||
|
||||
@@ -121,7 +121,7 @@ public class BiometricLoggerTest {
|
||||
verify(mSink).authenticate(eq(mOpContext),
|
||||
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
|
||||
anyLong(), anyInt(), eq(requireConfirmation),
|
||||
eq(targetUserId), anyFloat());
|
||||
eq(targetUserId), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.same;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -215,7 +216,7 @@ public class FingerprintAuthenticationClientTest {
|
||||
|
||||
@Test
|
||||
public void luxProbeWhenAwake() throws RemoteException {
|
||||
when(mBiometricContext.isAwake()).thenReturn(false, true, false);
|
||||
when(mBiometricContext.isAwake()).thenReturn(false);
|
||||
when(mBiometricContext.isAod()).thenReturn(false);
|
||||
final FingerprintAuthenticationClient client = createClient();
|
||||
client.start(mCallback);
|
||||
@@ -228,14 +229,37 @@ public class FingerprintAuthenticationClientTest {
|
||||
verify(mLuxProbe, never()).enable();
|
||||
|
||||
reset(mLuxProbe);
|
||||
when(mBiometricContext.isAwake()).thenReturn(true);
|
||||
|
||||
mContextInjector.getValue().accept(opContext);
|
||||
verify(mLuxProbe).enable();
|
||||
verify(mLuxProbe, never()).disable();
|
||||
|
||||
when(mBiometricContext.isAwake()).thenReturn(false);
|
||||
|
||||
mContextInjector.getValue().accept(opContext);
|
||||
verify(mLuxProbe).disable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void luxProbeEnabledOnStartWhenWake() throws RemoteException {
|
||||
luxProbeEnabledOnStart(true /* isAwake */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void luxProbeNotEnabledOnStartWhenNotWake() throws RemoteException {
|
||||
luxProbeEnabledOnStart(false /* isAwake */);
|
||||
}
|
||||
|
||||
private void luxProbeEnabledOnStart(boolean isAwake) throws RemoteException {
|
||||
when(mBiometricContext.isAwake()).thenReturn(isAwake);
|
||||
when(mBiometricContext.isAod()).thenReturn(false);
|
||||
final FingerprintAuthenticationClient client = createClient();
|
||||
client.start(mCallback);
|
||||
|
||||
verify(mLuxProbe, isAwake ? times(1) : never()).enable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void luxProbeDisabledOnAod() throws RemoteException {
|
||||
when(mBiometricContext.isAwake()).thenReturn(false);
|
||||
|
||||
Reference in New Issue
Block a user