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:
Joe Bolinger
2022-10-14 01:22:02 +00:00
committed by Automerger Merge Worker
7 changed files with 293 additions and 18 deletions

View File

@@ -30,7 +30,10 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BaseClientMonitor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/** Probe for ambient light. */ /** Probe for ambient light. */
final class ALSProbe implements Probe { final class ALSProbe implements Probe {
@@ -47,12 +50,18 @@ final class ALSProbe implements Probe {
private boolean mEnabled = false; private boolean mEnabled = false;
private boolean mDestroyed = false; private boolean mDestroyed = false;
private boolean mDestroyRequested = false;
private boolean mDisableRequested = false;
private volatile NextConsumer mNextConsumer = null;
private volatile float mLastAmbientLux = -1; private volatile float mLastAmbientLux = -1;
private final SensorEventListener mLightSensorListener = new SensorEventListener() { private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override @Override
public void onSensorChanged(SensorEvent event) { public void onSensorChanged(SensorEvent event) {
mLastAmbientLux = event.values[0]; mLastAmbientLux = event.values[0];
if (mNextConsumer != null) {
completeNextConsumer(mLastAmbientLux);
}
} }
@Override @Override
@@ -102,29 +111,84 @@ final class ALSProbe implements Probe {
@Override @Override
public synchronized void enable() { public synchronized void enable() {
if (!mDestroyed) { if (!mDestroyed && !mDestroyRequested) {
mDisableRequested = false;
enableLightSensorLoggingLocked(); enableLightSensorLoggingLocked();
} }
} }
@Override @Override
public synchronized void disable() { 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(); disableLightSensorLoggingLocked();
} }
} }
@Override @Override
public synchronized void destroy() { public synchronized void destroy() {
disable(); mDestroyRequested = true;
mDestroyed = 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. */ /** The most recent lux reading. */
public float getCurrentLux() { public float getMostRecentLux() {
return mLastAmbientLux; 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() { private void enableLightSensorLoggingLocked() {
if (!mEnabled) { if (!mEnabled) {
mEnabled = true; mEnabled = true;
@@ -160,4 +224,30 @@ final class ALSProbe implements Probe {
+ mLightSensorListener.hashCode()); + mLightSensorListener.hashCode());
disable(); 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);
}
}
} }

View File

@@ -62,8 +62,7 @@ public class BiometricFrameworkStatsLogger {
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
public void authenticate(OperationContext operationContext, public void authenticate(OperationContext operationContext,
int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, int statsModality, int statsAction, int statsClient, boolean isDebug, long latency,
int authState, boolean requireConfirmation, int authState, boolean requireConfirmation, int targetUserId, float ambientLightLux) {
int targetUserId, float ambientLightLux) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED,
statsModality, statsModality,
targetUserId, targetUserId,
@@ -80,6 +79,16 @@ public class BiometricFrameworkStatsLogger {
operationContext.isAod); 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}. */ /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
public void enroll(int statsModality, int statsAction, int statsClient, public void enroll(int statsModality, int statsAction, int statsClient,
int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) { int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) {

View File

@@ -220,7 +220,7 @@ public class BiometricLogger {
+ ", RequireConfirmation: " + requireConfirmation + ", RequireConfirmation: " + requireConfirmation
+ ", State: " + authState + ", State: " + authState
+ ", Latency: " + latency + ", Latency: " + latency
+ ", Lux: " + mALSProbe.getCurrentLux()); + ", Lux: " + mALSProbe.getMostRecentLux());
} else { } else {
Slog.v(TAG, "Authentication latency: " + latency); Slog.v(TAG, "Authentication latency: " + latency);
} }
@@ -231,7 +231,7 @@ public class BiometricLogger {
mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient, mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient,
Utils.isDebugEnabled(context, targetUserId), Utils.isDebugEnabled(context, targetUserId),
latency, authState, requireConfirmation, targetUserId, mALSProbe.getCurrentLux()); latency, authState, requireConfirmation, targetUserId, mALSProbe);
} }
/** Log enrollment outcome. */ /** Log enrollment outcome. */
@@ -245,7 +245,7 @@ public class BiometricLogger {
+ ", User: " + targetUserId + ", User: " + targetUserId
+ ", Client: " + mStatsClient + ", Client: " + mStatsClient
+ ", Latency: " + latency + ", Latency: " + latency
+ ", Lux: " + mALSProbe.getCurrentLux() + ", Lux: " + mALSProbe.getMostRecentLux()
+ ", Success: " + enrollSuccessful); + ", Success: " + enrollSuccessful);
} else { } else {
Slog.v(TAG, "Enroll latency: " + latency); Slog.v(TAG, "Enroll latency: " + latency);
@@ -256,7 +256,7 @@ public class BiometricLogger {
} }
mSink.enroll(mStatsModality, mStatsAction, mStatsClient, mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
targetUserId, latency, enrollSuccessful, mALSProbe.getCurrentLux()); targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux());
} }
/** Report unexpected enrollment reported by the HAL. */ /** Report unexpected enrollment reported by the HAL. */

