Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 82b6469a authored by Kevin Chyn's avatar Kevin Chyn
Browse files

Check pending queue for cancelAuthentication

If cancelAuthentication is requested but actual authentication
has not started yet (e.g. a previous non-cancelable operation such
as updateActiveGroup or getAuthenticatorId is still running), look
through the pending queue and mark all matched authentication clients
as STATE_WAITING_IN_QUEUE_CANCELING.

Test: atest BiometricSchedulerTest
Fixes: 178105328
Change-Id: I755359adbdcda7f054135a190426b4a78a7a5125
parent 77bb0b72
Loading
Loading
Loading
Loading
+19 −7
Original line number Diff line number Diff line
@@ -563,14 +563,26 @@ public class BiometricScheduler {
        final boolean isAuthenticating =
                mCurrentOperation.mClientMonitor instanceof AuthenticationConsumer;
        final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
        if (!isAuthenticating || !tokenMatches) {
            Slog.w(getTag(), "Not cancelling authentication"
                    + ", current operation : " + mCurrentOperation
                    + ", tokenMatches: " + tokenMatches);
            return;
        }

        if (isAuthenticating && tokenMatches) {
            Slog.d(getTag(), "Cancelling authentication: " + mCurrentOperation);
            cancelInternal(mCurrentOperation);
        } else if (!isAuthenticating) {
            // Look through the current queue for all authentication clients for the specified
            // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
            // all of them, instead of just the first one, since the API surface currently doesn't
            // allow us to distinguish between multiple authentication requests from the same
            // process. However, this generally does not happen anyway, and would be a class of
            // bugs on its own.
            for (Operation operation : mPendingOperations) {
                if (operation.mClientMonitor instanceof AuthenticationConsumer
                        && operation.mClientMonitor.getToken() == token) {
                    Slog.d(getTag(), "Marking " + operation
                            + " as STATE_WAITING_IN_QUEUE_CANCELING");
                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
                }
            }
        }
    }

    /**
+57 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.IBiometricService;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;

import androidx.annotation.NonNull;
@@ -267,6 +268,39 @@ public class BiometricSchedulerTest {
        assertEquals(0, bsp.recentOperations.length);
    }

    @Test
    public void testCancelPendingAuth() throws RemoteException {
        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);

        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
                mToken, callback);

        // Add a non-cancellable client, then add the auth client
        mScheduler.scheduleClientMonitor(client1);
        mScheduler.scheduleClientMonitor(client2);
        waitForIdle();

        assertEquals(mScheduler.getCurrentClient(), client1);
        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
                mScheduler.mPendingOperations.getFirst().mState);

        // Request cancel before the authentication client has started
        mScheduler.cancelAuthentication(mToken);
        waitForIdle();
        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
                mScheduler.mPendingOperations.getFirst().mState);

        // Finish the blocking client. The authentication client should send ERROR_CANCELED
        client1.getCallback().onClientFinished(client1, true /* success */);
        waitForIdle();
        verify(callback).onError(anyInt(), anyInt(),
                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                eq(0) /* vendorCode */);
        assertNull(mScheduler.getCurrentClient());
    }

    private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
        return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
    }
@@ -293,6 +327,29 @@ public class BiometricSchedulerTest {
        }
    }

    private static class TestAuthenticationClient extends AuthenticationClient<Object> {

        public TestAuthenticationClient(@NonNull Context context,
                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
                @NonNull ClientMonitorCallbackConverter listener) {
            super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
                    false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
                    TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
                    0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
                    false /* isKeyguard */);
        }

        @Override
        protected void stopHalOperation() {

        }

        @Override
        protected void startHalOperation() {

        }
    }

    private static class TestClientMonitor2 extends TestClientMonitor {
        private final int mProtoEnum;