close
The Wayback Machine - https://web.archive.org/web/20171207114538/https://developer.android.com/samples/AsymmetricFingerprintDialog/src/com.example.android.asymmetricfingerprintdialog/FingerprintAuthenticationDialogFragment.html
Skip to content

Most visited

Recently visited

navigation
AsymmetricFingerprintDialog / src / com.example.android.asymmetricfingerprintdialog /

FingerprintAuthenticationDialogFragment.java

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.asymmetricfingerprintdialog;
18
 
19
import com.example.android.asymmetricfingerprintdialog.server.StoreBackend;
20
import com.example.android.asymmetricfingerprintdialog.server.Transaction;
21
 
22
import android.app.DialogFragment;
23
import android.content.Context;
24
import android.content.SharedPreferences;
25
import android.hardware.fingerprint.FingerprintManager;
26
import android.os.Bundle;
27
import android.view.KeyEvent;
28
import android.view.LayoutInflater;
29
import android.view.View;
30
import android.view.ViewGroup;
31
import android.view.inputmethod.EditorInfo;
32
import android.view.inputmethod.InputMethodManager;
33
import android.widget.Button;
34
import android.widget.CheckBox;
35
import android.widget.EditText;
36
import android.widget.ImageView;
37
import android.widget.TextView;
38
 
39
import java.io.IOException;
40
import java.security.KeyFactory;
41
import java.security.KeyStore;
42
import java.security.KeyStoreException;
43
import java.security.NoSuchAlgorithmException;
44
import java.security.PublicKey;
45
import java.security.SecureRandom;
46
import java.security.Signature;
47
import java.security.SignatureException;
48
import java.security.cert.CertificateException;
49
import java.security.spec.InvalidKeySpecException;
50
import java.security.spec.X509EncodedKeySpec;
51
 
52
import javax.inject.Inject;
53
 
54
/**
55
 * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
56
 * authentication if fingerprint is not available.
57
 */
58
public class FingerprintAuthenticationDialogFragment extends DialogFragment
59
        implements TextView.OnEditorActionListener, FingerprintUiHelper.Callback {
60
 
61
    private Button mCancelButton;
62
    private Button mSecondDialogButton;
63
    private View mFingerprintContent;
64
    private View mBackupContent;
65
    private EditText mPassword;
66
    private CheckBox mUseFingerprintFutureCheckBox;
67
    private TextView mPasswordDescriptionTextView;
68
    private TextView mNewFingerprintEnrolledTextView;
69
 
70
    private Stage mStage = Stage.FINGERPRINT;
71
 
72
    private FingerprintManager.CryptoObject mCryptoObject;
73
    private FingerprintUiHelper mFingerprintUiHelper;
74
    private MainActivity mActivity;
75
 
76
    @Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
77
    @Inject InputMethodManager mInputMethodManager;
78
    @Inject SharedPreferences mSharedPreferences;
79
    @Inject StoreBackend mStoreBackend;
80
 
81
    @Inject
82
    public FingerprintAuthenticationDialogFragment() {}
83
 
84
    @Override
85
    public void onCreate(Bundle savedInstanceState) {
86
        super.onCreate(savedInstanceState);
87
 
88
        // Do not create a new Fragment when the Activity is re-created such as orientation changes.
89
        setRetainInstance(true);
90
        setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
91
 
92
        // We register a new user account here. Real apps should do this with proper UIs.
93
        enroll();
94
    }
95
 
96
    @Override
97
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
98
            Bundle savedInstanceState) {
99
        getDialog().setTitle(getString(R.string.sign_in));
100
        View v = inflater.inflate(R.layout.fingerprint_dialog_container, container, false);
101
        mCancelButton = (Button) v.findViewById(R.id.cancel_button);
102
        mCancelButton.setOnClickListener(new View.OnClickListener() {
103
            @Override
104
            public void onClick(View view) {
105
                dismiss();
106
            }
107
        });
108
 
109
        mSecondDialogButton = (Button) v.findViewById(R.id.second_dialog_button);
110
        mSecondDialogButton.setOnClickListener(new View.OnClickListener() {
111
            @Override
112
            public void onClick(View view) {
113
                if (mStage == Stage.FINGERPRINT) {
114
                    goToBackup();
115
                } else {
116
                    verifyPassword();
117
                }
118
            }
119
        });
120
        mFingerprintContent = v.findViewById(R.id.fingerprint_container);
121
        mBackupContent = v.findViewById(R.id.backup_container);
122
        mPassword = (EditText) v.findViewById(R.id.password);
123
        mPassword.setOnEditorActionListener(this);
124
        mPasswordDescriptionTextView = (TextView) v.findViewById(R.id.password_description);
125
        mUseFingerprintFutureCheckBox = (CheckBox)
126
                v.findViewById(R.id.use_fingerprint_in_future_check);
127
        mNewFingerprintEnrolledTextView = (TextView)
128
                v.findViewById(R.id.new_fingerprint_enrolled_description);
129
        mFingerprintUiHelper = mFingerprintUiHelperBuilder.build(
130
                (ImageView) v.findViewById(R.id.fingerprint_icon),
131
                (TextView) v.findViewById(R.id.fingerprint_status), this);
132
        updateStage();
133
 
134
        // If fingerprint authentication is not available, switch immediately to the backup
135
        // (password) screen.
136
        if (!mFingerprintUiHelper.isFingerprintAuthAvailable()) {
137
            goToBackup();
138
        }
139
        return v;
140
    }
