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

Commit cf039249 authored by Diya Bera's avatar Diya Bera Committed by Android (Google) Code Review
Browse files

Merge "Distinct callback for each request" into main

parents 08f0ee00 bbd8a6a6
Loading
Loading
Loading
Loading
+321 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.hardware.face;

import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR_BASE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR_BASE;
import static android.hardware.face.FaceManager.getAuthHelpMessage;
import static android.hardware.face.FaceManager.getEnrollHelpMessage;
import static android.hardware.face.FaceManager.getErrorString;

import android.content.Context;
import android.hardware.biometrics.CryptoObject;
import android.hardware.face.FaceManager.AuthenticationCallback;
import android.hardware.face.FaceManager.EnrollmentCallback;
import android.hardware.face.FaceManager.FaceDetectionCallback;
import android.hardware.face.FaceManager.GenerateChallengeCallback;
import android.hardware.face.FaceManager.GetFeatureCallback;
import android.hardware.face.FaceManager.RemovalCallback;
import android.hardware.face.FaceManager.SetFeatureCallback;
import android.util.Slog;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * Encapsulates callbacks and client specific information for each face related request.
 * @hide
 */
public class FaceCallback {
    private static final String TAG = " FaceCallback";

    @Nullable
    private AuthenticationCallback mAuthenticationCallback;
    @Nullable
    private EnrollmentCallback mEnrollmentCallback;
    @Nullable
    private RemovalCallback mRemovalCallback;
    @Nullable
    private GenerateChallengeCallback mGenerateChallengeCallback;
    @Nullable
    private FaceDetectionCallback mFaceDetectionCallback;
    @Nullable
    private SetFeatureCallback mSetFeatureCallback;
    @Nullable
    private GetFeatureCallback mGetFeatureCallback;
    @Nullable
    private Face mRemovalFace;
    @Nullable
    private CryptoObject mCryptoObject;

    /**
     * Construction for face authentication client callback.
     */
    FaceCallback(AuthenticationCallback authenticationCallback, CryptoObject cryptoObject) {
        mAuthenticationCallback = authenticationCallback;
        mCryptoObject = cryptoObject;
    }

    /**
     * Construction for face detect client callback.
     */
    FaceCallback(FaceDetectionCallback faceDetectionCallback) {
        mFaceDetectionCallback = faceDetectionCallback;
    }

    /**
     * Construction for face enroll client callback.
     */
    FaceCallback(EnrollmentCallback enrollmentCallback) {
        mEnrollmentCallback = enrollmentCallback;
    }

    /**
     * Construction for face generate challenge client callback.
     */
    FaceCallback(GenerateChallengeCallback generateChallengeCallback) {
        mGenerateChallengeCallback = generateChallengeCallback;
    }

    /**
     * Construction for face set feature client callback.
     */
    FaceCallback(SetFeatureCallback setFeatureCallback) {
        mSetFeatureCallback = setFeatureCallback;
    }

    /**
     * Construction for face get feature client callback.
     */
    FaceCallback(GetFeatureCallback getFeatureCallback) {
        mGetFeatureCallback = getFeatureCallback;
    }

    /**
     * Construction for single face removal client callback.
     */
    FaceCallback(RemovalCallback removalCallback, Face removalFace) {
        mRemovalCallback = removalCallback;
        mRemovalFace = removalFace;
    }

    /**
     * Construction for all face removal client callback.
     */
    FaceCallback(RemovalCallback removalCallback) {
        mRemovalCallback = removalCallback;
    }

    /**
     * Propagate set feature completed via the callback.
     * @param success if the operation was completed successfully
     * @param feature the feature that was set
     */
    public void sendSetFeatureCompleted(boolean success, int feature) {
        if (mSetFeatureCallback == null) {
            return;
        }
        mSetFeatureCallback.onCompleted(success, feature);
    }

