diff options
Diffstat (limited to 'samples/browseable/FingerprintDialog/src')
5 files changed, 185 insertions, 28 deletions
diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java index 57c00de08..8909f752f 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintAuthenticationDialogFragment.java @@ -17,6 +17,7 @@ package com.example.android.fingerprintdialog; import android.app.DialogFragment; +import android.content.SharedPreferences; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.view.KeyEvent; @@ -26,6 +27,7 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; @@ -44,6 +46,9 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment private View mFingerprintContent; private View mBackupContent; private EditText mPassword; + private CheckBox mUseFingerprintFutureCheckBox; + private TextView mPasswordDescriptionTextView; + private TextView mNewFingerprintEnrolledTextView; private Stage mStage = Stage.FINGERPRINT; @@ -52,6 +57,7 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment @Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder; @Inject InputMethodManager mInputMethodManager; + @Inject SharedPreferences mSharedPreferences; @Inject public FingerprintAuthenticationDialogFragment() {} @@ -93,6 +99,11 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment mBackupContent = v.findViewById(R.id.backup_container); mPassword = (EditText) v.findViewById(R.id.password); mPassword.setOnEditorActionListener(this); + mPasswordDescriptionTextView = (TextView) v.findViewById(R.id.password_description); + mUseFingerprintFutureCheckBox = (CheckBox) + v.findViewById(R.id.use_fingerprint_in_future_check); + mNewFingerprintEnrolledTextView = (TextView) + v.findViewById(R.id.new_fingerprint_enrolled_description); mFingerprintUiHelper = mFingerprintUiHelperBuilder.build( (ImageView) v.findViewById(R.id.fingerprint_icon), (TextView) v.findViewById(R.id.fingerprint_status), this); @@ -114,6 +125,10 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment } } + public void setStage(Stage stage) { + mStage = stage; + } + @Override public void onPause() { super.onPause(); @@ -149,12 +164,25 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment * let's the activity know about the result. */ private void verifyPassword() { - if (checkPassword(mPassword.getText().toString())) { - ((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */); - dismiss(); - } else { - // assume the password is always correct. + if (!checkPassword(mPassword.getText().toString())) { + return; } + MainActivity activity = ((MainActivity) getActivity()); + if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(getString(R.string.use_fingerprint_to_authenticate_key), + mUseFingerprintFutureCheckBox.isChecked()); + editor.apply(); + + if (mUseFingerprintFutureCheckBox.isChecked()) { + // Re-create the key so that fingerprints including new ones are validated. + activity.createKey(); + mStage = Stage.FINGERPRINT; + } + } + mPassword.setText(""); + ((MainActivity) getActivity()).onPurchased(false /* without Fingerprint */); + dismiss(); } /** @@ -181,11 +209,18 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment mFingerprintContent.setVisibility(View.VISIBLE); mBackupContent.setVisibility(View.GONE); break; + case NEW_FINGERPRINT_ENROLLED: + // Intentional fall through case PASSWORD: mCancelButton.setText(R.string.cancel); mSecondDialogButton.setText(R.string.ok); mFingerprintContent.setVisibility(View.GONE); mBackupContent.setVisibility(View.VISIBLE); + if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) { + mPasswordDescriptionTextView.setVisibility(View.GONE); + mNewFingerprintEnrolledTextView.setVisibility(View.VISIBLE); + mUseFingerprintFutureCheckBox.setVisibility(View.VISIBLE); + } break; } } @@ -215,8 +250,9 @@ public class FingerprintAuthenticationDialogFragment extends DialogFragment /** * Enumeration to indicate which authentication method the user is trying to authenticate with. */ - private enum Stage { + public enum Stage { FINGERPRINT, + NEW_FINGERPRINT_ENROLLED, PASSWORD } } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java index 16d5067ea..964e1f6d8 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintModule.java @@ -18,7 +18,10 @@ package com.example.android.fingerprintdialog; import android.app.KeyguardManager; import android.content.Context; +import android.content.SharedPreferences; import android.hardware.fingerprint.FingerprintManager; +import android.preference.PreferenceManager; +import android.security.keystore.KeyProperties; import android.view.inputmethod.InputMethodManager; import java.security.KeyStore; @@ -75,7 +78,7 @@ public class FingerprintModule { @Provides public KeyGenerator providesKeyGenerator() { try { - return KeyGenerator.getInstance("AES", "AndroidKeyStore"); + return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new RuntimeException("Failed to get an instance of KeyGenerator", e); } @@ -84,7 +87,9 @@ public class FingerprintModule { @Provides public Cipher providesCipher(KeyStore keyStore) { try { - return Cipher.getInstance("AES/CBC/PKCS7Padding"); + return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException("Failed to get an instance of Cipher", e); } @@ -94,4 +99,9 @@ public class FingerprintModule { public InputMethodManager providesInputMethodManager(Context context) { return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); } + + @Provides + public SharedPreferences providesSharedPreferences(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java index ab7570ca7..92fcdb1d0 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/FingerprintUiHelper.java @@ -82,7 +82,8 @@ public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallba } mCancellationSignal = new CancellationSignal(); mSelfCancelled = false; - mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, this, 0 /* flags */); + mFingerprintManager + .authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null); mIcon.setImageResource(R.drawable.ic_fp_40px); } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java index 9d09765eb..c954bfa7b 100644 --- a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/MainActivity.java @@ -19,6 +19,8 @@ package com.example.android.fingerprintdialog; import android.Manifest; import android.app.Activity; import android.app.KeyguardManager; +import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; @@ -27,6 +29,8 @@ import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.util.Base64; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.TextView; @@ -60,23 +64,29 @@ public class MainActivity extends Activity { /** Alias for our key in the Android Key Store */ private static final String KEY_NAME = "my_key"; + private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0; + @Inject KeyguardManager mKeyguardManager; + @Inject FingerprintManager mFingerprintManager; @Inject FingerprintAuthenticationDialogFragment mFragment; @Inject KeyStore mKeyStore; @Inject KeyGenerator mKeyGenerator; @Inject Cipher mCipher; + @Inject SharedPreferences mSharedPreferences; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((InjectedApplication) getApplication()).inject(this); - requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, 0); + requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, + FINGERPRINT_PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { - if (requestCode == 0 && state[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE + && state[0] == PackageManager.PERMISSION_GRANTED) { setContentView(R.layout.activity_main); Button purchaseButton = (Button) findViewById(R.id.purchase_button); if (!mKeyguardManager.isKeyguardSecure()) { @@ -88,35 +98,71 @@ public class MainActivity extends Activity { purchaseButton.setEnabled(false); return; } + if (!mFingerprintManager.hasEnrolledFingerprints()) { + purchaseButton.setEnabled(false); + // This happens when no fingerprints are registered. + Toast.makeText(this, + "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint", + Toast.LENGTH_LONG).show(); + return; + } createKey(); + purchaseButton.setEnabled(true); purchaseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - - // Show the fingerprint dialog. The user has the option to use the fingerprint with - // crypto, or you can fall back to using a server-side verified password. - mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); - mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + findViewById(R.id.confirmation_message).setVisibility(View.GONE); + findViewById(R.id.encrypted_message).setVisibility(View.GONE); + + // Set up the crypto object for later. The object will be authenticated by use + // of the fingerprint. + if (initCipher()) { + + // Show the fingerprint dialog. The user has the option to use the fingerprint with + // crypto, or you can fall back to using a server-side verified password. + mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); + boolean useFingerprintPreference = mSharedPreferences + .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key), + true); + if (useFingerprintPreference) { + mFragment.setStage( + FingerprintAuthenticationDialogFragment.Stage.FINGERPRINT); + } else { + mFragment.setStage( + FingerprintAuthenticationDialogFragment.Stage.PASSWORD); + } + mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + } else { + // This happens if the lock screen has been disabled or or a fingerprint got + // enrolled. Thus show the dialog to authenticate with their password first + // and ask the user if they want to authenticate with fingerprints in the + // future + mFragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher)); + mFragment.setStage( + FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED); + mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + } } }); - - // Set up the crypto object for later. The object will be authenticated by use - // of the fingerprint. - initCipher(); } } - private void initCipher() { + /** + * Initialize the {@link Cipher} instance with the created key in the {@link #createKey()} + * method. + * + * @return {@code true} if initialization is successful, {@code false} if the lock screen has + * been disabled or reset after the key was generated, or if a fingerprint got enrolled after + * the key was generated. + */ + private boolean initCipher() { try { mKeyStore.load(null); SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); mCipher.init(Cipher.ENCRYPT_MODE, key); + return true; } catch (KeyPermanentlyInvalidatedException e) { - // This happens if the lock screen has been disabled or reset after the key was - // generated, or if a fingerprint got enrolled after the key was generated. - Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" - + e.getMessage(), - Toast.LENGTH_LONG).show(); + return false; } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("Failed to init Cipher", e); @@ -124,7 +170,6 @@ public class MainActivity extends Activity { } public void onPurchased(boolean withFingerprint) { - findViewById(R.id.purchase_button).setVisibility(View.GONE); if (withFingerprint) { // If the user has authenticated with fingerprint, verify that using cryptography and // then show the confirmation message. @@ -164,7 +209,7 @@ public class MainActivity extends Activity { * Creates a symmetric key in the Android Key Store which can only be used after the user has * authenticated with fingerprint. */ - private void createKey() { + public void createKey() { // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint // for your flow. Use of keys is necessary if you need to know if the set of // enrolled fingerprints has changed. @@ -187,4 +232,22 @@ public class MainActivity extends Activity { throw new RuntimeException(e); } } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.action_settings) { + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + return true; + } + return super.onOptionsItemSelected(item); + } } diff --git a/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/SettingsActivity.java b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/SettingsActivity.java new file mode 100644 index 000000000..08b391140 --- /dev/null +++ b/samples/browseable/FingerprintDialog/src/com.example.android.fingerprintdialog/SettingsActivity.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.example.android.fingerprintdialog; + +import android.app.Activity; +import android.os.Bundle; +import android.preference.PreferenceFragment; + +public class SettingsActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction().replace(android.R.id.content, + new SettingsFragment()).commit(); + } + + /** + * Fragment for settings. + */ + public static class SettingsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } + } +} + + |