/*
 * Copyright (C) 2020 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;

import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintServiceReceiver;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * A service to manage multiple clients that want to access the fingerprint HAL API.
 * The service is responsible for maintaining a list of clients and dispatching all
 * fingerprint-related events.
 */
public class FingerprintService extends SystemService {

    protected static final String TAG = "FingerprintService";

    private final Object mLock = new Object();
    private final AppOpsManager mAppOps;
    private final LockoutResetDispatcher mLockoutResetDispatcher;
    private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
    private final LockPatternUtils mLockPatternUtils;
    private final FingerprintServiceWrapper mServiceWrapper;
    @NonNull private final List<ServiceProvider> mServiceProviders;
    @NonNull private final BiometricStateCallback mBiometricStateCallback;
    @NonNull private final Handler mHandler;

    @GuardedBy("mLock")
    @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
            mAuthenticatorsRegisteredCallbacks;

    @GuardedBy("mLock")
    @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps;

    /**
     * Registers BiometricStateListener in list stored by FingerprintService
     * @param listener new BiometricStateListener being added
     */
    public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
        mBiometricStateCallback.registerBiometricStateListener(listener);
        broadcastCurrentEnrollmentState(listener);
    }

    /**
     * @param listener if non-null, notifies only this listener. if null, notifies all listeners
     *                 in {@link BiometricStateCallback}. This is slightly ugly, but reduces
     *                 redundant code.
     */
    private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) {
        final UserManager um = UserManager.get(getContext());
        synchronized (mLock) {
            // Update the new listener with current state of all sensors
            for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
                final ServiceProvider provider = getProviderForSensor(prop.sensorId);
                for (UserInfo userInfo : um.getAliveUsers()) {
                    final boolean enrolled = !provider
                            .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();

                    // Defer this work and allow the loop to release the lock sooner
                    mHandler.post(() -> {
                        if (listener != null) {
                            mBiometricStateCallback.notifyEnrollmentStateChanged(
                                    listener, userInfo.id, prop.sensorId, enrolled);
                        } else {
                            mBiometricStateCallback.notifyAllEnrollmentStateChanged(
                                    userInfo.id, prop.sensorId, enrolled);
                        }
                    });
                }
            }
        }
    }

    /**
     * Receives the incoming binder calls from FingerprintManager.
     */
    private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
        @Override
        public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                @NonNull String opPackageName) {
            Utils.checkPermission(getContext(), TEST_BIOMETRIC);

            final ServiceProvider provider = getProviderForSensor(sensorId);

            if (provider == null) {
                Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
                return null;
            }

            return provider.createTestSession(sensorId, callback, opPackageName);
        }

        @Override
        public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ProtoOutputStream proto = new ProtoOutputStream();
            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider != null) {
                provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
            }
            proto.flush();
            return proto.getBytes();
        }

        @Override // Binder call
        public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(
                @NonNull String opPackageName) {
            if (getContext().checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
                    != PackageManager.PERMISSION_GRANTED) {
                Utils.checkPermission(getContext(), TEST_BIOMETRIC);
            }

            return FingerprintService.this.getSensorProperties();
        }

        @Override
        public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
                @NonNull String opPackageName) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
                        + ", caller: " + opPackageName);
                return null;
            }
            return provider.getSensorProperties(sensorId);
        }

        @Override // Binder call
        public void generateChallenge(IBinder token, int sensorId, int userId,
                IFingerprintServiceReceiver receiver, String opPackageName) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
                return;
            }

            provider.scheduleGenerateChallenge(sensorId, userId, token, receiver, opPackageName);
        }

        @Override // Binder call
        public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
                long challenge) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
                return;
            }

            provider.scheduleRevokeChallenge(sensorId, userId, token, opPackageName,
                    challenge);
        }

        @Override // Binder call
        public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
                final int userId, final IFingerprintServiceReceiver receiver,
                final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for enroll");
                return -1;
            }

            return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
                    receiver, opPackageName, enrollReason);
        }

        @Override // Binder call
        public void cancelEnrollment(final IBinder token, long requestId) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for cancelEnrollment");
                return;
            }

            provider.second.cancelEnrollment(provider.first, token, requestId);
        }

        @SuppressWarnings("deprecation")
        @Override // Binder call
        public long authenticate(
                final IBinder token,
                final long operationId,
                final int sensorId,
                final int userId,
                final IFingerprintServiceReceiver receiver,
                final String opPackageName,
                final String attributionTag,
                boolean ignoreEnrollmentState) {
            final int callingUid = Binder.getCallingUid();
            final int callingPid = Binder.getCallingPid();
            final int callingUserId = UserHandle.getCallingUserId();

            if (!canUseFingerprint(
                    opPackageName,
                    attributionTag,
                    true /* requireForeground */,
                    callingUid,
                    callingPid,
                    callingUserId)) {
                Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
                return -1;
            }

            // Keyguard check must be done on the caller's binder identity, since it also checks
            // permission.
            final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);

            // Clear calling identity when checking LockPatternUtils for StrongAuth flags.
            final long identity1 = Binder.clearCallingIdentity();
            try {
                if (isKeyguard && Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
                    // If this happens, something in KeyguardUpdateMonitor is wrong.
                    // SafetyNet for b/79776455
                    EventLog.writeEvent(0x534e4554, "79776455");
                    Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
                    return -1;
                }
            } finally {
                Binder.restoreCallingIdentity(identity1);
            }

            final boolean restricted = getContext().checkCallingPermission(MANAGE_FINGERPRINT)
                    != PackageManager.PERMISSION_GRANTED;
            final int statsClient = isKeyguard ? BiometricsProtoEnums.CLIENT_KEYGUARD
                    : BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;

            final Pair<Integer, ServiceProvider> provider;
            if (sensorId == FingerprintManager.SENSOR_ID_ANY) {
                provider = getSingleProvider();
            } else {
                Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
                provider = new Pair<>(sensorId, getProviderForSensor(sensorId));
            }
            if (provider == null) {
                Slog.w(TAG, "Null provider for authenticate");
                return -1;
            }

            final FingerprintSensorPropertiesInternal sensorProps =
                    provider.second.getSensorProperties(sensorId);
            if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
                    && sensorProps != null && (sensorProps.isAnyUdfpsType()
                    || sensorProps.isAnySidefpsType())) {
                try {
                    return authenticateWithPrompt(operationId, sensorProps, callingUid,
                            callingUserId, receiver, opPackageName, ignoreEnrollmentState);
                } catch (PackageManager.NameNotFoundException e) {
                    Slog.e(TAG, "Invalid package", e);
                    return -1;
                }
            }
            return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
                    restricted, statsClient, isKeyguard);
        }

        private long authenticateWithPrompt(
                final long operationId,
                @NonNull final FingerprintSensorPropertiesInternal props,
                final int uId,
                final int userId,
                final IFingerprintServiceReceiver receiver,
                final String opPackageName,
                boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {

            final Context context = getUiContext();
            final Context promptContext = context.createPackageContextAsUser(
                    opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId));
            final Executor executor = context.getMainExecutor();

            final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(promptContext)
                    .setTitle(context.getString(R.string.biometric_dialog_default_title))
                    .setSubtitle(context.getString(R.string.fingerprint_dialog_default_subtitle))
                    .setNegativeButton(
                            context.getString(R.string.cancel),
                            executor,
                            (dialog, which) -> {
                                try {
                                    receiver.onError(
                                            FINGERPRINT_ERROR_USER_CANCELED, 0 /* vendorCode */);
                                } catch (RemoteException e) {
                                    Slog.e(TAG, "Remote exception in negative button onClick()", e);
                                }
                            })
                    .setIsForLegacyFingerprintManager(props.sensorId)
                    .setIgnoreEnrollmentState(ignoreEnrollmentState)
                    .build();

            final BiometricPrompt.AuthenticationCallback promptCallback =
                    new BiometricPrompt.AuthenticationCallback() {
                        @Override
                        public void onAuthenticationError(int errorCode, CharSequence errString) {
                            try {
                                if (FingerprintUtils.isKnownErrorCode(errorCode)) {
                                    receiver.onError(errorCode, 0 /* vendorCode */);
                                } else {
                                    receiver.onError(FINGERPRINT_ERROR_VENDOR, errorCode);
                                }
                            } catch (RemoteException e) {
                                Slog.e(TAG, "Remote exception in onAuthenticationError()", e);
                            }
                        }

                        @Override
                        public void onAuthenticationSucceeded(
                                BiometricPrompt.AuthenticationResult result) {
                            final Fingerprint fingerprint = new Fingerprint("", 0, 0L);
                            final boolean isStrong = props.sensorStrength == STRENGTH_STRONG;
                            try {
                                receiver.onAuthenticationSucceeded(fingerprint, userId, isStrong);
                            } catch (RemoteException e) {
                                Slog.e(TAG, "Remote exception in onAuthenticationSucceeded()", e);
                            }
                        }

                        @Override
                        public void onAuthenticationFailed() {
                            try {
                                receiver.onAuthenticationFailed();
                            } catch (RemoteException e) {
                                Slog.e(TAG, "Remote exception in onAuthenticationFailed()", e);
                            }
                        }

                        @Override
                        public void onAuthenticationAcquired(int acquireInfo) {
                            try {
                                if (FingerprintUtils.isKnownAcquiredCode(acquireInfo)) {
                                    receiver.onAcquired(acquireInfo, 0 /* vendorCode */);
                                } else {
                                    receiver.onAcquired(FINGERPRINT_ACQUIRED_VENDOR, acquireInfo);
                                }
                            } catch (RemoteException e) {
                                Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
                            }
                        }
                    };

            return biometricPrompt.authenticateForOperation(
                    new CancellationSignal(), executor, promptCallback, operationId);
        }

        @Override
        public long detectFingerprint(final IBinder token, final int userId,
                final IFingerprintServiceReceiver receiver, final String opPackageName) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
            if (!Utils.isKeyguard(getContext(), opPackageName)) {
                Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
                return -1;
            }

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for detectFingerprint");
                return -1;
            }

            return provider.second.scheduleFingerDetect(provider.first, token, userId,
                    new ClientMonitorCallbackConverter(receiver), opPackageName,
                    BiometricsProtoEnums.CLIENT_KEYGUARD);
        }

        @Override // Binder call
        public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
                int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
                long requestId, int cookie, boolean allowBackgroundAuthentication) {
            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for prepareForAuthentication");
                return;
            }

            final boolean restricted = true; // BiometricPrompt is always restricted
            provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
                    new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
                    restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
                    allowBackgroundAuthentication);
        }

        @Override // Binder call
        public void startPreparedClient(int sensorId, int cookie) {
            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for startPreparedClient");
                return;
            }

            provider.startPreparedClient(sensorId, cookie);
        }

        @Override // Binder call
        public void cancelAuthentication(
                final IBinder token,
                final String opPackageName,
                final String attributionTag,
                long requestId) {
            final int callingUid = Binder.getCallingUid();
            final int callingPid = Binder.getCallingPid();
            final int callingUserId = UserHandle.getCallingUserId();

            if (!canUseFingerprint(
                    opPackageName,
                    attributionTag,
                    true /* requireForeground */,
                    callingUid,
                    callingPid,
                    callingUserId)) {
                Slog.w(TAG, "cancelAuthentication rejecting package: " + opPackageName);
                return;
            }

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for cancelAuthentication");
                return;
            }

            provider.second.cancelAuthentication(provider.first, token, requestId);
        }

        @Override // Binder call
        public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
                final long requestId) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
            if (!Utils.isKeyguard(getContext(), opPackageName)) {
                Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
                        + opPackageName);
                return;
            }

            // For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
            // cancelling authentication.
            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for cancelFingerprintDetect");
                return;
            }

            provider.second.cancelAuthentication(provider.first, token, requestId);
        }

        @Override // Binder call
        public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
                final String opPackageName, final long requestId) {

            Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);

            Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
                return;
            }

            provider.cancelAuthentication(sensorId, token, requestId);
        }

        @Override // Binder call
        public void remove(final IBinder token, final int fingerId, final int userId,
                final IFingerprintServiceReceiver receiver, final String opPackageName) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for remove");
                return;
            }
            provider.second.scheduleRemove(provider.first, token, receiver, fingerId, userId,
                    opPackageName);
        }

        @Override // Binder call
        public void removeAll(final IBinder token, final int userId,
                final IFingerprintServiceReceiver receiver, final String opPackageName) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final FingerprintServiceReceiver internalReceiver = new FingerprintServiceReceiver() {
                int sensorsFinishedRemoving = 0;
                final int numSensors = getSensorPropertiesInternal(
                        getContext().getOpPackageName()).size();
                @Override
                public void onRemoved(Fingerprint fp, int remaining) throws RemoteException {
                    if (remaining == 0) {
                        sensorsFinishedRemoving++;
                        Slog.d(TAG, "sensorsFinishedRemoving: " + sensorsFinishedRemoving
                                + ", numSensors: " + numSensors);
                        if (sensorsFinishedRemoving == numSensors) {
                            receiver.onRemoved(null, 0 /* remaining */);
                        }
                    }
                }
            };

            // This effectively iterates through all sensors, but has to do so by finding all
            // sensors under each provider.
            for (ServiceProvider provider : mServiceProviders) {
                List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
                for (FingerprintSensorPropertiesInternal prop : props) {
                    provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId,
                            opPackageName);
                }
            }
        }

        @Override // Binder call
        public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
                final String opPackageName) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
            mLockoutResetDispatcher.addCallback(callback, opPackageName);
        }

        @Override // Binder call
        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                return;
            }

            final long ident = Binder.clearCallingIdentity();
            try {
                if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
                    final ProtoOutputStream proto = new ProtoOutputStream(fd);
                    for (ServiceProvider provider : mServiceProviders) {
                        for (FingerprintSensorPropertiesInternal props
                                : provider.getSensorProperties()) {
                            provider.dumpProtoState(props.sensorId, proto, false);
                        }
                    }
                    proto.flush();
                } else if (args.length > 0 && "--proto".equals(args[0])) {
                    for (ServiceProvider provider : mServiceProviders) {
                        for (FingerprintSensorPropertiesInternal props
                                : provider.getSensorProperties()) {
                            provider.dumpProtoMetrics(props.sensorId, fd);
                        }
                    }
                } else {
                    for (ServiceProvider provider : mServiceProviders) {
                        for (FingerprintSensorPropertiesInternal props
                                : provider.getSensorProperties()) {
                            pw.println("Dumping for sensorId: " + props.sensorId
                                    + ", provider: " + provider.getClass().getSimpleName());
                            pw.println("Fps state: "
                                    + mBiometricStateCallback.getBiometricState());
                            provider.dumpInternal(props.sensorId, pw);
                            pw.println();
                        }
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        @Override // Binder call
        public boolean isHardwareDetectedDeprecated(String opPackageName, String attributionTag) {
            if (!canUseFingerprint(
                    opPackageName,
                    attributionTag,
                    false /* foregroundOnly */,
                    Binder.getCallingUid(),
                    Binder.getCallingPid(),
                    UserHandle.getCallingUserId())) {
                return false;
            }

            final long token = Binder.clearCallingIdentity();
            try {
                final Pair<Integer, ServiceProvider> provider = getSingleProvider();
                if (provider == null) {
                    Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: "
                            + opPackageName);
                    return false;
                }
                return provider.second.isHardwareDetected(provider.first);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override // Binder call
        public boolean isHardwareDetected(int sensorId, String opPackageName) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
                return false;
            }

            return provider.isHardwareDetected(sensorId);
        }

        @Override // Binder call
        public void rename(final int fingerId, final int userId, final String name) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
            if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
                return;
            }

            final Pair<Integer, ServiceProvider> provider = getSingleProvider();
            if (provider == null) {
                Slog.w(TAG, "Null provider for rename");
                return;
            }

            provider.second.rename(provider.first, fingerId, userId, name);
        }

        @Override // Binder call
        public List<Fingerprint> getEnrolledFingerprints(
                int userId, String opPackageName, String attributionTag) {
            if (!canUseFingerprint(
                    opPackageName,
                    attributionTag,
                    false /* foregroundOnly */,
                    Binder.getCallingUid(),
                    Binder.getCallingPid(),
                    UserHandle.getCallingUserId())) {
                return Collections.emptyList();
            }

            if (userId != UserHandle.getCallingUserId()) {
                Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
            }

            return FingerprintService.this.getEnrolledFingerprintsDeprecated(userId, opPackageName);
        }

        @Override // Binder call
        public boolean hasEnrolledFingerprintsDeprecated(
                int userId, String opPackageName, String attributionTag) {
            if (!canUseFingerprint(
                    opPackageName,
                    attributionTag,
                    false /* foregroundOnly */,
                    Binder.getCallingUid(),
                    Binder.getCallingPid(),
                    UserHandle.getCallingUserId())) {
                return false;
            }

            if (userId != UserHandle.getCallingUserId()) {
                Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
            }
            return !FingerprintService.this.getEnrolledFingerprintsDeprecated(userId, opPackageName)
                    .isEmpty();
        }

        public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
                return false;
            }

            return provider.getEnrolledFingerprints(sensorId, userId).size() > 0;
        }

        @Override // Binder call
        public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for getLockoutModeForUser");
                return LockoutTracker.LOCKOUT_NONE;
            }
            return provider.getLockoutModeForUser(sensorId, userId);
        }

        @Override
        public void invalidateAuthenticatorId(int sensorId, int userId,
                IInvalidationCallback callback) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
                return;
            }
            provider.scheduleInvalidateAuthenticatorId(sensorId, userId, callback);
        }

        @Override // Binder call
        public long getAuthenticatorId(int sensorId, int userId) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for getAuthenticatorId");
                return 0;
            }
            return provider.getAuthenticatorId(sensorId, userId);
        }

        @Override // Binder call
        public void resetLockout(IBinder token, int sensorId, int userId,
                @Nullable byte[] hardwareAuthToken, String opPackageName) {
            Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
                return;
            }

            provider.scheduleResetLockout(sensorId, userId, hardwareAuthToken);
        }

        @Override
        public boolean isClientActive() {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
            return mGestureAvailabilityDispatcher.isAnySensorActive();
        }

        @Override
        public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
            mGestureAvailabilityDispatcher.registerCallback(callback);
        }

        @Override
        public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
            Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
            mGestureAvailabilityDispatcher.removeCallback(callback);
        }

        private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) {
            for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
                final Fingerprint21 fingerprint21;
                if ((Build.IS_USERDEBUG || Build.IS_ENG)
                        && getContext().getResources().getBoolean(R.bool.allow_test_udfps)
                        && Settings.Secure.getIntForUser(getContext().getContentResolver(),
                        Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
                        UserHandle.USER_CURRENT) != 0) {
                    fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
                            mBiometricStateCallback, hidlSensor,
                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
                            BiometricContext.getInstance(getContext()));
                } else {
                    fingerprint21 = Fingerprint21.newInstance(getContext(),
                            mBiometricStateCallback, hidlSensor, mHandler,
                            mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
                }
                mServiceProviders.add(fingerprint21);
            }
        }

        private void addAidlProviders() {
            final String[] instances = ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
            if (instances == null || instances.length == 0) {
                return;
            }
            for (String instance : instances) {
                final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
                final IFingerprint fp = IFingerprint.Stub.asInterface(
                        Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
                if (fp == null) {
                    Slog.e(TAG, "Unable to get declared service: " + fqName);
                    continue;
                }
                try {
                    final SensorProps[] props = fp.getSensorProps();
                    final FingerprintProvider provider =
                            new FingerprintProvider(getContext(), mBiometricStateCallback, props,
                                    instance, mLockoutResetDispatcher,
                                    mGestureAvailabilityDispatcher,
                                    BiometricContext.getInstance(getContext()));
                    mServiceProviders.add(provider);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                }
            }
        }

        @Override // Binder call
        public void registerAuthenticators(
                @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            // Some HAL might not be started before the system service and will cause the code below
            // to wait, and some of the operations below might take a significant amount of time to
            // complete (calls to the HALs). To avoid blocking the rest of system server we put
            // this on a background thread.
            final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
                    true /* allowIo */);
            thread.start();
            final Handler handler = new Handler(thread.getLooper());

            handler.post(() -> {
                addHidlProviders(hidlSensors);
                addAidlProviders();

                final IBiometricService biometricService = IBiometricService.Stub.asInterface(
                        ServiceManager.getService(Context.BIOMETRIC_SERVICE));

                // Register each sensor individually with BiometricService
                for (ServiceProvider provider : mServiceProviders) {
                    final List<FingerprintSensorPropertiesInternal> props =
                            provider.getSensorProperties();
                    for (FingerprintSensorPropertiesInternal prop : props) {
                        final int sensorId = prop.sensorId;
                        final @BiometricManager.Authenticators.Types int strength =
                                Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
                        final FingerprintAuthenticator authenticator = new FingerprintAuthenticator(
                                mServiceWrapper, sensorId);
                        try {
                            biometricService.registerAuthenticator(sensorId, TYPE_FINGERPRINT,
                                    strength, authenticator);
                        } catch (RemoteException e) {
                            Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
                        }
                    }
                }

                synchronized (mLock) {
                    for (ServiceProvider provider : mServiceProviders) {
                        mSensorProps.addAll(provider.getSensorProperties());
                    }
                }

                broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
                broadcastAllAuthenticatorsRegistered();
            });
        }

        @Override
        public void addAuthenticatorsRegisteredCallback(
                IFingerprintAuthenticatorsRegisteredCallback callback) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
            if (callback == null) {
                Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null");
                return;
            }

            final boolean registered;
            final boolean hasSensorProps;
            synchronized (mLock) {
                registered = mAuthenticatorsRegisteredCallbacks.register(callback);
                hasSensorProps = !mSensorProps.isEmpty();
            }
            if (registered && hasSensorProps) {
                broadcastAllAuthenticatorsRegistered();
            } else if (!registered) {
                Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback");
            }
        }

        @Override
        public void onPointerDown(long requestId, int sensorId, int x, int y,
                float minor, float major) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
                return;
            }
            provider.onPointerDown(requestId, sensorId, x, y, minor, major);
        }

        @Override
        public void onPointerUp(long requestId, int sensorId) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
                return;
            }
            provider.onPointerUp(requestId, sensorId);
        }

        @Override
        public void onUiReady(long requestId, int sensorId) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            final ServiceProvider provider = getProviderForSensor(sensorId);
            if (provider == null) {
                Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
                return;
            }
            provider.onUiReady(requestId, sensorId);
        }

        @Override
        public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            for (ServiceProvider provider : mServiceProviders) {
                provider.setUdfpsOverlayController(controller);
            }
        }

        @Override
        public void setSidefpsController(@NonNull ISidefpsController controller) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);

            for (ServiceProvider provider : mServiceProviders) {
                provider.setSidefpsController(controller);
            }
        }

        @Override
        public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
            FingerprintService.this.registerBiometricStateListener(listener);
        }

        @Override
        public void onPowerPressed() {
            Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
            for (ServiceProvider provider : mServiceProviders) {
                provider.onPowerPressed();
            }
        }
    };

    public FingerprintService(Context context) {
        super(context);
        mServiceWrapper = new FingerprintServiceWrapper();
        mAppOps = context.getSystemService(AppOpsManager.class);
        mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
        mLockoutResetDispatcher = new LockoutResetDispatcher(context);
        mLockPatternUtils = new LockPatternUtils(context);
        mServiceProviders = new ArrayList<>();
        mBiometricStateCallback = new BiometricStateCallback();
        mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
        mSensorProps = new ArrayList<>();
        mHandler = new Handler(Looper.getMainLooper());
    }

    // Notifies the callbacks that all of the authenticators have been registered and removes the
    // invoked callbacks from the callback list.
    private void broadcastAllAuthenticatorsRegistered() {
        // Make a local copy of the data so it can be used outside of the synchronized block when
        // making Binder calls.
        final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>();
        final List<FingerprintSensorPropertiesInternal> props;
        synchronized (mLock) {
            if (!mSensorProps.isEmpty()) {
                props = new ArrayList<>(mSensorProps);
            } else {
                Slog.e(TAG, "mSensorProps is empty");
                return;
            }
            final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast();
            for (int i = 0; i < n; ++i) {
                final IFingerprintAuthenticatorsRegisteredCallback cb =
                        mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i);
                callbacks.add(cb);
                mAuthenticatorsRegisteredCallbacks.unregister(cb);
            }
            mAuthenticatorsRegisteredCallbacks.finishBroadcast();
        }
        for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) {
            try {
                cb.onAllAuthenticatorsRegistered(props);
            } catch (RemoteException e) {
                Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e);
            }
        }
    }

    @Override
    public void onStart() {
        publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
    }

    @Nullable
    private ServiceProvider getProviderForSensor(int sensorId) {
        for (ServiceProvider provider : mServiceProviders) {
            if (provider.containsSensor(sensorId)) {
                return provider;
            }
        }
        return null;
    }

    /**
     * For devices with only a single provider, returns that provider. If multiple providers,
     * returns the first one. If no providers, returns null.
     */
    @Nullable
    private Pair<Integer, ServiceProvider> getSingleProvider() {
        final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
        if (properties.isEmpty()) {
            Slog.e(TAG, "No providers found");
            return null;
        }

        // Theoretically we can just return the first provider, but maybe this is easier to
        // understand.
        final int sensorId = properties.get(0).sensorId;
        for (ServiceProvider provider : mServiceProviders) {
            if (provider.containsSensor(sensorId)) {
                return new Pair<>(sensorId, provider);
            }
        }

        Slog.e(TAG, "Provider not found");
        return null;
    }

    @NonNull
    private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
        synchronized (mLock) {
            return mSensorProps;
        }
    }

    @NonNull
    private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
        final Pair<Integer, ServiceProvider> provider = getSingleProvider();
        if (provider == null) {
            Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: "
                    + opPackageName);
            return Collections.emptyList();
        }

        return provider.second.getEnrolledFingerprints(provider.first, userId);
    }

    /** Checks for public API invocations to ensure that permissions, etc are granted/correct. */
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    private boolean canUseFingerprint(
            String opPackageName,
            String attributionTag,
            boolean requireForeground,
            int uid,
            int pid,
            int userId) {
        if (getContext().checkCallingPermission(USE_FINGERPRINT)
                != PackageManager.PERMISSION_GRANTED) {
            Utils.checkPermission(getContext(), USE_BIOMETRIC);
        }

        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
            return true; // System process (BiometricService, etc) is always allowed
        }
        if (Utils.isKeyguard(getContext(), opPackageName)) {
            return true;
        }
        if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
            Slog.w(TAG, "Rejecting " + opPackageName + "; not a current user or profile");
            return false;
        }
        if (!checkAppOps(uid, opPackageName, attributionTag)) {
            Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied");
            return false;
        }
        if (requireForeground && !Utils.isForeground(uid, pid)) {
            Slog.w(TAG, "Rejecting " + opPackageName + "; not in foreground");
            return false;
        }
        return true;
    }

    private boolean checkAppOps(int uid, String opPackageName, String attributionTag) {
        boolean appOpsOk = false;
        if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid, opPackageName, attributionTag, null)
                == AppOpsManager.MODE_ALLOWED) {
            appOpsOk = true;
        } else if (mAppOps.noteOp(
                        AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName, attributionTag, null)
                == AppOpsManager.MODE_ALLOWED) {
            appOpsOk = true;
        }
        return appOpsOk;
    }
}