    /**
     * Propagate get feature completed via the callback.
     * @param success if the operation was completed successfully
     * @param features list of features available
     * @param featureState status of the features corresponding to the previous parameter
     */
    public void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
        if (mGetFeatureCallback == null) {
            return;
        }
        mGetFeatureCallback.onCompleted(success, features, featureState);
    }

    /**
     * Propagate challenge generated completed via the callback.
     * @param sensorId id of the corresponding sensor
     * @param userId id of the corresponding sensor
     * @param challenge value of the challenge generated
     */
    public void sendChallengeGenerated(int sensorId, int userId, long challenge) {
        if (mGenerateChallengeCallback == null) {
            return;
        }
        mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
    }

    /**
     * Propagate face detected completed via the callback.
     * @param sensorId id of the corresponding sensor
     * @param userId id of the corresponding user
     * @param isStrongBiometric if the sensor is strong or not
     */
    public void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
        if (mFaceDetectionCallback == null) {
            Slog.e(TAG, "sendFaceDetected, callback null");
            return;
        }
        mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
    }

    /**
     * Propagate remove face completed via the callback.
     * @param face removed identifier
     * @param remaining number of face enrollments remaining
     */
    public void sendRemovedResult(Face face, int remaining) {
        if (mRemovalCallback == null) {
            return;
        }
        mRemovalCallback.onRemovalSucceeded(face, remaining);
    }

    /**
     * Propagate errors via the callback.
     * @param context corresponding context
     * @param errMsgId represents the framework error id
     * @param vendorCode represents the vendor error code
     */
    public void sendErrorResult(Context context, int errMsgId, int vendorCode) {
        // emulate HAL 2.1 behavior and send real errMsgId
        final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
                ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
                    getErrorString(context, errMsgId, vendorCode));
        } else if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
                    getErrorString(context, errMsgId, vendorCode));
        } else if (mRemovalCallback != null) {
            mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
                    getErrorString(context, errMsgId, vendorCode));
        } else if (mFaceDetectionCallback != null) {
            mFaceDetectionCallback.onDetectionError(errMsgId);
            mFaceDetectionCallback = null;
        }
    }

    /**
     * Propagate enroll progress via the callback.
     * @param remaining number of enrollment steps remaining
     */
    public void sendEnrollResult(int remaining) {
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentProgress(remaining);
        }
    }

    /**
     * Propagate authentication succeeded via the callback.
     * @param face matched identifier
     * @param userId id of the corresponding user
     * @param isStrongBiometric if the sensor is strong or not
     */
    public void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
        if (mAuthenticationCallback != null) {
            final FaceManager.AuthenticationResult result = new FaceManager.AuthenticationResult(
                    mCryptoObject, face, userId, isStrongBiometric);
            mAuthenticationCallback.onAuthenticationSucceeded(result);
        }
    }

    /**
     * Propagate authentication failed via the callback.
     */
    public void sendAuthenticatedFailed() {
        if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationFailed();
        }
    }

    /**
     * Propagate acquired result via the callback.
     * @param context corresponding context
     * @param acquireInfo represents the framework acquired id
     * @param vendorCode represents the vendor acquired code
     */
    public void sendAcquiredResult(Context context, int acquireInfo, int vendorCode) {
        if (mAuthenticationCallback != null) {
            final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
                    new FaceDataFrame(acquireInfo, vendorCode));
            sendAuthenticationFrame(context, frame);
        } else if (mEnrollmentCallback != null) {
            final FaceEnrollFrame frame = new FaceEnrollFrame(
                    null /* cell */,
                    FaceEnrollStages.UNKNOWN,
                    new FaceDataFrame(acquireInfo, vendorCode));
            sendEnrollmentFrame(context, frame);
        }
    }

    /**
     * Propagate authentication frame via the callback.
     * @param context corresponding context
     * @param frame authentication frame to be sent
     */
    public void sendAuthenticationFrame(@NonNull Context context,
            @Nullable FaceAuthenticationFrame frame) {
        if (frame == null) {
            Slog.w(TAG, "Received null authentication frame");
        } else if (mAuthenticationCallback != null) {
            // TODO(b/178414967): Send additional frame data to callback
            final int acquireInfo = frame.getData().getAcquiredInfo();
            final int vendorCode = frame.getData().getVendorCode();
            final int helpCode = getHelpCode(acquireInfo, vendorCode);
            final String helpMessage = getAuthHelpMessage(context, acquireInfo, vendorCode);
            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);

            // Ensure that only non-null help messages are sent.
            if (helpMessage != null) {
                mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
            }
        }
    }

    /**
     * Propagate enrollment via the callback.
     * @param context corresponding context
     * @param frame enrollment frame to be sent
     */
    public void sendEnrollmentFrame(Context context, @Nullable FaceEnrollFrame frame) {
        if (frame == null) {
            Slog.w(TAG, "Received null enrollment frame");
        } else if (mEnrollmentCallback != null) {
            final FaceDataFrame data = frame.getData();
            final int acquireInfo = data.getAcquiredInfo();
            final int vendorCode = data.getVendorCode();
            final int helpCode = getHelpCode(acquireInfo, vendorCode);
            final String helpMessage = getEnrollHelpMessage(context, acquireInfo, vendorCode);
            mEnrollmentCallback.onEnrollmentFrame(
                    helpCode,
                    helpMessage,
                    frame.getCell(),
                    frame.getStage(),
                    data.getPan(),
                    data.getTilt(),
                    data.getDistance());
        }
    }

    private static int getHelpCode(int acquireInfo, int vendorCode) {
        return acquireInfo == FACE_ACQUIRED_VENDOR
                ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
                : acquireInfo;
    }
}
+57 −269

