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.DialogFragment;
20 import android.content.Context;
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
onCreate(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onResume()119     public void onResume() {
120         super.onResume();
121         if (mStage == Stage.FINGERPRINT) {
122             mFingerprintUiHelper.startListening(mCryptoObject);
123         }
124     }
125 
setStage(Stage stage)126     public void setStage(Stage stage) {
127         mStage = stage;
128     }
129 
130     @Override
onPause()131     public void onPause() {
132         super.onPause();
133         mFingerprintUiHelper.stopListening();
134     }
135 
136     @Override
onAttach(Context context)137     public void onAttach(Context context) {
138         super.onAttach(context);
139         mActivity = (MainActivity) getActivity();
140         mInputMethodManager = context.getSystemService(InputMethodManager.class);
141         mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
142     }
143 
144     /**
145      * Sets the crypto object to be passed in when authenticating with fingerprint.
146      */
setCryptoObject(FingerprintManager.CryptoObject cryptoObject)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      */
goToBackup()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      */
verifyPassword()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(MainActivity.DEFAULT_KEY_NAME, true);
185                 mStage = Stage.FINGERPRINT;
186             }
187         }
188         mPassword.setText("");
189         mActivity.onPurchased(false /* without Fingerprint */, null);
190         dismiss();
191     }
192 
193     /**
194      * @return true if {@code password} is correct, false otherwise
195      */
checkPassword(String password)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 
updateStage()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
onEditorAction(TextView v, int actionId, KeyEvent event)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
onAuthenticated()243     public void onAuthenticated() {
244         // Callback from FingerprintUiHelper. Let the activity know that authentication was
245         // successful.
246         mActivity.onPurchased(true /* withFingerprint */, mCryptoObject);
247         dismiss();
248     }
249 
250     @Override
onError()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