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