File changed.

Preview size limit exceeded, changes collapsed.

+299 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.hardware.fingerprint;

import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR_BASE;
import static android.hardware.fingerprint.FingerprintManager.getAcquiredString;
import static android.hardware.fingerprint.FingerprintManager.getErrorString;

import android.annotation.IntDef;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintManager.CryptoObject;
import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
import android.hardware.fingerprint.FingerprintManager.FingerprintDetectionCallback;
import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback;
import android.hardware.fingerprint.FingerprintManager.RemovalCallback;
import android.util.Slog;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * Encapsulates callbacks and client specific information for each fingerprint related request.
 * @hide
 */
public class FingerprintCallback {
    private static final String TAG = "FingerprintCallback";
    public static final int REMOVE_SINGLE = 1;
    public static final int REMOVE_ALL = 2;
    @IntDef({REMOVE_SINGLE, REMOVE_ALL})
    public @interface RemoveRequest {}
    @Nullable
    private AuthenticationCallback mAuthenticationCallback;
    @Nullable
    private EnrollmentCallback mEnrollmentCallback;
    @Nullable
    private RemovalCallback mRemovalCallback;
    @Nullable
    private GenerateChallengeCallback mGenerateChallengeCallback;
    @Nullable
    private FingerprintDetectionCallback mFingerprintDetectionCallback;
    @Nullable
    private CryptoObject mCryptoObject;
    @Nullable
    private @RemoveRequest int mRemoveRequest;
    @Nullable
    private Fingerprint mRemoveFingerprint;

    /**
     * Construction for fingerprint authentication client callback.
     */
    FingerprintCallback(@NonNull AuthenticationCallback authenticationCallback,
            @Nullable CryptoObject cryptoObject) {
        mAuthenticationCallback = authenticationCallback;
        mCryptoObject = cryptoObject;
    }

    /**
     * Construction for fingerprint detect client callback.
     */
    FingerprintCallback(@NonNull FingerprintDetectionCallback fingerprintDetectionCallback) {
        mFingerprintDetectionCallback = fingerprintDetectionCallback;
    }

