Home | History | Annotate | Download | only in fingerprint
      1 /**
      2  * Copyright (C) 2016 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 com.android.server.fingerprint;
     18 
     19 import android.content.Context;
     20 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
     21 import android.hardware.biometrics.BiometricPrompt;
     22 import android.hardware.biometrics.IBiometricPromptReceiver;
     23 import android.hardware.fingerprint.Fingerprint;
     24 import android.hardware.fingerprint.FingerprintManager;
     25 import android.hardware.fingerprint.IFingerprintServiceReceiver;
     26 import android.os.Bundle;
     27 import android.os.IBinder;
     28 import android.os.RemoteException;
     29 import android.util.Slog;
     30 
     31 import com.android.internal.logging.MetricsLogger;
     32 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     33 import com.android.internal.statusbar.IStatusBarService;
     34 
     35 /**
     36  * A class to keep track of the authentication state for a given client.
     37  */
     38 public abstract class AuthenticationClient extends ClientMonitor {
     39     private long mOpId;
     40 
     41     public abstract int handleFailedAttempt();
     42     public abstract void resetFailedAttempts();
     43 
     44     public static final int LOCKOUT_NONE = 0;
     45     public static final int LOCKOUT_TIMED = 1;
     46     public static final int LOCKOUT_PERMANENT = 2;
     47 
     48     // Callback mechanism received from the client
     49     // (BiometricPrompt -> FingerprintManager -> FingerprintService -> AuthenticationClient)
     50     private IBiometricPromptReceiver mDialogReceiverFromClient;
     51     private Bundle mBundle;
     52     private IStatusBarService mStatusBarService;
     53     private boolean mInLockout;
     54     private final FingerprintManager mFingerprintManager;
     55     protected boolean mDialogDismissed;
     56 
     57     // Receives events from SystemUI and handles them before forwarding them to FingerprintDialog
     58     protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
     59         @Override // binder call
     60         public void onDialogDismissed(int reason) {
     61             if (mBundle != null && mDialogReceiverFromClient != null) {
     62                 try {
     63                     mDialogReceiverFromClient.onDialogDismissed(reason);
     64                     if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
     65                         onError(FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED,
     66                                 0 /* vendorCode */);
     67                     }
     68                     mDialogDismissed = true;
     69                 } catch (RemoteException e) {
     70                     Slog.e(TAG, "Unable to notify dialog dismissed", e);
     71                 }
     72                 stop(true /* initiatedByClient */);
     73             }
     74         }
     75     };
     76 
     77     /**
     78      * This method is called when authentication starts.
     79      */
     80     public abstract void onStart();
     81 
     82     /**
     83      * This method is called when a fingerprint is authenticated or authentication is stopped
     84      * (cancelled by the user, or an error such as lockout has occurred).
     85      */
     86     public abstract void onStop();
     87 
     88     public AuthenticationClient(Context context, long halDeviceId, IBinder token,
     89             IFingerprintServiceReceiver receiver, int targetUserId, int groupId, long opId,
     90             boolean restricted, String owner, Bundle bundle,
     91             IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService) {
     92         super(context, halDeviceId, token, receiver, targetUserId, groupId, restricted, owner);
     93         mOpId = opId;
     94         mBundle = bundle;
     95         mDialogReceiverFromClient = dialogReceiver;
     96         mStatusBarService = statusBarService;
     97         mFingerprintManager = (FingerprintManager) getContext()
     98                 .getSystemService(Context.FINGERPRINT_SERVICE);
     99     }
    100 
    101     @Override
    102     public void binderDied() {
    103         super.binderDied();
    104         // When the binder dies, we should stop the client. This probably belongs in
    105         // ClientMonitor's binderDied(), but testing all the cases would be tricky.
    106         // AuthenticationClient is the most user-visible case.
    107         stop(false /* initiatedByClient */);
    108     }
    109 
    110     @Override
    111     public boolean onAcquired(int acquiredInfo, int vendorCode) {
    112         // If the dialog is showing, the client doesn't need to receive onAcquired messages.
    113         if (mBundle != null) {
    114             try {
    115                 if (acquiredInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
    116                     mStatusBarService.onFingerprintHelp(
    117                             mFingerprintManager.getAcquiredString(acquiredInfo, vendorCode));
    118                 }
    119                 return false; // acquisition continues
    120             } catch (RemoteException e) {
    121                 Slog.e(TAG, "Remote exception when sending acquired message", e);
    122                 return true; // client failed
    123             } finally {
    124                 // Good scans will keep the device awake
    125                 if (acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
    126                     notifyUserActivity();
    127                 }
    128             }
    129         } else {
    130             return super.onAcquired(acquiredInfo, vendorCode);
    131         }
    132     }
    133 
    134     @Override
    135     public boolean onError(int error, int vendorCode) {
    136         if (mDialogDismissed) {
    137             // If user cancels authentication, the application has already received the
    138             // FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED message from onDialogDismissed()
    139             // and stopped the fingerprint hardware, so there is no need to send a
    140             // FingerprintManager.FINGERPRINT_ERROR_CANCELED message.
    141             return true;
    142         }
    143         if (mBundle != null) {
    144             try {
    145                 mStatusBarService.onFingerprintError(
    146                         mFingerprintManager.getErrorString(error, vendorCode));
    147             } catch (RemoteException e) {
    148                 Slog.e(TAG, "Remote exception when sending error", e);
    149             }
    150         }
    151         return super.onError(error, vendorCode);
    152     }
    153 
    154     @Override
    155     public boolean onAuthenticated(int fingerId, int groupId) {
    156         boolean result = false;
    157         boolean authenticated = fingerId != 0;
    158 
    159         // If the fingerprint dialog is showing, notify authentication succeeded
    160         if (mBundle != null) {
    161             try {
    162                 if (authenticated) {
    163                     mStatusBarService.onFingerprintAuthenticated();
    164                 } else {
    165                     mStatusBarService.onFingerprintHelp(getContext().getResources().getString(
    166                             com.android.internal.R.string.fingerprint_not_recognized));
    167                 }
    168             } catch (RemoteException e) {
    169                 Slog.e(TAG, "Failed to notify Authenticated:", e);
    170             }
    171         }
    172 
    173         IFingerprintServiceReceiver receiver = getReceiver();
    174         if (receiver != null) {
    175             try {
    176                 MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_AUTH,
    177                         authenticated);
    178                 if (!authenticated) {
    179                     receiver.onAuthenticationFailed(getHalDeviceId());
    180                 } else {
    181                     if (DEBUG) {
    182                         Slog.v(TAG, "onAuthenticated(owner=" + getOwnerString()
    183                                 + ", id=" + fingerId + ", gp=" + groupId + ")");
    184                     }
    185                     Fingerprint fp = !getIsRestricted()
    186                             ? new Fingerprint("" /* TODO */, groupId, fingerId, getHalDeviceId())
    187                             : null;
    188                     receiver.onAuthenticationSucceeded(getHalDeviceId(), fp, getTargetUserId());
    189                 }
    190             } catch (RemoteException e) {
    191                 Slog.w(TAG, "Failed to notify Authenticated:", e);
    192                 result = true; // client failed
    193             }
    194         } else {
    195             result = true; // client not listening
    196         }
    197         if (!authenticated) {
    198             if (receiver != null) {
    199                 vibrateError();
    200             }
    201             // allow system-defined limit of number of attempts before giving up
    202             int lockoutMode =  handleFailedAttempt();
    203             if (lockoutMode != LOCKOUT_NONE) {
    204                 try {
    205                     mInLockout = true;
    206                     Slog.w(TAG, "Forcing lockout (fp driver code should do this!), mode(" +
    207                             lockoutMode + ")");
    208                     stop(false);
    209                     int errorCode = lockoutMode == LOCKOUT_TIMED ?
    210                             FingerprintManager.FINGERPRINT_ERROR_LOCKOUT :
    211                             FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
    212 
    213                     // TODO: if the dialog is showing, this error should be delayed. On a similar
    214                     // note, AuthenticationClient should override onError and delay all other errors
    215                     // as well, if the dialog is showing
    216                     receiver.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
    217 
    218                     // Send the lockout message to the system dialog
    219                     if (mBundle != null) {
    220                         mStatusBarService.onFingerprintError(
    221                                 mFingerprintManager.getErrorString(errorCode, 0 /* vendorCode */));
    222                     }
    223                 } catch (RemoteException e) {
    224                     Slog.w(TAG, "Failed to notify lockout:", e);
    225                 }
    226             }
    227             result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
    228         } else {
    229             if (receiver != null) {
    230                 vibrateSuccess();
    231             }
    232             result |= true; // we have a valid fingerprint, done
    233             resetFailedAttempts();
    234             onStop();
    235         }
    236         return result;
    237     }
    238 
    239     /**
    240      * Start authentication
    241      */
    242     @Override
    243     public int start() {
    244         IBiometricsFingerprint daemon = getFingerprintDaemon();
    245         if (daemon == null) {
    246             Slog.w(TAG, "start authentication: no fingerprint HAL!");
    247             return ERROR_ESRCH;
    248         }
    249         onStart();
    250         try {
    251             final int result = daemon.authenticate(mOpId, getGroupId());
    252             if (result != 0) {
    253                 Slog.w(TAG, "startAuthentication failed, result=" + result);
    254                 MetricsLogger.histogram(getContext(), "fingeprintd_auth_start_error", result);
    255                 onError(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
    256                 return result;
    257             }
    258             if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is authenticating...");
    259 
    260             // If authenticating with system dialog, show the dialog
    261             if (mBundle != null) {
    262                 try {
    263                     mStatusBarService.showFingerprintDialog(mBundle, mDialogReceiver);
    264                 } catch (RemoteException e) {
    265                     Slog.e(TAG, "Unable to show fingerprint dialog", e);
    266                 }
    267             }
    268         } catch (RemoteException e) {
    269             Slog.e(TAG, "startAuthentication failed", e);
    270             return ERROR_ESRCH;
    271         }
    272         return 0; // success
    273     }
    274 
    275     @Override
    276     public int stop(boolean initiatedByClient) {
    277         if (mAlreadyCancelled) {
    278             Slog.w(TAG, "stopAuthentication: already cancelled!");
    279             return 0;
    280         }
    281 
    282         onStop();
    283         IBiometricsFingerprint daemon = getFingerprintDaemon();
    284         if (daemon == null) {
    285             Slog.w(TAG, "stopAuthentication: no fingerprint HAL!");
    286             return ERROR_ESRCH;
    287         }
    288         try {
    289             final int result = daemon.cancel();
    290             if (result != 0) {
    291                 Slog.w(TAG, "stopAuthentication failed, result=" + result);
    292                 return result;
    293             }
    294             if (DEBUG) Slog.w(TAG, "client " + getOwnerString() + " is no longer authenticating");
    295         } catch (RemoteException e) {
    296             Slog.e(TAG, "stopAuthentication failed", e);
    297             return ERROR_ESRCH;
    298         } finally {
    299             // If the user already cancelled authentication (via some interaction with the
    300             // dialog, we do not need to hide it since it's already hidden.
    301             // If the device is in lockout, don't hide the dialog - it will automatically hide
    302             // after BiometricPrompt.HIDE_DIALOG_DELAY
    303             if (mBundle != null && !mDialogDismissed && !mInLockout) {
    304                 try {
    305                     mStatusBarService.hideFingerprintDialog();
    306                 } catch (RemoteException e) {
    307                     Slog.e(TAG, "Unable to hide fingerprint dialog", e);
    308                 }
    309             }
    310         }
    311         mAlreadyCancelled = true;
    312         return 0; // success
    313     }
    314 
    315     @Override
    316     public boolean onEnrollResult(int fingerId, int groupId, int remaining) {
    317         if (DEBUG) Slog.w(TAG, "onEnrollResult() called for authenticate!");
    318         return true; // Invalid for Authenticate
    319     }
    320 
    321     @Override
    322     public boolean onRemoved(int fingerId, int groupId, int remaining) {
    323         if (DEBUG) Slog.w(TAG, "onRemoved() called for authenticate!");
    324         return true; // Invalid for Authenticate
    325     }
    326 
    327     @Override
    328     public boolean onEnumerationResult(int fingerId, int groupId, int remaining) {
    329         if (DEBUG) Slog.w(TAG, "onEnumerationResult() called for authenticate!");
    330         return true; // Invalid for Authenticate
    331     }
    332 }
    333