blob: 942e0501d88d5236bd8c50692a969c85a4cce365 [file] [log] [blame]
/*
* Copyright (C) 2018 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;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.media.AudioAttributes;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Slog;
import com.android.internal.logging.MetricsLogger;
import java.util.ArrayList;
import java.util.NoSuchElementException;
/**
* Abstract base class for keeping track and dispatching events from the biometric's HAL to the
* the current client. Subclasses are responsible for coordinating the interaction with
* the biometric's HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.).
*/
public abstract class ClientMonitor extends LoggableMonitor implements IBinder.DeathRecipient {
protected static final int ERROR_ESRCH = 3; // Likely HAL is dead. See errno.h.
protected static final boolean DEBUG = BiometricServiceBase.DEBUG;
private static final AudioAttributes FINGERPRINT_SONFICATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
private final Context mContext;
private final long mHalDeviceId;
private final int mTargetUserId;
private final int mGroupId;
// True if client does not have MANAGE_FINGERPRINT permission
private final boolean mIsRestricted;
private final String mOwner;
private final VibrationEffect mSuccessVibrationEffect;
private final VibrationEffect mErrorVibrationEffect;
private final BiometricServiceBase.DaemonWrapper mDaemon;
private IBinder mToken;
private BiometricServiceBase.ServiceListener mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
private final int mCookie;
protected final MetricsLogger mMetricsLogger;
protected final Constants mConstants;
protected boolean mAlreadyCancelled;
protected boolean mAlreadyDone;
/**
* @param context context of BiometricService
* @param daemon interface to call back to a specific biometric's daemon
* @param halDeviceId the HAL device ID of the associated biometric hardware
* @param token a unique token for the client
* @param listener recipient of related events (e.g. authentication)
* @param userId target user id for operation
* @param groupId groupId for the fingerprint set
* @param restricted whether or not client has the MANAGE_* permission
* permission
* @param owner name of the client that owns this
*/
public ClientMonitor(Context context, Constants constants,
BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
BiometricServiceBase.ServiceListener listener, int userId, int groupId,
boolean restricted, String owner, int cookie) {
mContext = context;
mConstants = constants;
mDaemon = daemon;
mHalDeviceId = halDeviceId;
mToken = token;
mListener = listener;
mTargetUserId = userId;
mGroupId = groupId;
mIsRestricted = restricted;
mOwner = owner;
mCookie = cookie;
mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
mMetricsLogger = new MetricsLogger();
try {
if (token != null) {
token.linkToDeath(this, 0);
}
} catch (RemoteException e) {
Slog.w(getLogTag(), "caught remote exception in linkToDeath: ", e);
}
}
protected String getLogTag() {
return mConstants.logTag();
}
public int getCookie() {
return mCookie;
}
/**
* Contacts the biometric's HAL to start the client.
* @return 0 on success, errno from driver on failure
*/
public abstract int start();
/**
* Contacts the biometric's HAL to stop the client.
* @param initiatedByClient whether the operation is at the request of a client
*/
public abstract int stop(boolean initiatedByClient);
/**
* Method to explicitly poke powermanager on events
*/
public abstract void notifyUserActivity();
// Event callbacks from driver. Inappropriate calls is flagged/logged by the
// respective client (e.g. enrolling shouldn't get authenticate events).
// All of these return 'true' if the operation is completed and it's ok to move
// to the next client (e.g. authentication accepts or rejects a biometric).
public abstract boolean onEnrollResult(BiometricAuthenticator.Identifier identifier,
int remaining);
public abstract boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
boolean authenticated, ArrayList<Byte> token);
public abstract boolean onRemoved(BiometricAuthenticator.Identifier identifier,
int remaining);
public abstract boolean onEnumerationResult(
BiometricAuthenticator.Identifier identifier, int remaining);
public int[] getAcquireIgnorelist() {
return new int[0];
}
public int[] getAcquireVendorIgnorelist() {
return new int[0];
}
private boolean blacklistContains(int acquiredInfo, int vendorCode) {
if (acquiredInfo == mConstants.acquireVendorCode()) {
for (int i = 0; i < getAcquireVendorIgnorelist().length; i++) {
if (getAcquireVendorIgnorelist()[i] == vendorCode) {
if (DEBUG) Slog.v(getLogTag(), "Ignoring vendor message: " + vendorCode);
return true;
}
}
} else {
for (int i = 0; i < getAcquireIgnorelist().length; i++) {
if (getAcquireIgnorelist()[i] == acquiredInfo) {
if (DEBUG) Slog.v(getLogTag(), "Ignoring message: " + acquiredInfo);
return true;
}
}
}
return false;
}
public boolean isAlreadyDone() {
return mAlreadyDone;
}
/**
* Called when we get notification from the biometric's HAL that an image has been acquired.
* Common to authenticate and enroll.
* @param acquiredInfo info about the current image acquisition
* @return true if client should be removed
*/
public boolean onAcquired(int acquiredInfo, int vendorCode) {
super.logOnAcquired(mContext, acquiredInfo, vendorCode, getTargetUserId());
if (DEBUG) Slog.v(getLogTag(), "Acquired: " + acquiredInfo + " " + vendorCode);
try {
if (mListener != null && !blacklistContains(acquiredInfo, vendorCode)) {
mListener.onAcquired(getHalDeviceId(), acquiredInfo, vendorCode);
}
return false; // acquisition continues...
} catch (RemoteException e) {
Slog.w(getLogTag(), "Failed to invoke sendAcquired", e);
return true;
} finally {
// Good scans will keep the device awake
if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
notifyUserActivity();
}
}
}
/**
* Called when we get notification from the biometric's HAL that an error has occurred with the
* current operation. Common to authenticate, enroll, enumerate and remove.
* @param error
* @return true if client should be removed
*/
public boolean onError(long deviceId, int error, int vendorCode) {
super.logOnError(mContext, error, vendorCode, getTargetUserId());
try {
if (mListener != null) {
mListener.onError(deviceId, error, vendorCode, getCookie());
}
} catch (RemoteException e) {
Slog.w(getLogTag(), "Failed to invoke sendError", e);
}
return true; // errors always remove current client
}
public void destroy() {
if (mToken != null) {
try {
mToken.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
// TODO: remove when duplicate call bug is found
Slog.e(getLogTag(), "destroy(): " + this + ":", new Exception("here"));
}
mToken = null;
}
mListener = null;
}
@Override
public void binderDied() {
// If the current client dies we should cancel the current operation.
Slog.e(getLogTag(), "Binder died, cancelling client");
stop(false /* initiatedByClient */);
mToken = null;
mListener = null;
}
@Override
protected void finalize() throws Throwable {
try {
if (mToken != null) {
if (DEBUG) Slog.w(getLogTag(), "removing leaked reference: " + mToken);
onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
}
} finally {
super.finalize();
}
}
public final Context getContext() {
return mContext;
}
public final long getHalDeviceId() {
return mHalDeviceId;
}
public final String getOwnerString() {
return mOwner;
}
public final BiometricServiceBase.ServiceListener getListener() {
return mListener;
}
public final BiometricServiceBase.DaemonWrapper getDaemonWrapper() {
return mDaemon;
}
public final boolean getIsRestricted() {
return mIsRestricted;
}
public final int getTargetUserId() {
return mTargetUserId;
}
public final int getGroupId() {
return mGroupId;
}
public final IBinder getToken() {
return mToken;
}
public final void vibrateSuccess() {
Vibrator vibrator = mContext.getSystemService(Vibrator.class);
if (vibrator != null) {
vibrator.vibrate(mSuccessVibrationEffect, FINGERPRINT_SONFICATION_ATTRIBUTES);
}
}
public final void vibrateError() {
Vibrator vibrator = mContext.getSystemService(Vibrator.class);
if (vibrator != null) {
vibrator.vibrate(mErrorVibrationEffect, FINGERPRINT_SONFICATION_ATTRIBUTES);
}
}
}