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

Commit e44a86b2 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

2/n: Keep track of IBiometricsFace@1.0 challenge interruptions

IBiometricsFace@1.0 only supports a single in-flight challenge.
Currently, challenge owners paths are designed to never conflict
lifecycle-wise. However, to decouple resetLockout (which relies
on challenge) from the critical user unlock path, we need to
keep track of challenge interruptions, and notify clients when
an interruption starts/finishes.

This change only adds the infrastructure for tracking challenge
interruptions. A following change will make use of this.

Bug: 145978626
Test: Builds
Change-Id: I708312cafad29614e3708b5c606ae8a90704ac66
parent 86943d14
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
@@ -72,6 +72,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
    private static final int MSG_SET_FEATURE_COMPLETED = 107;
    private static final int MSG_CHALLENGE_GENERATED = 108;
    private static final int MSG_FACE_DETECTED = 109;
    private static final int MSG_CHALLENGE_INTERRUPTED = 110;
    private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;

    private final IFaceService mService;
    private final Context mContext;
@@ -150,6 +152,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
                mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, challenge).sendToTarget();
            }
        }

        @Override
        public void onChallengeInterrupted(int sensorId) {
            mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPTED, sensorId).sendToTarget();
        }

        @Override
        public void onChallengeInterruptFinished(int sensorId) {
            mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
        }
    };

    /**
@@ -1071,10 +1083,25 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
    }

    /**
     * Callback structure provided to {@link #generateChallenge(GenerateChallengeCallback)}.
     * @hide
     */
    public interface GenerateChallengeCallback {
        /**
         * Invoked when a challenge has been generated.
         */
        void onGenerateChallengeResult(long challenge);

        /**
         * Invoked if the challenge has not been revoked and a subsequent caller/owner invokes
         * {@link #generateChallenge(GenerateChallengeCallback)}, but
         */
        default void onChallengeInterrupted(int sensorId) {}

        /**
         * Invoked when the interrupting client has finished (e.g. revoked its challenge).
         */
        default void onChallengeInterruptFinished(int sensorId) {}
    }

    private abstract static class InternalGenerateChallengeCallback
@@ -1157,6 +1184,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
                    sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
                            (boolean) msg.obj /* isStrongBiometric */);
                    break;
                case MSG_CHALLENGE_INTERRUPTED:
                    sendChallengeInterrupted((int) msg.obj /* sensorId */);
                    break;
                case MSG_CHALLENGE_INTERRUPT_FINISHED:
                    sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
                    break;
                default:
                    Slog.w(TAG, "Unknown message: " + msg.what);
            }
