1 /** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.fingerprint; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS; 20 import static android.Manifest.permission.MANAGE_FINGERPRINT; 21 import static android.Manifest.permission.USE_BIOMETRIC; 22 import static android.Manifest.permission.USE_FINGERPRINT; 23 24 import android.annotation.CallbackExecutor; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresFeature; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SystemService; 30 import android.app.ActivityManager; 31 import android.content.Context; 32 import android.content.pm.PackageManager; 33 import android.hardware.biometrics.BiometricAuthenticator; 34 import android.hardware.biometrics.BiometricFingerprintConstants; 35 import android.hardware.biometrics.BiometricPrompt; 36 import android.hardware.biometrics.IBiometricPromptReceiver; 37 import android.os.Binder; 38 import android.os.Bundle; 39 import android.os.CancellationSignal; 40 import android.os.CancellationSignal.OnCancelListener; 41 import android.os.Handler; 42 import android.os.IBinder; 43 import android.os.IRemoteCallback; 44 import android.os.Looper; 45 import android.os.PowerManager; 46 import android.os.RemoteException; 47 import android.os.UserHandle; 48 import android.util.Slog; 49 50 import java.security.Signature; 51 import java.util.List; 52 import java.util.concurrent.Executor; 53 54 import javax.crypto.Cipher; 55 import javax.crypto.Mac; 56 57 /** 58 * A class that coordinates access to the fingerprint hardware. 59 * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting 60 * authentication. In a world where devices may have different types of biometric authentication, 61 * it's much more realistic to have a system-provided authentication dialog since the method may 62 * vary by vendor/device. 63 */ 64 @Deprecated 65 @SystemService(Context.FINGERPRINT_SERVICE) 66 @RequiresFeature(PackageManager.FEATURE_FINGERPRINT) 67 public class FingerprintManager implements BiometricFingerprintConstants { 68 private static final String TAG = "FingerprintManager"; 69 private static final boolean DEBUG = true; 70 private static final int MSG_ENROLL_RESULT = 100; 71 private static final int MSG_ACQUIRED = 101; 72 private static final int MSG_AUTHENTICATION_SUCCEEDED = 102; 73 private static final int MSG_AUTHENTICATION_FAILED = 103; 74 private static final int MSG_ERROR = 104; 75 private static final int MSG_REMOVED = 105; 76 private static final int MSG_ENUMERATED = 106; 77 78 private IFingerprintService mService; 79 private Context mContext; 80 private IBinder mToken = new Binder(); 81 private BiometricAuthenticator.AuthenticationCallback mAuthenticationCallback; 82 private EnrollmentCallback mEnrollmentCallback; 83 private RemovalCallback mRemovalCallback; 84 private EnumerateCallback mEnumerateCallback; 85 private android.hardware.biometrics.CryptoObject mCryptoObject; 86 private Fingerprint mRemovalFingerprint; 87 private Handler mHandler; 88 private Executor mExecutor; 89 90 private class OnEnrollCancelListener implements OnCancelListener { 91 @Override onCancel()92 public void onCancel() { 93 cancelEnrollment(); 94 } 95 } 96 97 private class OnAuthenticationCancelListener implements OnCancelListener { 98 private android.hardware.biometrics.CryptoObject mCrypto; 99 OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto)100 public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) { 101 mCrypto = crypto; 102 } 103 104 @Override onCancel()105 public void onCancel() { 106 cancelAuthentication(mCrypto); 107 } 108 } 109 110 /** 111 * A wrapper class for the crypto objects supported by FingerprintManager. Currently the 112 * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. 113 * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} 114 */ 115 @Deprecated 116 public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { CryptoObject(@onNull Signature signature)117 public CryptoObject(@NonNull Signature signature) { 118 super(signature); 119 } 120 CryptoObject(@onNull Cipher cipher)121 public CryptoObject(@NonNull Cipher cipher) { 122 super(cipher); 123 } 124 CryptoObject(@onNull Mac mac)125 public CryptoObject(@NonNull Mac mac) { 126 super(mac); 127 } 128 129 /** 130 * Get {@link Signature} object. 131 * @return {@link Signature} object or null if this doesn't contain one. 132 */ getSignature()133 public Signature getSignature() { 134 return super.getSignature(); 135 } 136 137 /** 138 * Get {@link Cipher} object. 139 * @return {@link Cipher} object or null if this doesn't contain one. 140 */ getCipher()141 public Cipher getCipher() { 142 return super.getCipher(); 143 } 144 145 /** 146 * Get {@link Mac} object. 147 * @return {@link Mac} object or null if this doesn't contain one. 148 */ getMac()149 public Mac getMac() { 150 return super.getMac(); 151 } 152 } 153 154 /** 155 * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, 156 * CancellationSignal, int, AuthenticationCallback, Handler)}. 157 * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult} 158 */ 159 @Deprecated 160 public static class AuthenticationResult { 161 private Fingerprint mFingerprint; 162 private CryptoObject mCryptoObject; 163 private int mUserId; 164 165 /** 166 * Authentication result 167 * 168 * @param crypto the crypto object 169 * @param fingerprint the recognized fingerprint data, if allowed. 170 * @hide 171 */ AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId)172 public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) { 173 mCryptoObject = crypto; 174 mFingerprint = fingerprint; 175 mUserId = userId; 176 } 177 178 /** 179 * Obtain the crypto object associated with this transaction 180 * @return crypto object provided to {@link FingerprintManager#authenticate(CryptoObject, 181 * CancellationSignal, int, AuthenticationCallback, Handler)}. 182 */ getCryptoObject()183 public CryptoObject getCryptoObject() { return mCryptoObject; } 184 185 /** 186 * Obtain the Fingerprint associated with this operation. Applications are strongly 187 * discouraged from associating specific fingers with specific applications or operations. 188 * 189 * @hide 190 */ getFingerprint()191 public Fingerprint getFingerprint() { return mFingerprint; } 192 193 /** 194 * Obtain the userId for which this fingerprint was authenticated. 195 * @hide 196 */ getUserId()197 public int getUserId() { return mUserId; } 198 }; 199 200 /** 201 * Callback structure provided to {@link FingerprintManager#authenticate(CryptoObject, 202 * CancellationSignal, int, AuthenticationCallback, Handler)}. Users of {@link 203 * FingerprintManager#authenticate(CryptoObject, CancellationSignal, 204 * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to 205 * fingerprint events. 206 * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} 207 */ 208 @Deprecated 209 public static abstract class AuthenticationCallback 210 extends BiometricAuthenticator.AuthenticationCallback { 211 /** 212 * Called when an unrecoverable error has been encountered and the operation is complete. 213 * No further callbacks will be made on this object. 214 * @param errorCode An integer identifying the error message 215 * @param errString A human-readable error string that can be shown in UI 216 */ 217 @Override onAuthenticationError(int errorCode, CharSequence errString)218 public void onAuthenticationError(int errorCode, CharSequence errString) { } 219 220 /** 221 * Called when a recoverable error has been encountered during authentication. The help 222 * string is provided to give the user guidance for what went wrong, such as 223 * "Sensor dirty, please clean it." 224 * @param helpCode An integer identifying the error message 225 * @param helpString A human-readable string that can be shown in UI 226 */ 227 @Override onAuthenticationHelp(int helpCode, CharSequence helpString)228 public void onAuthenticationHelp(int helpCode, CharSequence helpString) { } 229 230 /** 231 * Called when a fingerprint is recognized. 232 * @param result An object containing authentication-related data 233 */ onAuthenticationSucceeded(AuthenticationResult result)234 public void onAuthenticationSucceeded(AuthenticationResult result) { } 235 236 /** 237 * Called when a fingerprint is valid but not recognized. 238 */ 239 @Override onAuthenticationFailed()240 public void onAuthenticationFailed() { } 241 242 /** 243 * Called when a fingerprint image has been acquired, but wasn't processed yet. 244 * 245 * @param acquireInfo one of FINGERPRINT_ACQUIRED_* constants 246 * @hide 247 */ 248 @Override onAuthenticationAcquired(int acquireInfo)249 public void onAuthenticationAcquired(int acquireInfo) {} 250 251 /** 252 * @hide 253 * @param result 254 */ 255 @Override onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result)256 public void onAuthenticationSucceeded(BiometricAuthenticator.AuthenticationResult result) { 257 onAuthenticationSucceeded(new AuthenticationResult( 258 (CryptoObject) result.getCryptoObject(), 259 (Fingerprint) result.getId(), result.getUserId())); 260 } 261 }; 262 263 /** 264 * Callback structure provided to {@link FingerprintManager#enroll(long, EnrollmentCallback, 265 * CancellationSignal, int). Users of {@link #FingerprintManager()} 266 * must provide an implementation of this to {@link FingerprintManager#enroll(long, 267 * CancellationSignal, int, EnrollmentCallback) for listening to fingerprint events. 268 * 269 * @hide 270 */ 271 public static abstract class EnrollmentCallback { 272 /** 273 * Called when an unrecoverable error has been encountered and the operation is complete. 274 * No further callbacks will be made on this object. 275 * @param errMsgId An integer identifying the error message 276 * @param errString A human-readable error string that can be shown in UI 277 */ onEnrollmentError(int errMsgId, CharSequence errString)278 public void onEnrollmentError(int errMsgId, CharSequence errString) { } 279 280 /** 281 * Called when a recoverable error has been encountered during enrollment. The help 282 * string is provided to give the user guidance for what went wrong, such as 283 * "Sensor dirty, please clean it" or what they need to do next, such as 284 * "Touch sensor again." 285 * @param helpMsgId An integer identifying the error message 286 * @param helpString A human-readable string that can be shown in UI 287 */ onEnrollmentHelp(int helpMsgId, CharSequence helpString)288 public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) { } 289 290 /** 291 * Called as each enrollment step progresses. Enrollment is considered complete when 292 * remaining reaches 0. This function will not be called if enrollment fails. See 293 * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} 294 * @param remaining The number of remaining steps 295 */ onEnrollmentProgress(int remaining)296 public void onEnrollmentProgress(int remaining) { } 297 }; 298 299 /** 300 * Callback structure provided to {@link #remove}. Users of {@link FingerprintManager} may 301 * optionally provide an implementation of this to 302 * {@link #remove(Fingerprint, int, RemovalCallback)} for listening to fingerprint template 303 * removal events. 304 * 305 * @hide 306 */ 307 public static abstract class RemovalCallback { 308 /** 309 * Called when the given fingerprint can't be removed. 310 * @param fp The fingerprint that the call attempted to remove 311 * @param errMsgId An associated error message id 312 * @param errString An error message indicating why the fingerprint id can't be removed 313 */ onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString)314 public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { } 315 316 /** 317 * Called when a given fingerprint is successfully removed. 318 * @param fp The fingerprint template that was removed. 319 * @param remaining The number of fingerprints yet to be removed in this operation. If 320 * {@link #remove} is called on one fingerprint, this should be 0. If 321 * {@link #remove} is called on a group, this should be the number of remaining 322 * fingerprints in the group, and 0 after the last fingerprint is removed. 323 */ onRemovalSucceeded(Fingerprint fp, int remaining)324 public void onRemovalSucceeded(Fingerprint fp, int remaining) { } 325 }; 326 327 /** 328 * Callback structure provided to {@link FingerprintManager#enumerate(int). Users of 329 * {@link #FingerprintManager()} may optionally provide an implementation of this to 330 * {@link FingerprintManager#enumerate(int, int, EnumerateCallback)} for listening to 331 * fingerprint template removal events. 332 * 333 * @hide 334 */ 335 public static abstract class EnumerateCallback { 336 /** 337 * Called when the given fingerprint can't be removed. 338 * @param errMsgId An associated error message id 339 * @param errString An error message indicating why the fingerprint id can't be removed 340 */ onEnumerateError(int errMsgId, CharSequence errString)341 public void onEnumerateError(int errMsgId, CharSequence errString) { } 342 343 /** 344 * Called when a given fingerprint is successfully removed. 345 * @param fingerprint the fingerprint template that was removed. 346 */ onEnumerate(Fingerprint fingerprint)347 public void onEnumerate(Fingerprint fingerprint) { } 348 }; 349 350 /** 351 * @hide 352 */ 353 public static abstract class LockoutResetCallback { 354 355 /** 356 * Called when lockout period expired and clients are allowed to listen for fingerprint 357 * again. 358 */ onLockoutReset()359 public void onLockoutReset() { } 360 }; 361 362 /** 363 * Request authentication of a crypto object. This call warms up the fingerprint hardware 364 * and starts scanning for a fingerprint. It terminates when 365 * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or 366 * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at 367 * which point the object is no longer valid. The operation can be canceled by using the 368 * provided cancel object. 369 * 370 * @param crypto object associated with the call or null if none required. 371 * @param cancel an object that can be used to cancel authentication 372 * @param flags optional flags; should be 0 373 * @param callback an object to receive authentication events 374 * @param handler an optional handler to handle callback events 375 * 376 * @throws IllegalArgumentException if the crypto operation is not supported or is not backed 377 * by <a href="{@docRoot}training/articles/keystore.html">Android Keystore 378 * facility</a>. 379 * @throws IllegalStateException if the crypto primitive is not initialized. 380 * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor, 381 * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate( 382 * BiometricPrompt.CryptoObject, CancellationSignal, Executor, 383 * BiometricPrompt.AuthenticationCallback)} 384 */ 385 @Deprecated 386 @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) authenticate(@ullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler)387 public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, 388 int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { 389 authenticate(crypto, cancel, flags, callback, handler, mContext.getUserId()); 390 } 391 392 /** 393 * Use the provided handler thread for events. 394 * @param handler 395 */ useHandler(Handler handler)396 private void useHandler(Handler handler) { 397 if (handler != null) { 398 mHandler = new MyHandler(handler.getLooper()); 399 } else if (mHandler.getLooper() != mContext.getMainLooper()){ 400 mHandler = new MyHandler(mContext.getMainLooper()); 401 } 402 } 403 404 /** 405 * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, 406 * CancellationSignal, int, AuthenticationCallback, Handler)} 407 * @param userId the user ID that the fingerprint hardware will authenticate for. 408 * @hide 409 */ 410 @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) authenticate(@ullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId)411 public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, 412 int flags, @NonNull AuthenticationCallback callback, Handler handler, int userId) { 413 if (callback == null) { 414 throw new IllegalArgumentException("Must supply an authentication callback"); 415 } 416 417 if (cancel != null) { 418 if (cancel.isCanceled()) { 419 Slog.w(TAG, "authentication already canceled"); 420 return; 421 } else { 422 cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto)); 423 } 424 } 425 426 if (mService != null) try { 427 useHandler(handler); 428 mAuthenticationCallback = callback; 429 mCryptoObject = crypto; 430 long sessionId = crypto != null ? crypto.getOpId() : 0; 431 mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, 432 mContext.getOpPackageName(), null /* bundle */, null /* receiver */); 433 } catch (RemoteException e) { 434 Slog.w(TAG, "Remote exception while authenticating: ", e); 435 if (callback != null) { 436 // Though this may not be a hardware issue, it will cause apps to give up or try 437 // again later. 438 callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 439 getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); 440 } 441 } 442 } 443 444 /** 445 * Per-user version, see {@link FingerprintManager#authenticate(CryptoObject, 446 * CancellationSignal, Bundle, Executor, IBiometricPromptReceiver, AuthenticationCallback)} 447 * @param userId the user ID that the fingerprint hardware will authenticate for. 448 */ authenticate(int userId, @Nullable android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver, @NonNull BiometricAuthenticator.AuthenticationCallback callback)449 private void authenticate(int userId, 450 @Nullable android.hardware.biometrics.CryptoObject crypto, 451 @NonNull CancellationSignal cancel, 452 @NonNull Bundle bundle, 453 @NonNull @CallbackExecutor Executor executor, 454 @NonNull IBiometricPromptReceiver receiver, 455 @NonNull BiometricAuthenticator.AuthenticationCallback callback) { 456 mCryptoObject = crypto; 457 if (cancel.isCanceled()) { 458 Slog.w(TAG, "authentication already canceled"); 459 return; 460 } else { 461 cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto)); 462 } 463 464 if (mService != null) { 465 try { 466 mExecutor = executor; 467 mAuthenticationCallback = callback; 468 final long sessionId = crypto != null ? crypto.getOpId() : 0; 469 mService.authenticate(mToken, sessionId, userId, mServiceReceiver, 470 0 /* flags */, mContext.getOpPackageName(), bundle, receiver); 471 } catch (RemoteException e) { 472 Slog.w(TAG, "Remote exception while authenticating", e); 473 mExecutor.execute(() -> { 474 callback.onAuthenticationError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 475 getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); 476 }); 477 } 478 } 479 } 480 481 /** 482 * Private method, see {@link BiometricPrompt#authenticate(CancellationSignal, Executor, 483 * BiometricPrompt.AuthenticationCallback)} 484 * @param cancel 485 * @param executor 486 * @param callback 487 * @hide 488 */ authenticate( @onNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver, @NonNull BiometricAuthenticator.AuthenticationCallback callback)489 public void authenticate( 490 @NonNull CancellationSignal cancel, 491 @NonNull Bundle bundle, 492 @NonNull @CallbackExecutor Executor executor, 493 @NonNull IBiometricPromptReceiver receiver, 494 @NonNull BiometricAuthenticator.AuthenticationCallback callback) { 495 if (cancel == null) { 496 throw new IllegalArgumentException("Must supply a cancellation signal"); 497 } 498 if (bundle == null) { 499 throw new IllegalArgumentException("Must supply a bundle"); 500 } 501 if (executor == null) { 502 throw new IllegalArgumentException("Must supply an executor"); 503 } 504 if (receiver == null) { 505 throw new IllegalArgumentException("Must supply a receiver"); 506 } 507 if (callback == null) { 508 throw new IllegalArgumentException("Must supply a calback"); 509 } 510 authenticate(mContext.getUserId(), null, cancel, bundle, executor, receiver, callback); 511 } 512 513 /** 514 * Private method, see {@link BiometricPrompt#authenticate(BiometricPrompt.CryptoObject, 515 * CancellationSignal, Executor, BiometricPrompt.AuthenticationCallback)} 516 * @param crypto 517 * @param cancel 518 * @param executor 519 * @param callback 520 * @hide 521 */ authenticate(@onNull android.hardware.biometrics.CryptoObject crypto, @NonNull CancellationSignal cancel, @NonNull Bundle bundle, @NonNull @CallbackExecutor Executor executor, @NonNull IBiometricPromptReceiver receiver, @NonNull BiometricAuthenticator.AuthenticationCallback callback)522 public void authenticate(@NonNull android.hardware.biometrics.CryptoObject crypto, 523 @NonNull CancellationSignal cancel, 524 @NonNull Bundle bundle, 525 @NonNull @CallbackExecutor Executor executor, 526 @NonNull IBiometricPromptReceiver receiver, 527 @NonNull BiometricAuthenticator.AuthenticationCallback callback) { 528 if (crypto == null) { 529 throw new IllegalArgumentException("Must supply a crypto object"); 530 } 531 if (cancel == null) { 532 throw new IllegalArgumentException("Must supply a cancellation signal"); 533 } 534 if (bundle == null) { 535 throw new IllegalArgumentException("Must supply a bundle"); 536 } 537 if (executor == null) { 538 throw new IllegalArgumentException("Must supply an executor"); 539 } 540 if (receiver == null) { 541 throw new IllegalArgumentException("Must supply a receiver"); 542 } 543 if (callback == null) { 544 throw new IllegalArgumentException("Must supply a callback"); 545 } 546 authenticate(mContext.getUserId(), crypto, cancel, 547 bundle, executor, receiver, callback); 548 } 549 550 /** 551 * Request fingerprint enrollment. This call warms up the fingerprint hardware 552 * and starts scanning for fingerprints. Progress will be indicated by callbacks to the 553 * {@link EnrollmentCallback} object. It terminates when 554 * {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)} or 555 * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at 556 * which point the object is no longer valid. The operation can be canceled by using the 557 * provided cancel object. 558 * @param token a unique token provided by a recent creation or verification of device 559 * credentials (e.g. pin, pattern or password). 560 * @param cancel an object that can be used to cancel enrollment 561 * @param flags optional flags 562 * @param userId the user to whom this fingerprint will belong to 563 * @param callback an object to receive enrollment events 564 * @hide 565 */ 566 @RequiresPermission(MANAGE_FINGERPRINT) enroll(byte [] token, CancellationSignal cancel, int flags, int userId, EnrollmentCallback callback)567 public void enroll(byte [] token, CancellationSignal cancel, int flags, 568 int userId, EnrollmentCallback callback) { 569 if (userId == UserHandle.USER_CURRENT) { 570 userId = getCurrentUserId(); 571 } 572 if (callback == null) { 573 throw new IllegalArgumentException("Must supply an enrollment callback"); 574 } 575 576 if (cancel != null) { 577 if (cancel.isCanceled()) { 578 Slog.w(TAG, "enrollment already canceled"); 579 return; 580 } else { 581 cancel.setOnCancelListener(new OnEnrollCancelListener()); 582 } 583 } 584 585 if (mService != null) try { 586 mEnrollmentCallback = callback; 587 mService.enroll(mToken, token, userId, mServiceReceiver, flags, 588 mContext.getOpPackageName()); 589 } catch (RemoteException e) { 590 Slog.w(TAG, "Remote exception in enroll: ", e); 591 if (callback != null) { 592 // Though this may not be a hardware issue, it will cause apps to give up or try 593 // again later. 594 callback.onEnrollmentError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 595 getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); 596 } 597 } 598 } 599 600 /** 601 * Requests a pre-enrollment auth token to tie enrollment to the confirmation of 602 * existing device credentials (e.g. pin/pattern/password). 603 * @hide 604 */ 605 @RequiresPermission(MANAGE_FINGERPRINT) preEnroll()606 public long preEnroll() { 607 long result = 0; 608 if (mService != null) try { 609 result = mService.preEnroll(mToken); 610 } catch (RemoteException e) { 611 throw e.rethrowFromSystemServer(); 612 } 613 return result; 614 } 615 616 /** 617 * Finishes enrollment and cancels the current auth token. 618 * @hide 619 */ 620 @RequiresPermission(MANAGE_FINGERPRINT) postEnroll()621 public int postEnroll() { 622 int result = 0; 623 if (mService != null) try { 624 result = mService.postEnroll(mToken); 625 } catch (RemoteException e) { 626 throw e.rethrowFromSystemServer(); 627 } 628 return result; 629 } 630 631 /** 632 * Sets the active user. This is meant to be used to select the current profile for enrollment 633 * to allow separate enrolled fingers for a work profile 634 * @param userId 635 * @hide 636 */ 637 @RequiresPermission(MANAGE_FINGERPRINT) setActiveUser(int userId)638 public void setActiveUser(int userId) { 639 if (mService != null) try { 640 mService.setActiveUser(userId); 641 } catch (RemoteException e) { 642 throw e.rethrowFromSystemServer(); 643 } 644 } 645 646 /** 647 * Remove given fingerprint template from fingerprint hardware and/or protected storage. 648 * @param fp the fingerprint item to remove 649 * @param userId the user who this fingerprint belongs to 650 * @param callback an optional callback to verify that fingerprint templates have been 651 * successfully removed. May be null of no callback is required. 652 * 653 * @hide 654 */ 655 @RequiresPermission(MANAGE_FINGERPRINT) remove(Fingerprint fp, int userId, RemovalCallback callback)656 public void remove(Fingerprint fp, int userId, RemovalCallback callback) { 657 if (mService != null) try { 658 mRemovalCallback = callback; 659 mRemovalFingerprint = fp; 660 mService.remove(mToken, fp.getFingerId(), fp.getGroupId(), userId, mServiceReceiver); 661 } catch (RemoteException e) { 662 Slog.w(TAG, "Remote exception in remove: ", e); 663 if (callback != null) { 664 callback.onRemovalError(fp, FINGERPRINT_ERROR_HW_UNAVAILABLE, 665 getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); 666 } 667 } 668 } 669 670 /** 671 * Enumerate all fingerprint templates stored in hardware and/or protected storage. 672 * @param userId the user who this fingerprint belongs to 673 * @param callback an optional callback to verify that fingerprint templates have been 674 * successfully removed. May be null of no callback is required. 675 * 676 * @hide 677 */ 678 @RequiresPermission(MANAGE_FINGERPRINT) enumerate(int userId, @NonNull EnumerateCallback callback)679 public void enumerate(int userId, @NonNull EnumerateCallback callback) { 680 if (mService != null) try { 681 mEnumerateCallback = callback; 682 mService.enumerate(mToken, userId, mServiceReceiver); 683 } catch (RemoteException e) { 684 Slog.w(TAG, "Remote exception in enumerate: ", e); 685 if (callback != null) { 686 callback.onEnumerateError(FINGERPRINT_ERROR_HW_UNAVAILABLE, 687 getErrorString(FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */)); 688 } 689 } 690 } 691 692 /** 693 * Renames the given fingerprint template 694 * @param fpId the fingerprint id 695 * @param userId the user who this fingerprint belongs to 696 * @param newName the new name 697 * 698 * @hide 699 */ 700 @RequiresPermission(MANAGE_FINGERPRINT) rename(int fpId, int userId, String newName)701 public void rename(int fpId, int userId, String newName) { 702 // Renames the given fpId 703 if (mService != null) { 704 try { 705 mService.rename(fpId, userId, newName); 706 } catch (RemoteException e) { 707 throw e.rethrowFromSystemServer(); 708 } 709 } else { 710 Slog.w(TAG, "rename(): Service not connected!"); 711 } 712 } 713 714 /** 715 * Obtain the list of enrolled fingerprints templates. 716 * @return list of current fingerprint items 717 * 718 * @hide 719 */ 720 @RequiresPermission(USE_FINGERPRINT) getEnrolledFingerprints(int userId)721 public List<Fingerprint> getEnrolledFingerprints(int userId) { 722 if (mService != null) try { 723 return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName()); 724 } catch (RemoteException e) { 725 throw e.rethrowFromSystemServer(); 726 } 727 return null; 728 } 729 730 /** 731 * Obtain the list of enrolled fingerprints templates. 732 * @return list of current fingerprint items 733 * 734 * @hide 735 */ 736 @RequiresPermission(USE_FINGERPRINT) getEnrolledFingerprints()737 public List<Fingerprint> getEnrolledFingerprints() { 738 return getEnrolledFingerprints(mContext.getUserId()); 739 } 740 741 /** 742 * Determine if there is at least one fingerprint enrolled. 743 * 744 * @return true if at least one fingerprint is enrolled, false otherwise 745 * @deprecated See {@link BiometricPrompt} and 746 * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS} 747 */ 748 @Deprecated 749 @RequiresPermission(USE_FINGERPRINT) hasEnrolledFingerprints()750 public boolean hasEnrolledFingerprints() { 751 if (mService != null) try { 752 return mService.hasEnrolledFingerprints( 753 mContext.getUserId(), mContext.getOpPackageName()); 754 } catch (RemoteException e) { 755 throw e.rethrowFromSystemServer(); 756 } 757 return false; 758 } 759 760 /** 761 * @hide 762 */ 763 @RequiresPermission(allOf = { 764 USE_FINGERPRINT, 765 INTERACT_ACROSS_USERS}) hasEnrolledFingerprints(int userId)766 public boolean hasEnrolledFingerprints(int userId) { 767 if (mService != null) try { 768 return mService.hasEnrolledFingerprints(userId, mContext.getOpPackageName()); 769 } catch (RemoteException e) { 770 throw e.rethrowFromSystemServer(); 771 } 772 return false; 773 } 774 775 /** 776 * Determine if fingerprint hardware is present and functional. 777 * 778 * @return true if hardware is present and functional, false otherwise. 779 * @deprecated See {@link BiometricPrompt} and 780 * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE} 781 */ 782 @Deprecated 783 @RequiresPermission(USE_FINGERPRINT) isHardwareDetected()784 public boolean isHardwareDetected() { 785 if (mService != null) { 786 try { 787 long deviceId = 0; /* TODO: plumb hardware id to FPMS */ 788 return mService.isHardwareDetected(deviceId, mContext.getOpPackageName()); 789 } catch (RemoteException e) { 790 throw e.rethrowFromSystemServer(); 791 } 792 } else { 793 Slog.w(TAG, "isFingerprintHardwareDetected(): Service not connected!"); 794 } 795 return false; 796 } 797 798 /** 799 * Retrieves the authenticator token for binding keys to the lifecycle 800 * of the calling user's fingerprints. Used only by internal clients. 801 * 802 * @hide 803 */ getAuthenticatorId()804 public long getAuthenticatorId() { 805 if (mService != null) { 806 try { 807 return mService.getAuthenticatorId(mContext.getOpPackageName()); 808 } catch (RemoteException e) { 809 throw e.rethrowFromSystemServer(); 810 } 811 } else { 812 Slog.w(TAG, "getAuthenticatorId(): Service not connected!"); 813 } 814 return 0; 815 } 816 817 /** 818 * Reset the lockout timer when asked to do so by keyguard. 819 * 820 * @param token an opaque token returned by password confirmation. 821 * 822 * @hide 823 */ resetTimeout(byte[] token)824 public void resetTimeout(byte[] token) { 825 if (mService != null) { 826 try { 827 mService.resetTimeout(token); 828 } catch (RemoteException e) { 829 throw e.rethrowFromSystemServer(); 830 } 831 } else { 832 Slog.w(TAG, "resetTimeout(): Service not connected!"); 833 } 834 } 835 836 /** 837 * @hide 838 */ addLockoutResetCallback(final LockoutResetCallback callback)839 public void addLockoutResetCallback(final LockoutResetCallback callback) { 840 if (mService != null) { 841 try { 842 final PowerManager powerManager = mContext.getSystemService(PowerManager.class); 843 mService.addLockoutResetCallback( 844 new IFingerprintServiceLockoutResetCallback.Stub() { 845 846 @Override 847 public void onLockoutReset(long deviceId, IRemoteCallback serverCallback) 848 throws RemoteException { 849 try { 850 final PowerManager.WakeLock wakeLock = powerManager.newWakeLock( 851 PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback"); 852 wakeLock.acquire(); 853 mHandler.post(() -> { 854 try { 855 callback.onLockoutReset(); 856 } finally { 857 wakeLock.release(); 858 } 859 }); 860 } finally { 861 serverCallback.sendResult(null /* data */); 862 } 863 } 864 }); 865 } catch (RemoteException e) { 866 throw e.rethrowFromSystemServer(); 867 } 868 } else { 869 Slog.w(TAG, "addLockoutResetCallback(): Service not connected!"); 870 } 871 } 872 873 private class MyHandler extends Handler { MyHandler(Context context)874 private MyHandler(Context context) { 875 super(context.getMainLooper()); 876 } 877 MyHandler(Looper looper)878 private MyHandler(Looper looper) { 879 super(looper); 880 } 881 882 @Override handleMessage(android.os.Message msg)883 public void handleMessage(android.os.Message msg) { 884 switch(msg.what) { 885 case MSG_ENROLL_RESULT: 886 sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */); 887 break; 888 case MSG_ACQUIRED: 889 sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */, 890 msg.arg2 /* vendorCode */); 891 break; 892 case MSG_AUTHENTICATION_SUCCEEDED: 893 sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */); 894 break; 895 case MSG_AUTHENTICATION_FAILED: 896 sendAuthenticatedFailed(); 897 break; 898 case MSG_ERROR: 899 sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */, 900 msg.arg2 /* vendorCode */); 901 break; 902 case MSG_REMOVED: 903 sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */); 904 break; 905 case MSG_ENUMERATED: 906 sendEnumeratedResult((Long) msg.obj /* deviceId */, msg.arg1 /* fingerId */, 907 msg.arg2 /* groupId */); 908 break; 909 } 910 } 911 sendRemovedResult(Fingerprint fingerprint, int remaining)912 private void sendRemovedResult(Fingerprint fingerprint, int remaining) { 913 if (mRemovalCallback == null) { 914 return; 915 } 916 if (fingerprint == null) { 917 Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null"); 918 return; 919 } 920 921 int fingerId = fingerprint.getFingerId(); 922 int reqFingerId = mRemovalFingerprint.getFingerId(); 923 if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) { 924 Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId); 925 return; 926 } 927 int groupId = fingerprint.getGroupId(); 928 int reqGroupId = mRemovalFingerprint.getGroupId(); 929 if (groupId != reqGroupId) { 930 Slog.w(TAG, "Group id didn't match: " + groupId + " != " + reqGroupId); 931 return; 932 } 933 934 mRemovalCallback.onRemovalSucceeded(fingerprint, remaining); 935 } 936 sendEnumeratedResult(long deviceId, int fingerId, int groupId)937 private void sendEnumeratedResult(long deviceId, int fingerId, int groupId) { 938 if (mEnumerateCallback != null) { 939 mEnumerateCallback.onEnumerate(new Fingerprint(null, groupId, fingerId, deviceId)); 940 } 941 } 942 sendEnrollResult(Fingerprint fp, int remaining)943 private void sendEnrollResult(Fingerprint fp, int remaining) { 944 if (mEnrollmentCallback != null) { 945 mEnrollmentCallback.onEnrollmentProgress(remaining); 946 } 947 } 948 }; 949 sendAuthenticatedSucceeded(Fingerprint fp, int userId)950 private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) { 951 if (mAuthenticationCallback != null) { 952 final BiometricAuthenticator.AuthenticationResult result = 953 new BiometricAuthenticator.AuthenticationResult(mCryptoObject, fp, userId); 954 mAuthenticationCallback.onAuthenticationSucceeded(result); 955 } 956 } 957 sendAuthenticatedFailed()958 private void sendAuthenticatedFailed() { 959 if (mAuthenticationCallback != null) { 960 mAuthenticationCallback.onAuthenticationFailed(); 961 } 962 } 963 sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode)964 private void sendAcquiredResult(long deviceId, int acquireInfo, int vendorCode) { 965 if (mAuthenticationCallback != null) { 966 mAuthenticationCallback.onAuthenticationAcquired(acquireInfo); 967 } 968 final String msg = getAcquiredString(acquireInfo, vendorCode); 969 if (msg == null) { 970 return; 971 } 972 // emulate HAL 2.1 behavior and send real acquiredInfo 973 final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR 974 ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo; 975 if (mEnrollmentCallback != null) { 976 mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); 977 } else if (mAuthenticationCallback != null) { 978 mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); 979 } 980 } 981 sendErrorResult(long deviceId, int errMsgId, int vendorCode)982 private void sendErrorResult(long deviceId, int errMsgId, int vendorCode) { 983 // emulate HAL 2.1 behavior and send real errMsgId 984 final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR 985 ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId; 986 if (mEnrollmentCallback != null) { 987 mEnrollmentCallback.onEnrollmentError(clientErrMsgId, 988 getErrorString(errMsgId, vendorCode)); 989 } else if (mAuthenticationCallback != null) { 990 mAuthenticationCallback.onAuthenticationError(clientErrMsgId, 991 getErrorString(errMsgId, vendorCode)); 992 } else if (mRemovalCallback != null) { 993 mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId, 994 getErrorString(errMsgId, vendorCode)); 995 } else if (mEnumerateCallback != null) { 996 mEnumerateCallback.onEnumerateError(clientErrMsgId, 997 getErrorString(errMsgId, vendorCode)); 998 } 999 } 1000 1001 /** 1002 * @hide 1003 */ FingerprintManager(Context context, IFingerprintService service)1004 public FingerprintManager(Context context, IFingerprintService service) { 1005 mContext = context; 1006 mService = service; 1007 if (mService == null) { 1008 Slog.v(TAG, "FingerprintManagerService was null"); 1009 } 1010 mHandler = new MyHandler(context); 1011 } 1012 getCurrentUserId()1013 private int getCurrentUserId() { 1014 try { 1015 return ActivityManager.getService().getCurrentUser().id; 1016 } catch (RemoteException e) { 1017 throw e.rethrowFromSystemServer(); 1018 } 1019 } 1020 cancelEnrollment()1021 private void cancelEnrollment() { 1022 if (mService != null) try { 1023 mService.cancelEnrollment(mToken); 1024 } catch (RemoteException e) { 1025 throw e.rethrowFromSystemServer(); 1026 } 1027 } 1028 cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject)1029 private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) { 1030 if (mService != null) try { 1031 mService.cancelAuthentication(mToken, mContext.getOpPackageName()); 1032 } catch (RemoteException e) { 1033 throw e.rethrowFromSystemServer(); 1034 } 1035 } 1036 1037 /** 1038 * @hide 1039 */ getErrorString(int errMsg, int vendorCode)1040 public String getErrorString(int errMsg, int vendorCode) { 1041 switch (errMsg) { 1042 case FINGERPRINT_ERROR_UNABLE_TO_PROCESS: 1043 return mContext.getString( 1044 com.android.internal.R.string.fingerprint_error_unable_to_process); 1045 case FINGERPRINT_ERROR_HW_UNAVAILABLE: 1046 return mContext.getString( 1047 com.android.internal.R.string.fingerprint_error_hw_not_available); 1048 case FINGERPRINT_ERROR_NO_SPACE: 1049 return mContext.getString( 1050 com.android.internal.R.string.fingerprint_error_no_space); 1051 case FINGERPRINT_ERROR_TIMEOUT: 1052 return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout); 1053 case FINGERPRINT_ERROR_CANCELED: 1054 return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled); 1055 case FINGERPRINT_ERROR_LOCKOUT: 1056 return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout); 1057 case FINGERPRINT_ERROR_LOCKOUT_PERMANENT: 1058 return mContext.getString( 1059 com.android.internal.R.string.fingerprint_error_lockout_permanent); 1060 case FINGERPRINT_ERROR_USER_CANCELED: 1061 return mContext.getString( 1062 com.android.internal.R.string.fingerprint_error_user_canceled); 1063 case FINGERPRINT_ERROR_NO_FINGERPRINTS: 1064 return mContext.getString( 1065 com.android.internal.R.string.fingerprint_error_no_fingerprints); 1066 case FINGERPRINT_ERROR_HW_NOT_PRESENT: 1067 return mContext.getString( 1068 com.android.internal.R.string.fingerprint_error_hw_not_present); 1069 case FINGERPRINT_ERROR_VENDOR: { 1070 String[] msgArray = mContext.getResources().getStringArray( 1071 com.android.internal.R.array.fingerprint_error_vendor); 1072 if (vendorCode < msgArray.length) { 1073 return msgArray[vendorCode]; 1074 } 1075 } 1076 } 1077 Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode); 1078 return null; 1079 } 1080 1081 /** 1082 * @hide 1083 */ getAcquiredString(int acquireInfo, int vendorCode)1084 public String getAcquiredString(int acquireInfo, int vendorCode) { 1085 switch (acquireInfo) { 1086 case FINGERPRINT_ACQUIRED_GOOD: 1087 return null; 1088 case FINGERPRINT_ACQUIRED_PARTIAL: 1089 return mContext.getString( 1090 com.android.internal.R.string.fingerprint_acquired_partial); 1091 case FINGERPRINT_ACQUIRED_INSUFFICIENT: 1092 return mContext.getString( 1093 com.android.internal.R.string.fingerprint_acquired_insufficient); 1094 case FINGERPRINT_ACQUIRED_IMAGER_DIRTY: 1095 return mContext.getString( 1096 com.android.internal.R.string.fingerprint_acquired_imager_dirty); 1097 case FINGERPRINT_ACQUIRED_TOO_SLOW: 1098 return mContext.getString( 1099 com.android.internal.R.string.fingerprint_acquired_too_slow); 1100 case FINGERPRINT_ACQUIRED_TOO_FAST: 1101 return mContext.getString( 1102 com.android.internal.R.string.fingerprint_acquired_too_fast); 1103 case FINGERPRINT_ACQUIRED_VENDOR: { 1104 String[] msgArray = mContext.getResources().getStringArray( 1105 com.android.internal.R.array.fingerprint_acquired_vendor); 1106 if (vendorCode < msgArray.length) { 1107 return msgArray[vendorCode]; 1108 } 1109 } 1110 } 1111 Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode); 1112 return null; 1113 } 1114 1115 private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() { 1116 1117 @Override // binder call 1118 public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { 1119 mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, 1120 new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget(); 1121 } 1122 1123 @Override // binder call 1124 public void onAcquired(long deviceId, int acquireInfo, int vendorCode) { 1125 if (mExecutor != null) { 1126 mExecutor.execute(() -> { 1127 sendAcquiredResult(deviceId, acquireInfo, vendorCode); 1128 }); 1129 } else { 1130 mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode, 1131 deviceId).sendToTarget(); 1132 } 1133 } 1134 1135 @Override // binder call 1136 public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) { 1137 if (mExecutor != null) { 1138 mExecutor.execute(() -> { 1139 sendAuthenticatedSucceeded(fp, userId); 1140 }); 1141 } else { 1142 mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget(); 1143 } 1144 } 1145 1146 @Override // binder call 1147 public void onAuthenticationFailed(long deviceId) { 1148 if (mExecutor != null) { 1149 mExecutor.execute(() -> { 1150 sendAuthenticatedFailed(); 1151 }); 1152 } else { 1153 mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget(); 1154 } 1155 } 1156 1157 @Override // binder call 1158 public void onError(long deviceId, int error, int vendorCode) { 1159 if (mExecutor != null) { 1160 // BiometricPrompt case 1161 if (error == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED 1162 || error == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { 1163 // User tapped somewhere to cancel, or authentication was cancelled by the app 1164 // or got kicked out. The prompt is already gone, so send the error immediately. 1165 mExecutor.execute(() -> { 1166 sendErrorResult(deviceId, error, vendorCode); 1167 }); 1168 } else { 1169 // User got an error that needs to be displayed on the dialog, post a delayed 1170 // runnable on the FingerprintManager handler that sends the error message after 1171 // FingerprintDialog.HIDE_DIALOG_DELAY to send the error to the application. 1172 mHandler.postDelayed(() -> { 1173 mExecutor.execute(() -> { 1174 sendErrorResult(deviceId, error, vendorCode); 1175 }); 1176 }, BiometricPrompt.HIDE_DIALOG_DELAY); 1177 } 1178 } else { 1179 mHandler.obtainMessage(MSG_ERROR, error, vendorCode, deviceId).sendToTarget(); 1180 } 1181 } 1182 1183 @Override // binder call 1184 public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) { 1185 mHandler.obtainMessage(MSG_REMOVED, remaining, 0, 1186 new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget(); 1187 } 1188 1189 @Override // binder call 1190 public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) { 1191 // TODO: propagate remaining 1192 mHandler.obtainMessage(MSG_ENUMERATED, fingerId, groupId, deviceId).sendToTarget(); 1193 } 1194 }; 1195 1196 } 1197