diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index dd3057e721bea..6a7d201d9f253 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -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); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index f551930aee490..1e1b532961dfc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -80,11 +80,26 @@ public class FaceResetLockoutClient extends HalClientMonitor 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; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 724531ebcf42b..0e6a0f72206cc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -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 userStartedCallback = (userIdStarted, newSession) -> { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 20d6ee24d3279..e5fafcd1bd3bd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -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); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index bab950691b080..878ef46d2b2e0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -80,11 +80,26 @@ class FingerprintResetLockoutClient extends HalClientMonitor 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; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index cf915ad78509a..9884a78b2a5d2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -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 userStartedCallback = (userIdStarted, newSession) -> { @@ -466,7 +484,6 @@ class Sensor { resultController, userStartedCallback); } }); - mLockoutCache = new LockoutCache(); mAuthenticatorIds = new HashMap<>(); mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java new file mode 100644 index 0000000000000..b8fbe34a7dcba --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -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) 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)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java new file mode 100644 index 0000000000000..5dfc248898155 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -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) 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)); + } +}