    /**
     * Construction for fingerprint enroll client callback.
     */
    FingerprintCallback(@NonNull EnrollmentCallback enrollmentCallback) {
        mEnrollmentCallback = enrollmentCallback;
    }

    /**
     * Construction for fingerprint generate challenge client callback.
     */
    FingerprintCallback(@NonNull GenerateChallengeCallback generateChallengeCallback) {
        mGenerateChallengeCallback = generateChallengeCallback;
    }

    /**
     * Construction for fingerprint removal client callback.
     */
    FingerprintCallback(@NonNull RemovalCallback removalCallback, @RemoveRequest int removeRequest,
            @Nullable Fingerprint removeFingerprint) {
        mRemovalCallback = removalCallback;
        mRemoveRequest = removeRequest;
        mRemoveFingerprint = removeFingerprint;
    }

    /**
     * Propagate enroll progress via the callback.
     * @param remaining number of enrollment steps remaining
     */
    public void sendEnrollResult(int remaining) {
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentProgress(remaining);
        }
    }

    /**
     * Propagate remove face completed via the callback.
     * @param fingerprint removed identifier
     * @param remaining number of face enrollments remaining
     */
    public void sendRemovedResult(@Nullable Fingerprint fingerprint, int remaining) {
        if (mRemovalCallback == null) {
            return;
        }

        if (mRemoveRequest == REMOVE_SINGLE) {
            if (fingerprint == null) {
                Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
                return;
            }

            if (mRemoveFingerprint == null) {
                Slog.e(TAG, "Missing fingerprint");
                return;
            }

            final int fingerId = fingerprint.getBiometricId();
            int reqFingerId = mRemoveFingerprint.getBiometricId();
            if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
                Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
                return;
            }
        }

        mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
    }

    /**
     * Propagate authentication succeeded via the callback.
     * @param fingerprint matched identifier
     * @param userId id of the corresponding user
     * @param isStrongBiometric if the sensor is strong or not
     */
    public void sendAuthenticatedSucceeded(@NonNull Fingerprint fingerprint, int userId,
            boolean isStrongBiometric) {
        if (mAuthenticationCallback == null) {
            Slog.e(TAG, "Authentication succeeded but callback is null.");
            return;
        }

        final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fingerprint,
                userId, isStrongBiometric);
        mAuthenticationCallback.onAuthenticationSucceeded(result);
    }

    /**
     * Propagate authentication failed via the callback.
     */
    public void sendAuthenticatedFailed() {
        if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationFailed();
        }
    }

    /**
     * Propagate acquired result via the callback.
     * @param context corresponding context
     * @param acquireInfo represents the framework acquired id
     * @param vendorCode represents the vendor acquired code
     */
    public void sendAcquiredResult(@NonNull Context context, int acquireInfo, int vendorCode) {
        if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
        }
        if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
            mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
        }
        final String msg = getAcquiredString(context, acquireInfo, vendorCode);
        if (msg == null) {
            return;
        }
        // emulate HAL 2.1 behavior and send real acquiredInfo
        final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
                ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
        } else if (mAuthenticationCallback != null) {
            if (acquireInfo != FINGERPRINT_ACQUIRED_START) {
                mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
            }
        }
    }

    /**
     * Propagate errors via the callback.
     * @param context corresponding context
     * @param errMsgId represents the framework error id
     * @param vendorCode represents the vendor error code
     */
    public void sendErrorResult(@NonNull Context context, int errMsgId, int vendorCode) {
        // emulate HAL 2.1 behavior and send real errMsgId
        final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
                ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
                    getErrorString(context, errMsgId, vendorCode));
        } else if (mAuthenticationCallback != null) {
            mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
                    getErrorString(context, errMsgId, vendorCode));
        } else if (mRemovalCallback != null) {
            mRemovalCallback.onRemovalError(mRemoveFingerprint, clientErrMsgId,
                    getErrorString(context, errMsgId, vendorCode));
        } else if (mFingerprintDetectionCallback != null) {
            mFingerprintDetectionCallback.onDetectionError(errMsgId);
            mFingerprintDetectionCallback = null;
        }
    }

    /**
     * Propagate challenge generated completed via the callback.
     * @param sensorId id of the corresponding sensor
     * @param userId id of the corresponding sensor
     * @param challenge value of the challenge generated
     */
    public void sendChallengeGenerated(long challenge, int sensorId, int userId) {
        if (mGenerateChallengeCallback == null) {
            Slog.e(TAG, "sendChallengeGenerated, callback null");
            return;
        }
        mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
    }

    /**
     * Propagate fingerprint detected completed via the callback.
     * @param sensorId id of the corresponding sensor
     * @param userId id of the corresponding user
     * @param isStrongBiometric if the sensor is strong or not
     */
    public void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
        if (mFingerprintDetectionCallback == null) {
            Slog.e(TAG, "sendFingerprintDetected, callback null");
            return;
        }
        mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
    }

    /**
     * Propagate udfps pointer down via the callback.
     * @param sensorId id of the corresponding sensor
     */
    public void sendUdfpsPointerDown(int sensorId) {
        if (mAuthenticationCallback == null) {
            Slog.e(TAG, "sendUdfpsPointerDown, callback null");
        } else {
            mAuthenticationCallback.onUdfpsPointerDown(sensorId);
        }

        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onUdfpsPointerDown(sensorId);
        }
    }

    /**
     * Propagate udfps pointer up via the callback.
     * @param sensorId id of the corresponding sensor
     */
    public void sendUdfpsPointerUp(int sensorId) {
        if (mAuthenticationCallback == null) {
            Slog.e(TAG, "sendUdfpsPointerUp, callback null");
        } else {
            mAuthenticationCallback.onUdfpsPointerUp(sensorId);
        }
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onUdfpsPointerUp(sensorId);
        }
    }

    /**
     * Propagate udfps overlay shown via the callback.
     */
    public void sendUdfpsOverlayShown() {
        if (mEnrollmentCallback != null) {
            mEnrollmentCallback.onUdfpsOverlayShown();
        }
    }
}
+72 −286

