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