@@ -1193,6 +1226,22 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
        mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
    }

    private void sendChallengeInterrupted(int sensorId) {
        if (mGenerateChallengeCallback == null) {
            Slog.e(TAG, "sendChallengeInterrupted, callback null");
            return;
        }
        mGenerateChallengeCallback.onChallengeInterrupted(sensorId);
    }

    private void sendChallengeInterruptFinished(int sensorId) {
        if (mGenerateChallengeCallback == null) {
            Slog.e(TAG, "sendChallengeInterruptFinished, callback null");
            return;
        }
        mGenerateChallengeCallback.onChallengeInterruptFinished(sensorId);
    }

    private void sendRemovedResult(Face face, int remaining) {
        if (mRemovalCallback == null) {
            return;
+2 −0
Original line number Diff line number Diff line
@@ -32,4 +32,6 @@ oneway interface IFaceServiceReceiver {
    void onFeatureSet(boolean success, int feature);
    void onFeatureGet(boolean success, int feature, boolean value);
    void onChallengeGenerated(long challenge);
    void onChallengeInterrupted(int sensorId);
    void onChallengeInterruptFinished(int sensorId);
}
+13 −2
Original line number Diff line number Diff line
@@ -142,10 +142,21 @@ public final class ClientMonitorCallbackConverter {
        }
    }

    public void onFeatureGet(boolean success, int feature, boolean value)
            throws RemoteException {
    public void onFeatureGet(boolean success, int feature, boolean value) throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onFeatureGet(success, feature, value);
        }
    }

    public void onChallengeInterrupted(int sensorId) throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onChallengeInterrupted(sensorId);
        }
    }

    public void onChallengeInterruptFinished(int sensorId) throws RemoteException {
        if (mFaceServiceReceiver != null) {
            mFaceServiceReceiver.onChallengeInterruptFinished(sensorId);
        }
    }
}
+58 −3
Original line number Diff line number Diff line
@@ -98,6 +98,11 @@ class Face10 implements IHwBinder.DeathRecipient {

    @Nullable private IBiometricsFace mDaemon;
    private int mCurrentUserId = UserHandle.USER_NULL;
    // If a challenge is generated, keep track of its owner. Since IBiometricsFace@1.0 only
    // supports a single in-flight challenge, we must notify the interrupted owner that its
    // challenge is no longer valid. The interrupted owner will be notified when the interrupter
    // has finished.
    @Nullable private FaceGenerateChallengeClient mCurrentChallengeOwner;

    private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
        @Override
@@ -462,18 +467,68 @@ class Face10 implements IHwBinder.DeathRecipient {
    void scheduleGenerateChallenge(@NonNull IBinder token, @NonNull IFaceServiceReceiver receiver,
            @NonNull String opPackageName) {
        mHandler.post(() -> {
            if (mCurrentChallengeOwner != null) {
                Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
                        + ", interrupted by: " + opPackageName);
                try {
                    mCurrentChallengeOwner.getListener().onChallengeInterrupted(mSensorId);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Unable to notify challenge interrupted", e);
                }
            }

            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), opPackageName,
                    mSensorId);
            mScheduler.scheduleClientMonitor(client);
                    mSensorId, mCurrentChallengeOwner);
            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
                @Override
                public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) {
                    if (client != clientMonitor) {
                        Slog.e(TAG, "scheduleGenerateChallenge, mismatched client."
                                + " Expecting: " + client + ", received: " + clientMonitor);
                        return;
                    }
                    Slog.d(TAG, "Current challenge owner: " + client);
                    mCurrentChallengeOwner = client;
                }
            });
        });
    }

    void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
        mHandler.post(() -> {
            if (!mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
                Slog.e(TAG, "Package: " + owner + " attempting to revoke challenge owned by: "
                        + mCurrentChallengeOwner.getOwnerString());
                return;
            }

            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                    mLazyDaemon, token, owner, mSensorId);
            mScheduler.scheduleClientMonitor(client);
            mScheduler.scheduleClientMonitor(client, new ClientMonitor.Callback() {
                @Override
                public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
                        boolean success) {
                    if (client != clientMonitor) {
                        Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
                                + "Expecting: " + client + ", received: " + clientMonitor);
                        return;
                    }

                    final FaceGenerateChallengeClient previousChallengeOwner =
                            mCurrentChallengeOwner.getInterruptedClient();
                    mCurrentChallengeOwner = null;
                    Slog.d(TAG, "Previous challenge owner: " + previousChallengeOwner);
                    if (previousChallengeOwner != null) {
                        try {
                            previousChallengeOwner.getListener()
                                    .onChallengeInterruptFinished(mSensorId);
                        } catch (RemoteException e) {
                            Slog.e(TAG, "Unable to notify interrupt finished", e);
                        }
                    }
                }
            });
        });
    }

+15 −1
Original line number Diff line number Diff line
@@ -17,12 +17,14 @@
package com.android.server.biometrics.sensors.face;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;

import com.android.server.biometrics.sensors.ClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.GenerateChallengeClient;

@@ -36,10 +38,22 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet
    private static final String TAG = "FaceGenerateChallengeClient";
    private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes

    // If `this` FaceGenerateChallengeClient was invoked while an existing in-flight challenge
    // was not revoked yet, store a reference to the interrupted client here. Notify the interrupted
    // client when `this` challenge is revoked.
    @Nullable private final FaceGenerateChallengeClient mInterruptedClient;

    FaceGenerateChallengeClient(@NonNull Context context,
            @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
            @NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId) {
            @NonNull ClientMonitorCallbackConverter listener, @NonNull String owner, int sensorId,
            @Nullable FaceGenerateChallengeClient interruptedClient) {
        super(context, lazyDaemon, token, listener, owner, sensorId);
        mInterruptedClient = interruptedClient;
    }

    @Nullable
    public FaceGenerateChallengeClient getInterruptedClient() {
        return mInterruptedClient;
    }

    @Override