File changed.

Preview size limit exceeded, changes collapsed.

+36 −1
Original line number Diff line number Diff line
@@ -28,7 +28,9 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -97,7 +99,7 @@ public class FaceManagerTest {
        mLooper = new TestLooper();
        mHandler = new Handler(mLooper.getLooper());

        when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
        when(mContext.getMainThreadHandler()).thenReturn(mHandler);
        when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
        when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -210,6 +212,39 @@ public class FaceManagerTest {
        verify(mFaceDetectionCallback).onDetectionError(anyInt());
    }

    @Test
    public void authenticate_onErrorCanceled() throws RemoteException {
        final FaceManager.AuthenticationCallback authenticationCallback1 = mock(
                FaceManager.AuthenticationCallback.class);
        final FaceManager.AuthenticationCallback authenticationCallback2 = mock(
                FaceManager.AuthenticationCallback.class);

        final ArgumentCaptor<IFaceServiceReceiver> faceServiceReceiverArgumentCaptor =
                ArgumentCaptor.forClass(IFaceServiceReceiver.class);

        mFaceManager.authenticate(null, new CancellationSignal(),
                authenticationCallback1, mHandler,
                new FaceAuthenticateOptions.Builder().build());
        mFaceManager.authenticate(null, new CancellationSignal(),
                authenticationCallback2, mHandler,
                new FaceAuthenticateOptions.Builder().build());

        verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
                faceServiceReceiverArgumentCaptor.capture(), any());

        final List<IFaceServiceReceiver> faceServiceReceivers =
                faceServiceReceiverArgumentCaptor.getAllValues();
        faceServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
        mLooper.dispatchAll();

        verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
        verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());

        faceServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
        mLooper.dispatchAll();
        verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
    }

    private void initializeProperties() throws RemoteException {
        verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());

Loading