Respond to lockout reset requests from the HAL.

Fix: 187393441
Test: manual by rapidly tapping on sensor and waiting for lockout to clear
Test: atest com.android.server.biometrics.sensors.face.aidl.SensorTest
Test: atest com.android.server.biometrics.sensors.fingerprint.aidl.SensorTest
Change-Id: I251b0c8cf25c96a3c959871199d3d6b7990d784e
This commit is contained in:
Joe Bolinger
2021-05-12 11:55:18 -07:00
parent ab0b82aef5
commit c22eee968c
8 changed files with 326 additions and 28 deletions

View File

@@ -149,7 +149,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
prop.supportsDetectInteraction, prop.halControlsPreview,
false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp);
internalProp, lockoutResetDispatcher);
mSensors.put(sensorId, sensor);
Slog.d(getTag(), "Added: " + internalProp);

View File

@@ -80,11 +80,26 @@ public class FaceResetLockoutClient extends HalClientMonitor<ISession> implement
}
void onLockoutCleared() {
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
mLockoutResetDispatcher);
mCallback.onClientFinished(this, true /* success */);
}
/**
* Reset the local lockout state and notify any listeners.
*
* This should only be called when the HAL sends a reset request directly to the
* framework (i.e. time based reset, etc.). When the HAL is responding to a
* resetLockout request from an instance of this client {@link #onLockoutCleared()} should
* be used instead.
*/
static void resetLocalLockoutStateToNone(int sensorId, int userId,
@NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
}
@Override
public int getProtoEnum() {
return BiometricsProto.CM_RESET_LOCKOUT;

View File

@@ -57,6 +57,7 @@ import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.Interruptable;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
@@ -124,10 +125,16 @@ public class Sensor {
private final int mSensorId;
private final int mUserId;
@NonNull
private final LockoutCache mLockoutCache;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull
private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
@NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
@@ -135,6 +142,8 @@ public class Sensor {
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mCallback = callback;
}
@@ -327,13 +336,15 @@ public class Sensor {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FaceResetLockoutClient)) {
Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
+ Utils.getClientName(client));
return;
Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
mLockoutCache, mLockoutResetDispatcher);
} else {
Slog.d(mTag, "onLockoutCleared after resetLockout");
final FaceResetLockoutClient resetLockoutClient =
(FaceResetLockoutClient) client;
resetLockoutClient.onLockoutCleared();
}
final FaceResetLockoutClient resetLockoutClient = (FaceResetLockoutClient) client;
resetLockoutClient.onLockoutCleared();
});
}
@@ -465,7 +476,8 @@ public class Sensor {
}
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties) {
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
mTag = tag;
mProvider = provider;
mContext = context;
@@ -493,7 +505,8 @@ public class Sensor {
final int sensorId = mSensorProperties.sensorId;
final HalSessionCallback resultController = new HalSessionCallback(mContext,
mHandler, mTag, mScheduler, sensorId, newUserId, callback);
mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
lockoutResetDispatcher, callback);
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession) -> {

View File

@@ -159,7 +159,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
prop.sensorLocations[0].sensorLocationY,
prop.sensorLocations[0].sensorRadius);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, gestureAvailabilityDispatcher);
internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
mSensors.put(sensorId, sensor);
Slog.d(getTag(), "Added: " + internalProp);

View File

