Loading services/core/java/com/android/server/biometrics/biometrics.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -41,3 +41,13 @@ flag { description: "This flag controls FRR dialog improvement" bug: "380800403" } flag { name: "biometric_scheduler_fix" namespace: "biometrics_framework" description: "This flag fixes a bug where watchdog is ineffective when user switching is required." bug: "420939050" metadata { purpose: PURPOSE_BUGFIX } } services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +5 −1 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.modules.expresslog.Counter; import com.android.server.biometrics.BiometricSchedulerProto; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.Flags; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.io.PrintWriter; Loading Loading @@ -314,8 +315,11 @@ public class BiometricScheduler<T, U> { final int currentUserId = mCurrentUserRetriever.get(); final int nextUserId = mPendingOperations.getFirst().getTargetUserId(); final boolean shouldStartNextOperationIfMarkedCancelling = Flags.biometricSchedulerFix() && mPendingOperations.getFirst().isMarkedCanceling(); if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) { if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation() || shouldStartNextOperationIfMarkedCancelling) { startNextOperationIfIdle(); } else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) { final BaseClientMonitor startClient = Loading services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +48 −1 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; Loading @@ -66,6 +67,7 @@ import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.nano.BiometricSchedulerProto; Loading Loading @@ -801,6 +803,46 @@ public class BiometricSchedulerTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRIC_SCHEDULER_FIX) public void testBiometricQueueHung_watchdogTriggered_noUserSwitch() { final int currentUserId = mCurrentUserId; final int newUserId = 10; final Supplier<Object> lazyDaemon1 = () -> mock(Object.class); final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon1, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER, currentUserId); final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken, lazyDaemon1, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER, newUserId); final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class); mScheduler.scheduleClientMonitor(client1, callback1); mScheduler.scheduleClientMonitor(client2, callback2); waitForIdle(); assertThat(mScheduler.mPendingOperations.size()).isEqualTo(1); assertThat(mScheduler.mCurrentOperation.getClientMonitor()).isEqualTo(client1); verify(callback1).onClientStarted(client1); mScheduler.startWatchdog(); waitForIdle(); mLooper.moveTimeForward(10000); waitForIdle(); // After 10 seconds the HAL has 3 seconds to respond to a cancel mLooper.moveTimeForward(3000); waitForIdle(); assertThat(mScheduler.mPendingOperations.size()).isEqualTo(0); assertThat(mScheduler.mCurrentOperation).isEqualTo(null); assertThat(mCurrentUserId).isEqualTo(currentUserId); verify(callback1).onClientFinished(client1, true); verify(callback2).onClientFinished(client2, true); } @Test public void testTwoInternalCleanupOps_withFirstFavorHalEnrollment() throws Exception { final String owner = "test.owner"; Loading Loading @@ -1182,7 +1224,12 @@ public class BiometricSchedulerTest { TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) { super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG, this(context, token, lazyDaemon, cookie, protoEnum, 0 /* userId */); } TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum, int userId) { super(context, lazyDaemon, token /* token */, null /* listener */, userId, TAG, cookie, TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class), false /* isMandatoryBiometrics */); mProtoEnum = protoEnum; Loading Loading
services/core/java/com/android/server/biometrics/biometrics.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -41,3 +41,13 @@ flag { description: "This flag controls FRR dialog improvement" bug: "380800403" } flag { name: "biometric_scheduler_fix" namespace: "biometrics_framework" description: "This flag fixes a bug where watchdog is ineffective when user switching is required." bug: "420939050" metadata { purpose: PURPOSE_BUGFIX } }
services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +5 −1 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.modules.expresslog.Counter; import com.android.server.biometrics.BiometricSchedulerProto; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.Flags; import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; import java.io.PrintWriter; Loading Loading @@ -314,8 +315,11 @@ public class BiometricScheduler<T, U> { final int currentUserId = mCurrentUserRetriever.get(); final int nextUserId = mPendingOperations.getFirst().getTargetUserId(); final boolean shouldStartNextOperationIfMarkedCancelling = Flags.biometricSchedulerFix() && mPendingOperations.getFirst().isMarkedCanceling(); if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) { if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation() || shouldStartNextOperationIfMarkedCancelling) { startNextOperationIfIdle(); } else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) { final BaseClientMonitor startClient = Loading
services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +48 −1 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; Loading @@ -66,6 +67,7 @@ import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.nano.BiometricSchedulerProto; Loading Loading @@ -801,6 +803,46 @@ public class BiometricSchedulerTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_BIOMETRIC_SCHEDULER_FIX) public void testBiometricQueueHung_watchdogTriggered_noUserSwitch() { final int currentUserId = mCurrentUserId; final int newUserId = 10; final Supplier<Object> lazyDaemon1 = () -> mock(Object.class); final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon1, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER, currentUserId); final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken, lazyDaemon1, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER, newUserId); final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class); mScheduler.scheduleClientMonitor(client1, callback1); mScheduler.scheduleClientMonitor(client2, callback2); waitForIdle(); assertThat(mScheduler.mPendingOperations.size()).isEqualTo(1); assertThat(mScheduler.mCurrentOperation.getClientMonitor()).isEqualTo(client1); verify(callback1).onClientStarted(client1); mScheduler.startWatchdog(); waitForIdle(); mLooper.moveTimeForward(10000); waitForIdle(); // After 10 seconds the HAL has 3 seconds to respond to a cancel mLooper.moveTimeForward(3000); waitForIdle(); assertThat(mScheduler.mPendingOperations.size()).isEqualTo(0); assertThat(mScheduler.mCurrentOperation).isEqualTo(null); assertThat(mCurrentUserId).isEqualTo(currentUserId); verify(callback1).onClientFinished(client1, true); verify(callback2).onClientFinished(client2, true); } @Test public void testTwoInternalCleanupOps_withFirstFavorHalEnrollment() throws Exception { final String owner = "test.owner"; Loading Loading @@ -1182,7 +1224,12 @@ public class BiometricSchedulerTest { TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) { super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG, this(context, token, lazyDaemon, cookie, protoEnum, 0 /* userId */); } TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum, int userId) { super(context, lazyDaemon, token /* token */, null /* listener */, userId, TAG, cookie, TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class), false /* isMandatoryBiometrics */); mProtoEnum = protoEnum; Loading