blob: 87d45b9de745b02dfd93a57c43e61cfaae59e5b3 [file] [log] [blame]
/*
* Copyright (C) 2014 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.Manifest.permission.INTERACT_ACROSS_USERS;
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.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_HAS_ENROLLED_FINGERPRINTS;
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_IS_HARDWARE_DETECTED;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricTestSession;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.SensorProperties;
import android.os.Binder;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.security.identity.IdentityCredential;
import android.util.Slog;
import android.view.Surface;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.Signature;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.crypto.Cipher;
import javax.crypto.Mac;
/**
* A class that coordinates access to the fingerprint hardware.
* @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
* authentication. In a world where devices may have different types of biometric authentication,
* it's much more realistic to have a system-provided authentication dialog since the method may
* vary by vendor/device.
*/
@SuppressWarnings("deprecation")
@Deprecated
@SystemService(Context.FINGERPRINT_SERVICE)
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
private static final boolean DEBUG = true;
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
private static final int MSG_AUTHENTICATION_FAILED = 103;
private static final int MSG_ERROR = 104;
private static final int MSG_REMOVED = 105;
private static final int MSG_CHALLENGE_GENERATED = 106;
private static final int MSG_FINGERPRINT_DETECTED = 107;
private static final int MSG_UDFPS_POINTER_DOWN = 108;
private static final int MSG_UDFPS_POINTER_UP = 109;
/**
* @hide
*/
public static final int ENROLL_FIND_SENSOR = 1;
/**
* @hide
*/
public static final int ENROLL_ENROLL = 2;
/**
* @hide
*/
@IntDef({ENROLL_FIND_SENSOR, ENROLL_ENROLL})
@Retention(RetentionPolicy.SOURCE)
public @interface EnrollReason {}
/**
* Request authentication with any single sensor.
* @hide
*/
public static final int SENSOR_ID_ANY = -1;
private static class RemoveTracker {
static final int REMOVE_SINGLE = 1;
static final int REMOVE_ALL = 2;
@IntDef({REMOVE_SINGLE, REMOVE_ALL})
@interface RemoveRequest {}
final @RemoveRequest int mRemoveRequest;
@Nullable final Fingerprint mSingleFingerprint;
RemoveTracker(@RemoveRequest int request, @Nullable Fingerprint fingerprint) {
mRemoveRequest = request;
mSingleFingerprint = fingerprint;
}
}
private IFingerprintService mService;
private Context mContext;
private IBinder mToken = new Binder();
private AuthenticationCallback mAuthenticationCallback;
private FingerprintDetectionCallback mFingerprintDetectionCallback;
private EnrollmentCallback mEnrollmentCallback;
private RemovalCallback mRemovalCallback;
private GenerateChallengeCallback mGenerateChallengeCallback;
private CryptoObject mCryptoObject;
@Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
* @hide
*/
@TestApi
@NonNull
@RequiresPermission(TEST_BIOMETRIC)
public List<SensorProperties> getSensorProperties() {
final List<SensorProperties> properties = new ArrayList<>();
final List<FingerprintSensorPropertiesInternal> internalProperties
= getSensorPropertiesInternal();
for (FingerprintSensorPropertiesInternal internalProp : internalProperties) {
properties.add(FingerprintSensorProperties.from(internalProp));
}
return properties;
}
/**
* Retrieves a test session for FingerprintManager.
* @hide
*/
@TestApi
@NonNull
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
return new BiometricTestSession(mContext, sensorId,
(context, sensorId1, callback) -> mService
.createTestSession(sensorId1, callback, context.getOpPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private class OnEnrollCancelListener implements OnCancelListener {
@Override
public void onCancel() {
cancelEnrollment();
}
}
private class OnAuthenticationCancelListener implements OnCancelListener {
private android.hardware.biometrics.CryptoObject mCrypto;
public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
mCrypto = crypto;
}
@Override
public void onCancel() {
cancelAuthentication(mCrypto);
}
}
private class OnFingerprintDetectionCancelListener implements OnCancelListener {
@Override
public void onCancel() {
cancelFingerprintDetect();
}
}
/**
* A wrapper class for the crypto objects supported by FingerprintManager. Currently the
* framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
* @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
*/
@Deprecated
public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
public CryptoObject(@NonNull Signature signature) {
super(signature);
}
public CryptoObject(@NonNull Cipher cipher) {
super(cipher);
}
public CryptoObject(@NonNull Mac mac) {
super(mac);
}
/**
* Get {@link Signature} object.
* @return {@link Signature} object or null if this doesn't contain one.
*/
public Signature getSignature() {
return super.getSignature();
}
/**
* Get {@link Cipher} object.
* @return {@link Cipher} object or null if this doesn't contain one.
*/
public Cipher getCipher() {
return super.getCipher();
}
/**
* Get {@link Mac} object.
* @return {@link Mac} object or null if this doesn't contain one.
*/
public Mac getMac() {
return super.getMac();
}
/**
* Get {@link IdentityCredential} object.
* @return {@link IdentityCredential} object or null if this doesn't contain one.
* @hide
*/
public IdentityCredential getIdentityCredential() {
return super.getIdentityCredential();
}
}
/**
* Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}.
* @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
*/
@Deprecated
public static class AuthenticationResult {
private Fingerprint mFingerprint;
private CryptoObject mCryptoObject;
private int mUserId;
private boolean mIsStrongBiometric;
/**
* Authentication result
*
* @param crypto the crypto object
* @param fingerprint the recognized fingerprint data, if allowed.
* @hide
*/
public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId,
boolean isStrongBiometric) {
mCryptoObject = crypto;
mFingerprint = fingerprint;
mUserId = userId;
mIsStrongBiometric = isStrongBiometric;
}
/**
* Obtain the crypto object associated with this transaction
* @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}.
*/
public CryptoObject getCryptoObject() { return mCryptoObject; }
/**
* Obtain the Fingerprint associated with this operation. Applications are strongly
* discouraged from associating specific fingers with specific applications or operations.
*
* @hide
*/
@UnsupportedAppUsage
public Fingerprint getFingerprint() { return mFingerprint; }
/**
* Obtain the userId for which this fingerprint was authenticated.
* @hide
*/
public int getUserId() { return mUserId; }
/**
* Check whether the strength of the fingerprint modality associated with this operation is
* strong (i.e. not weak or convenience).
* @hide
*/
public boolean isStrongBiometric() {
return mIsStrongBiometric;
}
}
/**
* Callback structure provided to {@link FingerprintManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link
* FingerprintManager#authenticate(CryptoObject, CancellationSignal,
* int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
* fingerprint events.
* @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
public static abstract class AuthenticationCallback
extends BiometricAuthenticator.AuthenticationCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further callbacks will be made on this object.
* @param errorCode An integer identifying the error message
* @param errString A human-readable error string that can be shown in UI
*/
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) { }
/**
* Called when a recoverable error has been encountered during authentication. The help
* string is provided to give the user guidance for what went wrong, such as
* "Sensor dirty, please clean it."
* @param helpCode An integer identifying the error message
* @param helpString A human-readable string that can be shown in UI
*/
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) { }
/**
* Called when a fingerprint is recognized.
* @param result An object containing authentication-related data
*/
public void onAuthenticationSucceeded(AuthenticationResult result) { }
/**
* Called when a fingerprint is valid but not recognized.
*/
@Override
public void onAuthenticationFailed() { }
/**
* Called when a fingerprint image has been acquired, but wasn't processed yet.
*
* @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants
* @hide
*/
@Override
public void onAuthenticationAcquired(int acquireInfo) {}
/**
* Invoked for under-display fingerprint sensors when a touch has been detected on the
* sensor area.
* @hide
*/
public void onUdfpsPointerDown(int sensorId) {}
/**
* Invoked for under-display fingerprint sensors when a touch has been removed from the
* sensor area.
* @hide
*/
public void onUdfpsPointerUp(int sensorId) {}
}
/**
* Callback structure provided for {@link #detectFingerprint(CancellationSignal,
* FingerprintDetectionCallback, int, Surface)}.
* @hide
*/
public interface FingerprintDetectionCallback {
/**
* Invoked when a fingerprint has been detected.
*/
void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric);
}
/**
* Callback structure provided to {@link FingerprintManager#enroll(byte[], CancellationSignal,
* int, EnrollmentCallback)} must provide an implementation of this for listening to
* fingerprint events.
*
* @hide
*/
public static abstract class EnrollmentCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further callbacks will be made on this object.
* @param errMsgId An integer identifying the error message
* @param errString A human-readable error string that can be shown in UI
*/
public void onEnrollmentError(int errMsgId, CharSequence errString) { }
/**
* Called when a recoverable error has been encountered during enrollment. The help
* string is provided to give the user guidance for what went wrong, such as
* "Sensor dirty, please clean it" or what they need to do next, such as
* "Touch sensor again."
* @param helpMsgId An integer identifying the error message
* @param helpString A human-readable string that can be shown in UI
*/
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { }
/**
* Called as each enrollment step progresses. Enrollment is considered complete when
* remaining reaches 0. This function will not be called if enrollment fails. See
* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
* @param remaining The number of remaining steps
*/
public void onEnrollmentProgress(int remaining) { }
}
/**
* Callback structure provided to {@link #remove}. Users of {@link FingerprintManager} may
* optionally provide an implementation of this to
* {@link #remove(Fingerprint, int, RemovalCallback)} for listening to fingerprint template
* removal events.
*
* @hide
*/
public static abstract class RemovalCallback {
/**
* Called when the given fingerprint can't be removed.
* @param fp The fingerprint that the call attempted to remove
* @param errMsgId An associated error message id
* @param errString An error message indicating why the fingerprint id can't be removed
*/
public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { }
/**
* Called when a given fingerprint is successfully removed.
* @param fp The fingerprint template that was removed.
* @param remaining The number of fingerprints yet to be removed in this operation. If
* {@link #remove} is called on one fingerprint, this should be 0. If
* {@link #remove} is called on a group, this should be the number of remaining
* fingerprints in the group, and 0 after the last fingerprint is removed.
*/
public void onRemovalSucceeded(@Nullable Fingerprint fp, int remaining) { }
}
/**
* @hide
*/
public static abstract class LockoutResetCallback {
/**
* Called when lockout period expired and clients are allowed to listen for fingerprint
* again.
*/
public void onLockoutReset(int sensorId) { }
}
/**
* Callbacks for generate challenge operations.
*
* @hide
*/
public interface GenerateChallengeCallback {
/** Called when a challenged has been generated. */
void onChallengeGenerated(int sensorId, int userId, long challenge);
}
/**
* Use the provided handler thread for events.
* @param handler
*/
private void useHandler(Handler handler) {
if (handler != null) {
mHandler = new MyHandler(handler.getLooper());
} else if (mHandler.getLooper() != mContext.getMainLooper()) {
mHandler = new MyHandler(mContext.getMainLooper());
}
}
/**
* Request authentication of a crypto object. This call warms up the fingerprint hardware
* and starts scanning for a fingerprint. It terminates when
* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
* which point the object is no longer valid. The operation can be canceled by using the
* provided cancel object.
*
* @param crypto object associated with the call or null if none required.
* @param cancel an object that can be used to cancel authentication
* @param flags optional flags; should be 0
* @param callback an object to receive authentication events
* @param handler an optional handler to handle callback events
*
* @throws IllegalArgumentException if the crypto operation is not supported or is not backed
* by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
* facility</a>.
* @throws IllegalStateException if the crypto primitive is not initialized.
* @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
* BiometricPrompt.CryptoObject, CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)}
*/
@Deprecated
@RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
authenticate(crypto, cancel, callback, handler, mContext.getUserId());
}
/**
* Per-user version of authenticate.
* @hide
*/
@RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
@NonNull AuthenticationCallback callback, Handler handler, int userId) {
authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId);
}
/**
* Per-user and per-sensor version of authenticate.
* @hide
*/
@RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
@NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId) {
FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED,
AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE,
mContext.getApplicationInfo().uid,
mContext.getApplicationInfo().targetSdkVersion);
if (callback == null) {
throw new IllegalArgumentException("Must supply an authentication callback");
}
if (cancel != null) {
if (cancel.isCanceled()) {
Slog.w(TAG, "authentication already canceled");
return;
} else {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
}
}
if (mService != null) {
try {
useHandler(handler);
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
mService.authenticate(mToken, operationId, sensorId, userId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try
// again later.
callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */));
}
}
}
/**
* Uses the fingerprint hardware to detect for the presence of a finger, without giving details
* about accept/reject/lockout.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void detectFingerprint(@NonNull CancellationSignal cancel,
@NonNull FingerprintDetectionCallback callback, int userId) {
if (mService == null) {
return;
}
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
} else {
cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
}
mFingerprintDetectionCallback = callback;
try {
mService.detectFingerprint(mToken, userId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
}
/**
* Request fingerprint enrollment. This call warms up the fingerprint hardware
* and starts scanning for fingerprints. Progress will be indicated by callbacks to the
* {@link EnrollmentCallback} object. It terminates when
* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or
* {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
* which point the object is no longer valid. The operation can be canceled by using the
* provided cancel object.
* @param token a unique token provided by a recent creation or verification of device
* credentials (e.g. pin, pattern or password).
* @param cancel an object that can be used to cancel enrollment
* @param userId the user to whom this fingerprint will belong to
* @param callback an object to receive enrollment events
* @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
* should be logged.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId,
EnrollmentCallback callback, @EnrollReason int enrollReason) {
if (userId == UserHandle.USER_CURRENT) {
userId = getCurrentUserId();
}
if (callback == null) {
throw new IllegalArgumentException("Must supply an enrollment callback");
}
if (cancel != null) {
if (cancel.isCanceled()) {
Slog.w(TAG, "enrollment already canceled");
return;
} else {
cancel.setOnCancelListener(new OnEnrollCancelListener());
}
}
if (mService != null) {
try {
mEnrollmentCallback = callback;
mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
mContext.getOpPackageName(), enrollReason);
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception in enroll: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try
// again later.
callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE,
getErrorString(mContext, FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */));
}
}
}
/**
* Generates a unique random challenge in the TEE. A typical use case is to have it wrapped in a
* HardwareAuthenticationToken, minted by Gatekeeper upon PIN/Pattern/Password verification.
* The HardwareAuthenticationToken can then be sent to the biometric HAL together with a
* request to perform sensitive operation(s) (for example enroll), represented by the challenge.
* Doing this ensures that a the sensitive operation cannot be performed unless the user has
* entered confirmed PIN/Pattern/Password.
*
* @see com.android.server.locksettings.LockSettingsService
*
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) try {
mGenerateChallengeCallback = callback;
mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
* enumerated sensor.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(int userId, GenerateChallengeCallback callback) {
final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor();
if (sensorProps == null) {
Slog.e(TAG, "No sensors");
return;
}
generateChallenge(sensorProps.sensorId, userId, callback);
}
/**
* Revokes the specified challenge.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void revokeChallenge(int userId, long challenge) {
if (mService != null) {
try {
final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor();
if (sensorProps == null) {
Slog.e(TAG, "No sensors");
return;
}
mService.revokeChallenge(mToken, sensorProps.sensorId, userId,
mContext.getOpPackageName(), challenge);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
*
* @param sensorId Sensor ID that this operation takes effect for
* @param userId User ID that this operation takes effect for.
* @param hardwareAuthToken An opaque token returned by password confirmation.
* @hide
*/
@RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
public void resetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
if (mService != null) {
try {
mService.resetLockout(mToken, sensorId, userId, hardwareAuthToken,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Remove given fingerprint template from fingerprint hardware and/or protected storage.
* @param fp the fingerprint item to remove
* @param userId the user who this fingerprint belongs to
* @param callback an optional callback to verify that fingerprint templates have been
* successfully removed. May be null of no callback is required.
*
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
if (mService != null) try {
mRemovalCallback = callback;
mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_SINGLE, fp);
mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Removes all face templates for the given user.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
mRemovalCallback = callback;
mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_ALL, null /* fp */);
mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Renames the given fingerprint template
* @param fpId the fingerprint id
* @param userId the user who this fingerprint belongs to
* @param newName the new name
*
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void rename(int fpId, int userId, String newName) {
// Renames the given fpId
if (mService != null) {
try {
mService.rename(fpId, userId, newName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else {
Slog.w(TAG, "rename(): Service not connected!");
}
}
/**
* Obtain the list of enrolled fingerprints templates.
* @return list of current fingerprint items
*
* @hide
*/
@RequiresPermission(USE_FINGERPRINT)
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public List<Fingerprint> getEnrolledFingerprints(int userId) {
if (mService != null) try {
return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return null;
}
/**
* Obtain the list of enrolled fingerprints templates.
* @return list of current fingerprint items
*
* @hide
*/
@RequiresPermission(USE_FINGERPRINT)
@UnsupportedAppUsage
public List<Fingerprint> getEnrolledFingerprints() {
return getEnrolledFingerprints(mContext.getUserId());
}
/**
* @hide
*/
public boolean hasEnrolledTemplates() {
return hasEnrolledFingerprints();
}
/**
* @hide
*/
public boolean hasEnrolledTemplates(int userId) {
return hasEnrolledFingerprints(userId);
}
/**
* Checks if the specified user has enrollments in any of the specified sensors.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledTemplatesForAnySensor(int userId,
@NonNull List<FingerprintSensorPropertiesInternal> sensors) {
if (mService == null) {
Slog.w(TAG, "hasEnrolledTemplatesForAnySensor: no fingerprint service");
return false;
}
try {
return mService.hasEnrolledTemplatesForAnySensor(userId, sensors,
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
if (mService == null) {
Slog.w(TAG, "setUdfpsOverlayController: no fingerprint service");
return;
}
try {
mService.setUdfpsOverlayController(controller);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void setSidefpsController(@NonNull ISidefpsController controller) {
if (mService == null) {
Slog.w(TAG, "setSidefpsController: no fingerprint service");
return;
}
try {
mService.setSidefpsController(controller);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Forwards FingerprintStateListener to FingerprintService
* @param listener new FingerprintStateListener being added
* @hide
*/
public void registerFingerprintStateListener(@NonNull FingerprintStateListener listener) {
try {
mService.registerFingerprintStateListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
if (mService == null) {
Slog.w(TAG, "onFingerDown: no fingerprint service");
return;
}
try {
mService.onPointerDown(sensorId, x, y, minor, major);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPointerUp(int sensorId) {
if (mService == null) {
Slog.w(TAG, "onFingerDown: no fingerprint service");
return;
}
try {
mService.onPointerUp(sensorId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onUiReady(int sensorId) {
if (mService == null) {
Slog.w(TAG, "onUiReady: no fingerprint service");
return;
}
try {
mService.onUiReady(sensorId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Determine if there is at least one fingerprint enrolled.
*
* @return true if at least one fingerprint is enrolled, false otherwise
* @deprecated See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
*/
@Deprecated
@RequiresPermission(USE_FINGERPRINT)
public boolean hasEnrolledFingerprints() {
FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED,
AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_HAS_ENROLLED_FINGERPRINTS,
mContext.getApplicationInfo().uid,
mContext.getApplicationInfo().targetSdkVersion);
return hasEnrolledFingerprints(UserHandle.myUserId());
}
/**
* @hide
*/
@RequiresPermission(allOf = {
USE_FINGERPRINT,
INTERACT_ACROSS_USERS})
public boolean hasEnrolledFingerprints(int userId) {
if (mService != null) try {
return mService.hasEnrolledFingerprintsDeprecated(userId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return false;
}
/**
* Determine if fingerprint hardware is present and functional.
*
* @return true if hardware is present and functional, false otherwise.
* @deprecated See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
*/
@Deprecated
@RequiresPermission(USE_FINGERPRINT)
public boolean isHardwareDetected() {
FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED,
AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_IS_HARDWARE_DETECTED,
mContext.getApplicationInfo().uid,
mContext.getApplicationInfo().targetSdkVersion);
if (mService != null) {
try {
return mService.isHardwareDetectedDeprecated(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else {
Slog.w(TAG, "isFingerprintHardwareDetected(): Service not connected!");
}
return false;
}
/**
* Get statically configured sensor properties.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@NonNull
public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal() {
try {
if (mService == null) {
return new ArrayList<>();
}
return mService.getSensorPropertiesInternal(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns whether the device has a power button fingerprint sensor.
* @return boolean indicating whether power button is fingerprint sensor
* @hide
*/
public boolean isPowerbuttonFps() {
final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor();
return sensorProps.sensorType == TYPE_POWER_BUTTON;
}
/**
* Adds a callback that gets called when the service registers all of the fingerprint
* authenticators (HALs).
*
* If the fingerprint authenticators are already registered when the callback is added, the
* callback is invoked immediately.
*
* The callback is automatically removed after it's invoked.
*
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
if (mService != null) {
try {
mService.addAuthenticatorsRegisteredCallback(callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else {
Slog.w(TAG, "addProvidersAvailableCallback(): Service not connected!");
}
}
/**
* @hide
*/
public void addLockoutResetCallback(final LockoutResetCallback callback) {
if (mService != null) {
try {
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
mService.addLockoutResetCallback(
new IBiometricServiceLockoutResetCallback.Stub() {
@Override
public void onLockoutReset(int sensorId, IRemoteCallback serverCallback)
throws RemoteException {
try {
final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback");
wakeLock.acquire();
mHandler.post(() -> {
try {
callback.onLockoutReset(sensorId);
} finally {
wakeLock.release();
}
});
} finally {
serverCallback.sendResult(null /* data */);
}
}
}, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else {
Slog.w(TAG, "addLockoutResetCallback(): Service not connected!");
}
}
private class MyHandler extends Handler {
private MyHandler(Context context) {
super(context.getMainLooper());
}
private MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_ENROLL_RESULT:
sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
break;
case MSG_ACQUIRED:
sendAcquiredResult(msg.arg1 /* acquire info */,
msg.arg2 /* vendorCode */);
break;
case MSG_AUTHENTICATION_SUCCEEDED:
sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
msg.arg2 == 1 /* isStrongBiometric */);
break;
case MSG_AUTHENTICATION_FAILED:
sendAuthenticatedFailed();
break;
case MSG_ERROR:
sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
break;
case MSG_REMOVED:
sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
break;
case MSG_CHALLENGE_GENERATED:
sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
(long) msg.obj /* challenge */);
break;
case MSG_FINGERPRINT_DETECTED:
sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
(boolean) msg.obj /* isStrongBiometric */);
break;
case MSG_UDFPS_POINTER_DOWN:
sendUdfpsPointerDown(msg.arg1 /* sensorId */);
break;
case MSG_UDFPS_POINTER_UP:
sendUdfpsPointerUp(msg.arg1 /* sensorId */);
break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
}
}
}
private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
if (mRemovalCallback == null) {
return;
}
if (mRemoveTracker == null) {
Slog.w(TAG, "Removal tracker is null");
return;
}
if (mRemoveTracker.mRemoveRequest == RemoveTracker.REMOVE_SINGLE) {
if (fingerprint == null) {
Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
return;
}
if (mRemoveTracker.mSingleFingerprint == null) {
Slog.e(TAG, "Missing fingerprint");
return;
}
final int fingerId = fingerprint.getBiometricId();
int reqFingerId = mRemoveTracker.mSingleFingerprint.getBiometricId();
if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
return;
}
}
mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
}
private void sendEnrollResult(Fingerprint fp, int remaining) {
if (mEnrollmentCallback != null) {
mEnrollmentCallback.onEnrollmentProgress(remaining);
}
}
private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
if (mAuthenticationCallback != null) {
final AuthenticationResult result =
new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
mAuthenticationCallback.onAuthenticationSucceeded(result);
}
}
private void sendAuthenticatedFailed() {
if (mAuthenticationCallback != null) {
mAuthenticationCallback.onAuthenticationFailed();
}
}
private void sendAcquiredResult(int acquireInfo, int vendorCode) {
if (mAuthenticationCallback != null) {
mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
}
final String msg = getAcquiredString(mContext, 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 != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) {
mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
}
}
}
private void sendErrorResult(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(mContext, errMsgId, vendorCode));
} else if (mAuthenticationCallback != null) {
mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
getErrorString(mContext, errMsgId, vendorCode));
} else if (mRemovalCallback != null) {
final Fingerprint fp = mRemoveTracker != null
? mRemoveTracker.mSingleFingerprint : null;
mRemovalCallback.onRemovalError(fp, clientErrMsgId,
getErrorString(mContext, errMsgId, vendorCode));
}
}
private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
if (mGenerateChallengeCallback == null) {
Slog.e(TAG, "sendChallengeGenerated, callback null");
return;
}
mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
}
private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
if (mFingerprintDetectionCallback == null) {
Slog.e(TAG, "sendFingerprintDetected, callback null");
return;
}
mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
}
private void sendUdfpsPointerDown(int sensorId) {
if (mAuthenticationCallback == null) {
Slog.e(TAG, "sendUdfpsPointerDown, callback null");
return;
}
mAuthenticationCallback.onUdfpsPointerDown(sensorId);
}
private void sendUdfpsPointerUp(int sensorId) {
if (mAuthenticationCallback == null) {
Slog.e(TAG, "sendUdfpsPointerUp, callback null");
return;
}
mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
/**
* @hide
*/
public FingerprintManager(Context context, IFingerprintService service) {
mContext = context;
mService = service;
if (mService == null) {
Slog.v(TAG, "FingerprintService was null");
}
mHandler = new MyHandler(context);
}
private int getCurrentUserId() {
try {
return ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Nullable
private FingerprintSensorPropertiesInternal getFirstFingerprintSensor() {
final List<FingerprintSensorPropertiesInternal> allSensors = getSensorPropertiesInternal();
return allSensors.isEmpty() ? null : allSensors.get(0);
}
private void cancelEnrollment() {
if (mService != null) try {
mService.cancelEnrollment(mToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
if (mService != null) try {
mService.cancelAuthentication(mToken, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private void cancelFingerprintDetect() {
if (mService == null) {
return;
}
try {
mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
public static String getErrorString(Context context, int errMsg, int vendorCode) {
switch (errMsg) {
case FINGERPRINT_ERROR_HW_UNAVAILABLE:
return context.getString(
com.android.internal.R.string.fingerprint_error_hw_not_available);
case FINGERPRINT_ERROR_UNABLE_TO_PROCESS:
return context.getString(
com.android.internal.R.string.fingerprint_error_unable_to_process);
case FINGERPRINT_ERROR_TIMEOUT:
return context.getString(com.android.internal.R.string.fingerprint_error_timeout);
case FINGERPRINT_ERROR_NO_SPACE:
return context.getString(
com.android.internal.R.string.fingerprint_error_no_space);
case FINGERPRINT_ERROR_CANCELED:
return context.getString(com.android.internal.R.string.fingerprint_error_canceled);
case FINGERPRINT_ERROR_LOCKOUT:
return context.getString(com.android.internal.R.string.fingerprint_error_lockout);
case FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
return context.getString(
com.android.internal.R.string.fingerprint_error_lockout_permanent);
case FINGERPRINT_ERROR_USER_CANCELED:
return context.getString(
com.android.internal.R.string.fingerprint_error_user_canceled);
case FINGERPRINT_ERROR_NO_FINGERPRINTS:
return context.getString(
com.android.internal.R.string.fingerprint_error_no_fingerprints);
case FINGERPRINT_ERROR_HW_NOT_PRESENT:
return context.getString(
com.android.internal.R.string.fingerprint_error_hw_not_present);
case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
return context.getString(
com.android.internal.R.string.fingerprint_error_security_update_required);
case FINGERPRINT_ERROR_BAD_CALIBRATION:
return context.getString(
com.android.internal.R.string.fingerprint_error_bad_calibration);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);
if (vendorCode < msgArray.length) {
return msgArray[vendorCode];
}
}
}
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FINGERPRINT_ERROR_VENDOR, but
// warn and use the default if all else fails.
// TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
return "";
}
/**
* @hide
*/
public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
switch (acquireInfo) {
case FINGERPRINT_ACQUIRED_GOOD:
return null;
case FINGERPRINT_ACQUIRED_PARTIAL:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_partial);
case FINGERPRINT_ACQUIRED_INSUFFICIENT:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_insufficient);
case FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_imager_dirty);
case FINGERPRINT_ACQUIRED_TOO_SLOW:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_too_slow);
case FINGERPRINT_ACQUIRED_TOO_FAST:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_too_fast);
case FINGERPRINT_ACQUIRED_IMMOBILE:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_immobile);
case FINGERPRINT_ACQUIRED_TOO_BRIGHT:
return context.getString(
com.android.internal.R.string.fingerprint_acquired_too_bright);
case FINGERPRINT_ACQUIRED_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_acquired_vendor);
if (vendorCode < msgArray.length) {
return msgArray[vendorCode];
}
}
break;
case FINGERPRINT_ACQUIRED_START:
return null;
}
Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
return null;
}
private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
@Override // binder call
public void onEnrollResult(Fingerprint fp, int remaining) {
mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, fp).sendToTarget();
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
}
@Override // binder call
public void onAuthenticationSucceeded(Fingerprint fp, int userId,
boolean isStrongBiometric) {
mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
fp).sendToTarget();
}
@Override
public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, sensorId, userId, isStrongBiometric)
.sendToTarget();
}
@Override // binder call
public void onAuthenticationFailed() {
mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
}
@Override // binder call
public void onError(int error, int vendorCode) {
mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
}
@Override // binder call
public void onRemoved(Fingerprint fp, int remaining) {
mHandler.obtainMessage(MSG_REMOVED, remaining, 0, fp).sendToTarget();
}
@Override // binder call
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
.sendToTarget();
}
@Override // binder call
public void onUdfpsPointerDown(int sensorId) {
mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget();
}
@Override // binder call
public void onUdfpsPointerUp(int sensorId) {
mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
}
};
}