141
 
142
    @Override
143
    public void onResume() {
144
        super.onResume();
145
        if (mStage == Stage.FINGERPRINT) {
146
            mFingerprintUiHelper.startListening(mCryptoObject);
147
        }
148
    }
149
 
150
    public void setStage(Stage stage) {
151
        mStage = stage;
152
    }
153
 
154
    @Override
155
    public void onPause() {
156
        super.onPause();
157
        mFingerprintUiHelper.stopListening();
158
    }
159
 
160
    @Override
161
    public void onAttach(Context context) {
162
        super.onAttach(context);
163
        mActivity = (MainActivity) getActivity();
164
    }
165
 
166
    /**
167
     * Sets the crypto object to be passed in when authenticating with fingerprint.
168
     */
169
    public void setCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
170
        mCryptoObject = cryptoObject;
171
    }
172
 
173
    /**
174
     * Switches to backup (password) screen. This either can happen when fingerprint is not
175
     * available or the user chooses to use the password authentication method by pressing the
176
     * button. This can also happen when the user had too many fingerprint attempts.
177
     */
178
    private void goToBackup() {
179
        mStage = Stage.PASSWORD;
180
        updateStage();
181
        mPassword.requestFocus();
182
 
183
        // Show the keyboard.
184
        mPassword.postDelayed(mShowKeyboardRunnable, 500);
185
 
186
        // Fingerprint is not used anymore. Stop listening for it.
187
        mFingerprintUiHelper.stopListening();
188
    }
189
 
190
    /**
191
     * Enrolls a user to the fake backend.
192
     */
193
    private void enroll() {
194
        try {
195
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
196
            keyStore.load(null);
197
            PublicKey publicKey = keyStore.getCertificate(MainActivity.KEY_NAME).getPublicKey();
198
            // Provide the public key to the backend. In most cases, the key needs to be transmitted
199
            // to the backend over the network, for which Key.getEncoded provides a suitable wire
200
            // format (X.509 DER-encoded). The backend can then create a PublicKey instance from the
201
            // X.509 encoded form using KeyFactory.generatePublic. This conversion is also currently
202
            // needed on API Level 23 (Android M) due to a platform bug which prevents the use of
203
            // Android Keystore public keys when their private keys require user authentication.
204
            // This conversion creates a new public key which is not backed by Android Keystore and
205
            // thus is not affected by the bug.
206
            KeyFactory factory = KeyFactory.getInstance(publicKey.getAlgorithm());
207
            X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey.getEncoded());
208
            PublicKey verificationKey = factory.generatePublic(spec);
209
            mStoreBackend.enroll("user", "password", verificationKey);
210
        } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException |
