• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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