blob: a4b0a0eece5b74dcabce8842d6777a5d1496e97e [file] [log] [blame]
/*
* Copyright (C) 2021 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;
import static android.hardware.biometrics.BiometricStateListener.STATE_AUTH_OTHER;
import static android.hardware.biometrics.BiometricStateListener.STATE_BP_AUTH;
import static android.hardware.biometrics.BiometricStateListener.STATE_ENROLLING;
import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE;
import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserManager;
import android.util.Slog;
import com.android.server.biometrics.Utils;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A callback for receiving notifications about biometric sensor state changes.
*
* @param <T> service provider type
* @param <P> internal property type
*/
public class BiometricStateCallback<T extends BiometricServiceProvider<P>,
P extends SensorPropertiesInternal>
implements ClientMonitorCallback, IBinder.DeathRecipient {
private static final String TAG = "BiometricStateCallback";
@NonNull
private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners =
new CopyOnWriteArrayList<>();
@NonNull
private final UserManager mUserManager;
@BiometricStateListener.State
private int mBiometricState;
@NonNull
private List<T> mProviders = List.of();
/**
* Create a new callback that must be {@link #start(List)}ed.
*
* @param userManager user manager
*/
public BiometricStateCallback(@NonNull UserManager userManager) {
mBiometricState = STATE_IDLE;
mUserManager = userManager;
}
/**
* This should be called when the service has been initialized and all providers are ready.
*
* @param allProviders all registered biometric service providers
*/
public synchronized void start(@NonNull List<T> allProviders) {
mProviders = Collections.unmodifiableList(allProviders);
broadcastCurrentEnrollmentState(null /* listener */);
}
/** Get the current state. */
@BiometricStateListener.State
public int getBiometricState() {
return mBiometricState;
}
@Override
public void onClientStarted(@NonNull BaseClientMonitor client) {
final int previousBiometricState = mBiometricState;
if (client instanceof AuthenticationClient) {
final AuthenticationClient<?, ?> authClient = (AuthenticationClient<?, ?>) client;
if (authClient.isKeyguard()) {
mBiometricState = STATE_KEYGUARD_AUTH;
} else if (authClient.isBiometricPrompt()) {
mBiometricState = STATE_BP_AUTH;
} else {
mBiometricState = STATE_AUTH_OTHER;
}
} else if (client instanceof EnrollClient) {
mBiometricState = STATE_ENROLLING;
} else {
Slog.w(TAG, "Other authentication client: " + Utils.getClientName(client));
mBiometricState = STATE_IDLE;
}
Slog.d(TAG, "State updated from " + previousBiometricState + " to " + mBiometricState
+ ", client " + client);
notifyBiometricStateListeners(mBiometricState);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor client, boolean success) {
mBiometricState = STATE_IDLE;
Slog.d(TAG, "Client finished, state updated to " + mBiometricState + ", client "
+ client);
if (client instanceof EnrollmentModifier) {
EnrollmentModifier enrollmentModifier = (EnrollmentModifier) client;
final boolean enrollmentStateChanged = enrollmentModifier.hasEnrollmentStateChanged();
Slog.d(TAG, "Enrollment state changed: " + enrollmentStateChanged);
if (enrollmentStateChanged) {
notifyAllEnrollmentStateChanged(client.getTargetUserId(),
client.getSensorId(),
enrollmentModifier.hasEnrollments());
}
}
notifyBiometricStateListeners(mBiometricState);
}
private void notifyBiometricStateListeners(@BiometricStateListener.State int newState) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
try {
listener.onStateChanged(newState);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in biometric state change", e);
}
}
}
@Override
public void onBiometricAction(@BiometricStateListener.Action int action) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
try {
listener.onBiometricAction(action);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in onBiometricAction", e);
}
}
}
/**
* Enables clients to register a BiometricStateListener. For example, this is used to forward
* fingerprint sensor state changes to SideFpsEventHandler.
*
* @param listener listener to register
*/
public synchronized void registerBiometricStateListener(
@NonNull IBiometricStateListener listener) {
mBiometricStateListeners.add(listener);
broadcastCurrentEnrollmentState(listener);
try {
listener.asBinder().linkToDeath(this, 0 /* flags */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to link to death", e);
}
}
private synchronized void broadcastCurrentEnrollmentState(
@Nullable IBiometricStateListener listener) {
for (T provider : mProviders) {
for (SensorPropertiesInternal prop : provider.getSensorProperties()) {
for (UserInfo userInfo : mUserManager.getAliveUsers()) {
final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id);
if (listener != null) {
notifyEnrollmentStateChanged(
listener, userInfo.id, prop.sensorId, enrolled);
} else {
notifyAllEnrollmentStateChanged(
userInfo.id, prop.sensorId, enrolled);
}
}
}
}
}
private void notifyAllEnrollmentStateChanged(int userId, int sensorId,
boolean hasEnrollments) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
}
}
private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
int userId, int sensorId, boolean hasEnrollments) {
try {
listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
@Override
public void binderDied() {
// Do nothing, handled below
}
@Override
public void binderDied(IBinder who) {
Slog.w(TAG, "Callback binder died: " + who);
if (mBiometricStateListeners.removeIf(listener -> listener.asBinder().equals(who))) {
Slog.w(TAG, "Removed dead listener for " + who);
} else {
Slog.w(TAG, "No dead listeners found");
}
}
}