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