Home | History | Annotate | Download | only in fingerprintdialog
      1 /*
      2  * Copyright (C) 2015 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.example.android.fingerprintdialog;
     18 
     19 import android.app.Activity;
     20 import android.app.DialogFragment;
     21 import android.content.SharedPreferences;
     22 import android.hardware.fingerprint.FingerprintManager;
     23 import android.os.Bundle;
     24 import android.preference.PreferenceManager;
     25 import android.view.KeyEvent;
     26 import android.view.LayoutInflater;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.inputmethod.EditorInfo;
     30 import android.view.inputmethod.InputMethodManager;
     31 import android.widget.Button;
     32 import android.widget.CheckBox;
     33 import android.widget.EditText;
     34 import android.widget.ImageView;
     35 import android.widget.TextView;
     36 
     37 /**
     38  * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
     39  * authentication if fingerprint is not available.
     40  */
     41 public class FingerprintAuthenticationDialogFragment extends DialogFragment
     42         implements TextView.OnEditorActionListener, FingerprintUiHelper.Callback {
     43 
     44     private Button mCancelButton;
     45     private Button mSecondDialogButton;
     46     private View mFingerprintContent;
     47     private View mBackupContent;
     48     private EditText mPassword;
     49     private CheckBox mUseFingerprintFutureCheckBox;
     50     private TextView mPasswordDescriptionTextView;
     51     private TextView mNewFingerprintEnrolledTextView;
     52 
     53     private Stage mStage = Stage.FINGERPRINT;
     54 
     55     private FingerprintManager.CryptoObject mCryptoObject;
     56     private FingerprintUiHelper mFingerprintUiHelper;
     57     private MainActivity mActivity;
     58 
     59     private InputMethodManager mInputMethodManager;
     60     private SharedPreferences mSharedPreferences;
     61 
     62     @Override
     63     public void onCreate(Bundle savedInstanceState) {
     64         super.onCreate(savedInstanceState);
     65 
     66         // Do not create a new Fragment when the Activity is re-created such as orientation changes.
     67         setRetainInstance(true);
     68         setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
     69     }
     70 
     71     @Override
     72     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     73             Bundle savedInstanceState) {
     74         getDialog().setTitle(getString(R.string.sign_in));
     75         View v = inflater.inflate(R.layout.fingerprint_dialog_container, container, false);
     76         mCancelButton = (Button) v.findViewById(R.id.cancel_button);
     77         mCancelButton.setOnClickListener(new View.OnClickListener() {
     78             @Override
     79             public void onClick(View view) {
     80                 dismiss();
     81             }
     82         });
     83 
     84         mSecondDialogButton = (Button) v.findViewById(R.id.second_dialog_button);
     85         mSecondDialogButton.setOnClickListener(new View.OnClickListener() {
     86             @Override
     87             public void onClick(View view) {
     88                 if (mStage == Stage.FINGERPRINT) {
     89                     goToBackup();
     90                 } else {
     91                     verifyPassword();
     92                 }
     93             }
     94         });
     95         mFingerprintContent = v.findViewById(R.id.fingerprint_container);
     96         mBackupContent = v.findViewById(R.id.backup_container);
     97         mPassword = (EditText) v.findViewById(R.id.password);
     98         mPassword.setOnEditorActionListener(this);
     99         mPasswordDescriptionTextView = (TextView) v.findViewById(R.id.password_description);
    100         mUseFingerprintFutureCheckBox = (CheckBox)
    101                 v.findViewById(R.id.use_fingerprint_in_future_check);
    102         mNewFingerprintEnrolledTextView = (TextView)
    103                 v.findViewById(R.id.new_fingerprint_enrolled_description);
    104         mFingerprintUiHelper = new FingerprintUiHelper(
    105                 mActivity.getSystemService(FingerprintManager.class),
    106                 (ImageView) v.findViewById(R.id.fingerprint_icon),
    107                 (TextView) v.findViewById(R.id.fingerprint_status), this);
    108         updateStage();
    109 
    110         // If fingerprint authentication is not available, switch immediately to the backup
    111         // (password) screen.
    112         if (!mFingerprintUiHelper.isFingerprintAuthAvailable()) {
    113             goToBackup();
    114         }
    115         return v;
    116     }
    117 
    118     @Override
    119     public void onResume() {
    120         super.onResume();
    121         if (mStage == Stage.FINGERPRINT) {
    122             mFingerprintUiHelper.startListening(mCryptoObject);
    123         }
    124     }
    125 
    126     public void setStage(Stage stage) {
    127         mStage = stage;
    128     }
    129 
    130     @Override
    131     public void onPause() {
    132         super.onPause();
    133         mFingerprintUiHelper.stopListening();
    134     }
    135 
    136     @Override
    137     public void onAttach(Activity activity) {
    138         super.onAttach(activity);
    139         mActivity = (MainActivity) activity;
    140         mInputMethodManager = mActivity.getSystemService(InputMethodManager.class);
    141         mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
    142     }
    143 
    144     /**
    145      * Sets the crypto object to be passed in when authenticating with fingerprint.
    146      */
    147     public void setCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
    148         mCryptoObject = cryptoObject;
    149     }
    150 
    151     /**
    152      * Switches to backup (password) screen. This either can happen when fingerprint is not
    153      * available or the user chooses to use the password authentication method by pressing the
    154      * button. This can also happen when the user had too many fingerprint attempts.
    155      */
    156     private void goToBackup() {
    157         mStage = Stage.PASSWORD;
    158         updateStage();
    159         mPassword.requestFocus();
    160 
    161         // Show the keyboard.
    162         mPassword.postDelayed(mShowKeyboardRunnable, 500);
    163 
    164         // Fingerprint is not used anymore. Stop listening for it.
    165         mFingerprintUiHelper.stopListening();
    166     }
    167 
    168     /**
    169      * Checks whether the current entered password is correct, and dismisses the the dialog and
    170      * let's the activity know about the result.
    171      */
    172     private void verifyPassword() {
    173         if (!checkPassword(mPassword.getText().toString())) {
    174             return;
    175         }
    176         if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
    177             SharedPreferences.Editor editor = mSharedPreferences.edit();
    178             editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
    179                     mUseFingerprintFutureCheckBox.isChecked());
    180             editor.apply();
    181 
    182             if (mUseFingerprintFutureCheckBox.isChecked()) {
    183                 // Re-create the key so that fingerprints including new ones are validated.
    184                 mActivity.createKey();
    185                 mStage = Stage.FINGERPRINT;
    186             }
    187         }
    188         mPassword.setText("");
    189         mActivity.onPurchased(false /* without Fingerprint */);
    190         dismiss();
    191     }
    192 
    193     /**
    194      * @return true if {@code password} is correct, false otherwise
    195      */
    196     private boolean checkPassword(String password) {
    197         // Assume the password is always correct.
    198         // In the real world situation, the password needs to be verified in the server side.
    199         return password.length() > 0;
    200     }
    201 
    202     private final Runnable mShowKeyboardRunnable = new Runnable() {
    203         @Override
    204         public void run() {
    205             mInputMethodManager.showSoftInput(mPassword, 0);
    206         }
    207     };
    208 
    209     private void updateStage() {
    210         switch (mStage) {
    211             case FINGERPRINT:
    212                 mCancelButton.setText(R.string.cancel);
    213                 mSecondDialogButton.setText(R.string.use_password);
    214                 mFingerprintContent.setVisibility(View.VISIBLE);
    215                 mBackupContent.setVisibility(View.GONE);
    216                 break;
    217             case NEW_FINGERPRINT_ENROLLED:
    218                 // Intentional fall through
    219             case PASSWORD:
    220                 mCancelButton.setText(R.string.cancel);
    221                 mSecondDialogButton.setText(R.string.ok);
    222                 mFingerprintContent.setVisibility(View.GONE);
    223                 mBackupContent.setVisibility(View.VISIBLE);
    224                 if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
    225                     mPasswordDescriptionTextView.setVisibility(View.GONE);
    226                     mNewFingerprintEnrolledTextView.setVisibility(View.VISIBLE);
    227                     mUseFingerprintFutureCheckBox.setVisibility(View.VISIBLE);
    228                 }
    229                 break;
    230         }
    231     }
    232 
    233     @Override
    234     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    235         if (actionId == EditorInfo.IME_ACTION_GO) {
    236             verifyPassword();
    237             return true;
    238         }
    239         return false;
    240     }
    241 
    242     @Override
    243     public void onAuthenticated() {
    244         // Callback from FingerprintUiHelper. Let the activity know that authentication was
    245         // successful.
    246         mActivity.onPurchased(true /* withFingerprint */);
    247         dismiss();
    248     }
    249 
    250     @Override
    251     public void onError() {
    252         goToBackup();
    253     }
    254 
    255     /**
    256      * Enumeration to indicate which authentication method the user is trying to authenticate with.
    257      */
    258     public enum Stage {
    259         FINGERPRINT,
    260         NEW_FINGERPRINT_ENROLLED,
    261         PASSWORD
    262     }
    263 }
    264