@@ -80,11 +80,26 @@ class FingerprintResetLockoutClient extends HalClientMonitor<ISession> implement
}
void onLockoutCleared() {
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
mLockoutResetDispatcher);
mCallback.onClientFinished(this, true /* success */);
}
/**
* Reset the local lockout state and notify any listeners.
*
* This should only be called when the HAL sends a reset request directly to the
* framework (i.e. time based reset, etc.). When the HAL is responding to a
* resetLockout request from an instance of this client {@link #onLockoutCleared()} should
* be used instead.
*/
static void resetLocalLockoutStateToNone(int sensorId, int userId,
@NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
}
@Override
public int getProtoEnum() {
return BiometricsProto.CM_RESET_LOCKOUT;

View File

@@ -54,6 +54,7 @@ import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
@@ -116,16 +117,27 @@ class Sensor {
void onHardwareUnavailable();
}
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final String mTag;
@NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull
private final Context mContext;
@NonNull
private final Handler mHandler;
@NonNull
private final String mTag;
@NonNull
private final UserAwareBiometricScheduler mScheduler;
private final int mSensorId;
private final int mUserId;
@NonNull private final Callback mCallback;
@NonNull
private final LockoutCache mLockoutCache;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull
private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
@NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull LockoutCache lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
@@ -133,6 +145,8 @@ class Sensor {
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mCallback = callback;
}
@@ -303,14 +317,15 @@ class Sensor {
mHandler.post(() -> {
final BaseClientMonitor client = mScheduler.getCurrentClient();
if (!(client instanceof FingerprintResetLockoutClient)) {
Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
+ Utils.getClientName(client));
return;
Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
mLockoutCache, mLockoutResetDispatcher);
} else {
Slog.d(mTag, "onLockoutCleared after resetLockout");
final FingerprintResetLockoutClient resetLockoutClient =
(FingerprintResetLockoutClient) client;
resetLockoutClient.onLockoutCleared();
}
final FingerprintResetLockoutClient resetLockoutClient =
(FingerprintResetLockoutClient) client;
resetLockoutClient.onLockoutCleared();
});
}
@@ -415,6 +430,7 @@ class Sensor {
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mTag = tag;
mProvider = provider;
@@ -422,6 +438,7 @@ class Sensor {
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
mLockoutCache = new LockoutCache();
mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher,
() -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -443,7 +460,8 @@ class Sensor {
final int sensorId = mSensorProperties.sensorId;
final HalSessionCallback resultController = new HalSessionCallback(mContext,
mHandler, mTag, mScheduler, sensorId, newUserId, callback);
mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
lockoutResetDispatcher, callback);
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession) -> {
@@ -466,7 +484,6 @@ class Sensor {
resultController, userStartedCallback);
}
});
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors.face.aidl;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.face.ISession;
import android.os.Handler;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@Presubmit
@SmallTest
public class SensorTest {
private static final String TAG = "SensorTest";
private static final int USER_ID = 2;
private static final int SENSOR_ID = 4;
private static final byte[] HAT = new byte[69];
@Mock
private Context mContext;
@Mock
private IBiometricService mBiometricService;
@Mock
private ISession mSession;
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
private Sensor.HalSessionCallback.Callback mHalSessionCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
private Sensor.HalSessionCallback mHalCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG,
null /* gestureAvailabilityDispatcher */,
() -> USER_ID,
mUserSwitchCallback);
mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
TAG, mScheduler, SENSOR_ID,
USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
}
@Test
public void halSessionCallback_respondsToResetLockout() throws Exception {
doAnswer((Answer<Void>) invocationOnMock -> {
mHalCallback.onLockoutCleared();
return null;
}).when(mSession).resetLockout(any());
mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext,
() -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
mLockoutResetDispatcher));
mLooper.dispatchAll();
verifyNotLocked();
}
@Test
public void halSessionCallback_respondsToUnprovokedResetLockout() {
mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
mHalCallback.onLockoutCleared();
mLooper.dispatchAll();
verifyNotLocked();
}
private void verifyNotLocked() {
assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.biometrics.sensors.fingerprint.aidl;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.fingerprint.ISession;
import android.os.Handler;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@Presubmit
@SmallTest
public class SensorTest {
private static final String TAG = "SensorTest";
private static final int USER_ID = 2;
private static final int SENSOR_ID = 4;
private static final byte[] HAT = new byte[69];
@Mock
private Context mContext;
@Mock
private IBiometricService mBiometricService;
@Mock
private ISession mSession;
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
private Sensor.HalSessionCallback.Callback mHalSessionCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
private Sensor.HalSessionCallback mHalCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
mScheduler = new UserAwareBiometricScheduler(TAG,
null /* gestureAvailabilityDispatcher */,
() -> USER_ID,
mUserSwitchCallback);
mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
TAG, mScheduler, SENSOR_ID,
USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
}
@Test
public void halSessionCallback_respondsToResetLockout() throws Exception {
doAnswer((Answer<Void>) invocationOnMock -> {
mHalCallback.onLockoutCleared();
return null;
}).when(mSession).resetLockout(any());
mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext,
() -> mSession, USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache,
mLockoutResetDispatcher));
mLooper.dispatchAll();
verifyNotLocked();
}
@Test
public void halSessionCallback_respondsToUnprovokedResetLockout() {
mLockoutCache.setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_TIMED);
mHalCallback.onLockoutCleared();
mLooper.dispatchAll();
verifyNotLocked();
}
private void verifyNotLocked() {
assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
}
}