Merge "Move ALS auth logging triggers to wakefullness events." into tm-qpr-dev

This commit is contained in:
Joe Bolinger
2022-09-01 19:27:04 +00:00
committed by Android (Google) Code Review
10 changed files with 189 additions and 69 deletions

View File

@@ -23,5 +23,8 @@ package android.hardware.biometrics;
* @hide
*/
oneway interface IBiometricContextListener {
void onDozeChanged(boolean isDozing);
// Called when doze or awake (screen on) status changes.
// These may be called while the device is still transitioning to the new state
// (i.e. about to become awake or enter doze)
void onDozeChanged(boolean isDozing, boolean isAwake);
}

View File

@@ -19,6 +19,9 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -655,7 +658,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
@Background DelayableExecutor bgExecutor) {
super(context);
mExecution = execution;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUserManager = userManager;
mLockPatternUtils = lockPatternUtils;
mHandler = handler;
@@ -681,11 +683,24 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
return Unit.INSTANCE;
});
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedWakingUp() {
notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_AWAKE);
}
@Override
public void onStartedGoingToSleep() {
notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_GOING_TO_SLEEP);
}
});
mStatusBarStateController = statusBarStateController;
mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
@Override
public void onDozingChanged(boolean isDozing) {
notifyDozeChanged(isDozing);
notifyDozeChanged(isDozing, wakefulnessLifecycle.getWakefulness());
}
});
@@ -763,13 +778,16 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
@Override
public void setBiometicContextListener(IBiometricContextListener listener) {
mBiometricContextListener = listener;
notifyDozeChanged(mStatusBarStateController.isDozing());
notifyDozeChanged(mStatusBarStateController.isDozing(),
mWakefulnessLifecycle.getWakefulness());
}
private void notifyDozeChanged(boolean isDozing) {
private void notifyDozeChanged(boolean isDozing,
@WakefulnessLifecycle.Wakefulness int wakefullness) {
if (mBiometricContextListener != null) {
try {
mBiometricContextListener.onDozeChanged(isDozing);
final boolean isAwake = wakefullness == WAKEFULNESS_AWAKE;
mBiometricContextListener.onDozeChanged(isDozing, isAwake);
} catch (RemoteException e) {
Log.w(TAG, "failed to notify initial doze state");
}

View File

@@ -20,6 +20,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -156,11 +158,13 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Captor
ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
@Captor
ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
@Captor
ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@Captor
private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefullnessObserverCaptor;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -224,7 +228,9 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthenticatorsRegisteredCaptor.capture());
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
verify(mWakefulnessLifecycle).addObserver(mWakefullnessObserverCaptor.capture());
mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
@@ -721,16 +727,37 @@ public class AuthControllerTest extends SysuiTestCase {
}
@Test
public void testForwardsDozeEvent() throws RemoteException {
public void testForwardsDozeEvents() throws RemoteException {
when(mStatusBarStateController.isDozing()).thenReturn(true);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
mAuthController.setBiometicContextListener(mContextListener);
mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);
InOrder order = inOrder(mContextListener);
// invoked twice since the initial state is false
order.verify(mContextListener, times(2)).onDozeChanged(eq(false));
order.verify(mContextListener).onDozeChanged(eq(true));
order.verify(mContextListener, times(2)).onDozeChanged(eq(true), eq(true));
order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
order.verifyNoMoreInteractions();
}
@Test
public void testForwardsWakeEvents() throws RemoteException {
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
mAuthController.setBiometicContextListener(mContextListener);
mWakefullnessObserverCaptor.getValue().onStartedGoingToSleep();
mWakefullnessObserverCaptor.getValue().onFinishedGoingToSleep();
mWakefullnessObserverCaptor.getValue().onStartedWakingUp();
mWakefullnessObserverCaptor.getValue().onFinishedWakingUp();
mWakefullnessObserverCaptor.getValue().onPostFinishedWakingUp();
InOrder order = inOrder(mContextListener);
order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
order.verify(mContextListener).onDozeChanged(eq(false), eq(false));
order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
order.verifyNoMoreInteractions();
}
@Test

View File

@@ -46,6 +46,9 @@ public interface BiometricContext {
/** If the display is in AOD. */
boolean isAod();
/** If the device is awake or is becoming awake. */
boolean isAwake();
/**
* Subscribe to context changes.
*

View File

@@ -43,7 +43,7 @@ import java.util.function.Consumer;
/**
* A default provider for {@link BiometricContext}.
*/
class BiometricContextProvider implements BiometricContext {
final class BiometricContextProvider implements BiometricContext {
private static final String TAG = "BiometricContextProvider";
@@ -76,7 +76,8 @@ class BiometricContextProvider implements BiometricContext {
private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>();
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private boolean mIsDozing = false;
private boolean mIsAod = false;
private boolean mIsAwake = false;
@VisibleForTesting
BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
@@ -85,9 +86,14 @@ class BiometricContextProvider implements BiometricContext {
try {
service.setBiometicContextListener(new IBiometricContextListener.Stub() {
@Override
public void onDozeChanged(boolean isDozing) {
mIsDozing = isDozing;
notifyChanged();
public void onDozeChanged(boolean isDozing, boolean isAwake) {
isDozing = isDozing && isAodEnabled();
final boolean changed = (mIsAod != isDozing) || (mIsAwake != isAwake);
if (changed) {
mIsAod = isDozing;
mIsAwake = isAwake;
notifyChanged();
}
}
private void notifyChanged() {
@@ -97,6 +103,10 @@ class BiometricContextProvider implements BiometricContext {
notifySubscribers();
}
}
private boolean isAodEnabled() {
return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
}
});
service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() {
@Override
@@ -161,7 +171,12 @@ class BiometricContextProvider implements BiometricContext {
@Override
public boolean isAod() {
return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
return mIsAod;
}
@Override
public boolean isAwake() {
return mIsAwake;
}
@Override

View File

@@ -315,21 +315,27 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
private ICancellationSignal doAuthenticate() throws RemoteException {
final AidlSession session = getFreshDaemon();
final OperationContext opContext = getOperationContext();
getBiometricContext().subscribe(opContext, ctx -> {
if (session.hasContextMethods()) {
try {
session.getSession().onContextChanged(ctx);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
}
// TODO(b/243836005): this should come via ctx
final boolean isAwake = getBiometricContext().isAwake();
if (isAwake) {
mALSProbeCallback.getProbe().enable();
} else {
mALSProbeCallback.getProbe().disable();
}
});
if (session.hasContextMethods()) {
final OperationContext opContext = getOperationContext();
final ICancellationSignal cancel =
session.getSession().authenticateWithContext(mOperationId, opContext);
getBiometricContext()
.subscribe(
opContext,
ctx -> {
try {
session.getSession().onContextChanged(ctx);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to notify context changed", e);
}
});
return cancel;
return session.getSession().authenticateWithContext(mOperationId, opContext);
} else {
return session.getSession().authenticate(mOperationId);
}
@@ -360,7 +366,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
try {
mIsPointerDown = true;
mState = STATE_STARTED;
mALSProbeCallback.getProbe().enable();
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
@@ -389,7 +394,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
try {
mIsPointerDown = false;
mState = STATE_STARTED_PAUSED_ATTEMPTED;
mALSProbeCallback.getProbe().disable();
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {

View File

@@ -94,7 +94,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -216,7 +216,6 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
public void onPointerDown(int x, int y, float minor, float major) {
try {
mIsPointerDown = true;
mALSProbeCallback.getProbe().enable();
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
@@ -240,7 +239,6 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
public void onPointerUp() {
try {
mIsPointerDown = false;
mALSProbeCallback.getProbe().disable();
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {

View File

@@ -40,8 +40,6 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBarService;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -89,33 +87,64 @@ public class BiometricContextProviderTest {
@Test
public void testIsAod() throws RemoteException {
mListener.onDozeChanged(true);
mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
assertThat(mProvider.isAod()).isTrue();
mListener.onDozeChanged(false);
mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
assertThat(mProvider.isAod()).isFalse();
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false);
mListener.onDozeChanged(true);
mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
assertThat(mProvider.isAod()).isFalse();
mListener.onDozeChanged(false);
mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
assertThat(mProvider.isAod()).isFalse();
}
@Test
public void testIsAwake() throws RemoteException {
mListener.onDozeChanged(false /* isDozing */, true /* isAwake */);
assertThat(mProvider.isAwake()).isTrue();
mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
assertThat(mProvider.isAwake()).isFalse();
mListener.onDozeChanged(true /* isDozing */, true /* isAwake */);
assertThat(mProvider.isAwake()).isTrue();
mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
assertThat(mProvider.isAwake()).isFalse();
}
@Test
public void testSubscribesToAod() throws RemoteException {
final List<Boolean> expected = ImmutableList.of(true, false, true, true, false);
final List<Boolean> actual = new ArrayList<>();
mProvider.subscribe(mOpContext, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext);
assertThat(mProvider.isAod()).isEqualTo(ctx.isAod);
assertThat(mProvider.isAwake()).isFalse();
actual.add(ctx.isAod);
});
for (boolean v : expected) {
mListener.onDozeChanged(v);
for (boolean v : List.of(true, false, true, true, false, false)) {
mListener.onDozeChanged(v /* isDozing */, false /* isAwake */);
}
assertThat(actual).containsExactlyElementsIn(expected).inOrder();
assertThat(actual).containsExactly(true, false, true, false).inOrder();
}
@Test
public void testSubscribesToAwake() throws RemoteException {
final List<Boolean> actual = new ArrayList<>();
mProvider.subscribe(mOpContext, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext);
assertThat(ctx.isAod).isFalse();
assertThat(mProvider.isAod()).isFalse();
actual.add(mProvider.isAwake());
});
for (boolean v : List.of(true, false, true, true, false, false)) {
mListener.onDozeChanged(false /* isDozing */, v /* isAwake */);
}
assertThat(actual).containsExactly(true, false, true, false).inOrder();
}
@Test
@@ -124,13 +153,13 @@ public class BiometricContextProviderTest {
mProvider.subscribe(mOpContext, emptyConsumer);
mProvider.unsubscribe(mOpContext);
mListener.onDozeChanged(true);
mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
mProvider.subscribe(mOpContext, nonEmptyConsumer);
mListener.onDozeChanged(false);
mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
mProvider.unsubscribe(mOpContext);
mListener.onDozeChanged(true);
mListener.onDozeChanged(true /* isDozing */, false /* isAwake */);
verify(emptyConsumer, never()).accept(any());
verify(nonEmptyConsumer).accept(same(mOpContext));
@@ -171,7 +200,7 @@ public class BiometricContextProviderTest {
@Test
public void testUpdate() throws RemoteException {
mListener.onDozeChanged(false);
mListener.onDozeChanged(false /* isDozing */, false /* isAwake */);
OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */);
// default state when nothing has been set
@@ -186,7 +215,7 @@ public class BiometricContextProviderTest {
final int id = 40 + type;
final boolean aod = (type & 1) == 0;
mListener.onDozeChanged(aod);
mListener.onDozeChanged(aod /* isDozing */, false /* isAwake */);
mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id));
context = mProvider.updateContext(mOpContext, false /* crypto */);
assertThat(context).isSameInstanceAs(mOpContext);

View File

@@ -26,8 +26,8 @@ import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
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;
@@ -96,6 +96,7 @@ public class FingerprintAuthenticationClientTest {
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Mock
private ISession mHal;
@Mock
@@ -213,21 +214,41 @@ public class FingerprintAuthenticationClientTest {
}
@Test
public void luxProbeWhenFingerDown() throws RemoteException {
public void luxProbeWhenAwake() throws RemoteException {
when(mBiometricContext.isAwake()).thenReturn(false, true, false);
when(mBiometricContext.isAod()).thenReturn(false);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
verify(mLuxProbe).enable();
verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
OperationContext opContext = mOperationContextCaptor.getValue();
verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
client.onAcquired(2, 0);
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe, never()).enable();
reset(mLuxProbe);
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).enable();
verify(mLuxProbe, never()).disable();
client.onPointerUp();
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe).disable();
}
client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
verify(mLuxProbe, times(2)).enable();
@Test
public void luxProbeDisabledOnAod() throws RemoteException {
when(mBiometricContext.isAwake()).thenReturn(false);
when(mBiometricContext.isAod()).thenReturn(true);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture());
OperationContext opContext = mOperationContextCaptor.getValue();
verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture());
mContextInjector.getValue().accept(opContext);
verify(mLuxProbe, never()).enable();
}
@Test

View File

@@ -64,6 +64,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.function.Consumer;
@Presubmit
@@ -196,21 +197,22 @@ public class FingerprintEnrollClientTest {
}
@Test
public void luxProbeWhenFingerDown() throws RemoteException {
public void luxProbeWhenStarted() throws RemoteException {
final FingerprintEnrollClient client = createClient();
client.start(mCallback);
client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
verify(mLuxProbe).enable();
client.onAcquired(2, 0);
verify(mLuxProbe, never()).disable();
client.onPointerUp();
verify(mLuxProbe).disable();
client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR);
verify(mLuxProbe, times(2)).enable();
verify(mLuxProbe, never()).disable();
verify(mLuxProbe, never()).destroy();
client.onEnrollResult(new Fingerprint("f", 30 /* fingerId */, 14 /* deviceId */),
0 /* remaining */);
verify(mLuxProbe).destroy();
}
@Test