211
                IOException | InvalidKeySpecException e) {
212
            e.printStackTrace();
213
        }
214
    }
215
 
216
    /**
217
     * Checks whether the current entered password is correct, and dismisses the the dialog and lets
218
     * the activity know about the result.
219
     */
220
    private void verifyPassword() {
221
        Transaction transaction = new Transaction("user", 1, new SecureRandom().nextLong());
222
        if (!mStoreBackend.verify(transaction, mPassword.getText().toString())) {
223
            return;
224
        }
225
        if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
226
            SharedPreferences.Editor editor = mSharedPreferences.edit();
227
            editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key),
228
                    mUseFingerprintFutureCheckBox.isChecked());
229
            editor.apply();
230
 
231
            if (mUseFingerprintFutureCheckBox.isChecked()) {
232
                // Re-create the key so that fingerprints including new ones are validated.
233
                mActivity.createKeyPair();
234
                mStage = Stage.FINGERPRINT;
235
            }
236
        }
237
        mPassword.setText("");
238
        mActivity.onPurchased(null);
239
        dismiss();
240
    }
241
 
242
    private final Runnable mShowKeyboardRunnable = new Runnable() {
243
        @Override
244
        public void run() {
245
            mInputMethodManager.showSoftInput(mPassword, 0);
246
        }
247
    };
248
 
249
    private void updateStage() {
250
        switch (mStage) {
251
            case FINGERPRINT:
252
                mCancelButton.setText(R.string.cancel);
253
                mSecondDialogButton.setText(R.string.use_password);
254
                mFingerprintContent.setVisibility(View.VISIBLE);
255
                mBackupContent.setVisibility(View.GONE);
256
                break;
257
            case NEW_FINGERPRINT_ENROLLED:
258
                // Intentional fall through
259
            case PASSWORD:
260
                mCancelButton.setText(R.string.cancel);
261
                mSecondDialogButton.setText(R.string.ok);
262
                mFingerprintContent.setVisibility(View.GONE);
263
                mBackupContent.setVisibility(View.VISIBLE);
264
                if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) {
265
                    mPasswordDescriptionTextView.setVisibility(View.GONE);
266
                    mNewFingerprintEnrolledTextView.setVisibility(View.VISIBLE);
267
                    mUseFingerprintFutureCheckBox.setVisibility(View.VISIBLE);
268
                }
269
                break;
270
        }
271
    }
272
 
273
    @Override
274
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
275
        if (actionId == EditorInfo.IME_ACTION_GO) {
276
            verifyPassword();
277
            return true;
278
        }
279
        return false;
280
    }
281
 
282
    @Override
283
    public void onAuthenticated() {
284
        // Callback from FingerprintUiHelper. Let the activity know that authentication was
285
        // successful.
286
        mPassword.setText("");
287
        Signature signature = mCryptoObject.getSignature();
288
        // Include a client nonce in the transaction so that the nonce is also signed by the private
289
        // key and the backend can verify that the same nonce can't be used to prevent replay
290
        // attacks.
291
        Transaction transaction = new Transaction("user", 1, new SecureRandom().nextLong());
292
        try {
293
            signature.update(transaction.toByteArray());
294
            byte[] sigBytes = signature.sign();
295
            if (mStoreBackend.verify(transaction, sigBytes)) {
296
                mActivity.onPurchased(sigBytes);
297
                dismiss();
298
            } else {
299
                mActivity.onPurchaseFailed();
300
                dismiss();
301
            }
302
        } catch (SignatureException e) {
303
            throw new RuntimeException(e);
304
        }
305
    }
306
 
307
    @Override
308
    public void onError() {
309
        goToBackup();
310
    }
311
 
312
    /**
313
     * Enumeration to indicate which authentication method the user is trying to authenticate with.
314
     */
315
    public enum Stage {
316
        FINGERPRINT,
317
        NEW_FINGERPRINT_ENROLLED,
318
        PASSWORD
319
    }
320
}
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

BERJAYA

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)