View File

@@ -333,6 +333,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mALSProbeCallback.getProbe().disable(); mALSProbeCallback.getProbe().disable();
} }
}); });
if (getBiometricContext().isAwake()) {
mALSProbeCallback.getProbe().enable();
}
if (session.hasContextMethods()) { if (session.hasContextMethods()) {
return session.getSession().authenticateWithContext(mOperationId, opContext); return session.getSession().authenticateWithContext(mOperationId, opContext);

View File

@@ -23,6 +23,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -50,6 +51,9 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Presubmit @Presubmit
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@@ -93,7 +97,7 @@ public class ALSProbeTest {
mSensorEventListenerCaptor.getValue().onSensorChanged( mSensorEventListenerCaptor.getValue().onSensorChanged(
new SensorEvent(mLightSensor, 1, 2, new float[]{value})); new SensorEvent(mLightSensor, 1, 2, new float[]{value}));
assertThat(mProbe.getCurrentLux()).isEqualTo(value); assertThat(mProbe.getMostRecentLux()).isEqualTo(value);
} }
@Test @Test
@@ -121,13 +125,17 @@ public class ALSProbeTest {
mProbe.destroy(); mProbe.destroy();
mProbe.enable(); mProbe.enable();
AtomicInteger lux = new AtomicInteger(10);
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
verify(mSensorManager, never()).registerListener(any(), any(), anyInt()); verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager); verifyNoMoreInteractions(mSensorManager);
assertThat(lux.get()).isLessThan(0);
} }
@Test @Test
public void testDisabledReportsNegativeValue() { public void testDisabledReportsNegativeValue() {
assertThat(mProbe.getCurrentLux()).isLessThan(0f); assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
mProbe.enable(); mProbe.enable();
verify(mSensorManager).registerListener( verify(mSensorManager).registerListener(
@@ -136,7 +144,7 @@ public class ALSProbeTest {
new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f})); new SensorEvent(mLightSensor, 1, 1, new float[]{4.0f}));
mProbe.disable(); mProbe.disable();
assertThat(mProbe.getCurrentLux()).isLessThan(0f); assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
} }
@Test @Test
@@ -150,7 +158,7 @@ public class ALSProbeTest {
verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue())); verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
verifyNoMoreInteractions(mSensorManager); verifyNoMoreInteractions(mSensorManager);
assertThat(mProbe.getCurrentLux()).isLessThan(0f); assertThat(mProbe.getMostRecentLux()).isLessThan(0f);
} }
@Test @Test
@@ -166,7 +174,148 @@ public class ALSProbeTest {
verify(mSensorManager).unregisterListener(any(SensorEventListener.class)); verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
verifyNoMoreInteractions(mSensorManager); 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) { private void moveTimeBy(long millis) {

View File

@@ -121,7 +121,7 @@ public class BiometricLoggerTest {
verify(mSink).authenticate(eq(mOpContext), verify(mSink).authenticate(eq(mOpContext),
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(),
anyLong(), anyInt(), eq(requireConfirmation), anyLong(), anyInt(), eq(requireConfirmation),
eq(targetUserId), anyFloat()); eq(targetUserId), any());
} }
@Test @Test

View File

@@ -28,6 +28,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same; import static org.mockito.Mockito.same;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -215,7 +216,7 @@ public class FingerprintAuthenticationClientTest {
@Test @Test
public void luxProbeWhenAwake() throws RemoteException { public void luxProbeWhenAwake() throws RemoteException {
when(mBiometricContext.isAwake()).thenReturn(false, true, false); when(mBiometricContext.isAwake()).thenReturn(false);
when(mBiometricContext.isAod()).thenReturn(false); when(mBiometricContext.isAod()).thenReturn(false);
final FingerprintAuthenticationClient client = createClient(); final FingerprintAuthenticationClient client = createClient();
client.start(mCallback); client.start(mCallback);
@@ -228,14 +229,37 @@ public class FingerprintAuthenticationClientTest {
verify(mLuxProbe, never()).enable(); verify(mLuxProbe, never()).enable();
reset(mLuxProbe); reset(mLuxProbe);
when(mBiometricContext.isAwake()).thenReturn(true);
mContextInjector.getValue().accept(opContext); mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).enable(); verify(mLuxProbe).enable();
verify(mLuxProbe, never()).disable(); verify(mLuxProbe, never()).disable();
when(mBiometricContext.isAwake()).thenReturn(false);
mContextInjector.getValue().accept(opContext); mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).disable(); 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 @Test
public void luxProbeDisabledOnAod() throws RemoteException { public void luxProbeDisabledOnAod() throws RemoteException {
when(mBiometricContext.isAwake()).thenReturn(false); when(mBiometricContext.isAwake()).thenReturn(false);