aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuichi Araki <yaraki@google.com>2015-09-15 11:13:03 +0900
committerYuichi Araki <yaraki@google.com>2015-09-29 17:40:46 +0900
commit424704d90af155363b09861648c48811c7a7a7ad (patch)
tree121a59dfba33f9bf332c0505b6d4fb1f4d67ee0b
parent1d1948e9152433a491780f085daded25f0b4ec3c (diff)
downloadandroid-424704d90af155363b09861648c48811c7a7a7ad.tar.gz
AsymmetricFingerprintDialog: Add a new sample
This is a version of FingerprintDialog with asymmetric keys. Bug: 23089151 Change-Id: I765430d9f79abcd57c9be8e4bf5f33bda1296f9b
-rw-r--r--security/AsymmetricFingerprintDialog/Application/.gitignore16
-rw-r--r--security/AsymmetricFingerprintDialog/Application/proguard-project.txt20
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/AndroidManifest.xml43
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintAuthenticationDialogFragment.java316
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintModule.java111
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintUiHelper.java165
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/InjectedApplication.java59
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java230
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/SettingsActivity.java47
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackend.java61
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackendImpl.java69
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/Transaction.java66
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.pngbin0 -> 7011 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.pngbin0 -> 4001 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.pngbin0 -> 8288 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.pngbin0 -> 10524 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.pngbin0 -> 18565 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.pngbin0 -> 16535 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/card.xml24
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml28
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml28
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/layout/activity_main.xml107
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml78
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml65
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml58
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/menu/menu_main.xml21
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3237 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2036 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4058 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 6516 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 9529 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/values/colors.xml21
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/values/strings.xml39
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/main/res/xml/preferences.xml23
-rw-r--r--security/AsymmetricFingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java111
-rw-r--r--security/AsymmetricFingerprintDialog/build.gradle11
-rw-r--r--security/AsymmetricFingerprintDialog/buildSrc/build.gradle15
-rw-r--r--security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.jarbin0 -> 49896 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xsecurity/AsymmetricFingerprintDialog/gradlew164
-rw-r--r--security/AsymmetricFingerprintDialog/gradlew.bat90
-rw-r--r--security/AsymmetricFingerprintDialog/screenshots/1-purchase-screen.pngbin0 -> 99587 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/screenshots/2-fingerprint-dialog.pngbin0 -> 104965 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/screenshots/3-fingerprint-authenticated.pngbin0 -> 92101 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/screenshots/4-new-fingerprint-enrolled.pngbin0 -> 103821 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/screenshots/big-icon.pngbin0 -> 35440 bytes
-rw-r--r--security/AsymmetricFingerprintDialog/settings.gradle1
-rw-r--r--security/AsymmetricFingerprintDialog/template-params.xml115
48 files changed, 2208 insertions, 0 deletions
diff --git a/security/AsymmetricFingerprintDialog/Application/.gitignore b/security/AsymmetricFingerprintDialog/Application/.gitignore
new file mode 100644
index 00000000..6eb878d4
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2013 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.
+src/template/
+src/common/
+build.gradle
diff --git a/security/AsymmetricFingerprintDialog/Application/proguard-project.txt b/security/AsymmetricFingerprintDialog/Application/proguard-project.txt
new file mode 100644
index 00000000..f2fe1559
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/AndroidManifest.xml b/security/AsymmetricFingerprintDialog/Application/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..d1cf9f87
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.asymmetricfingerprintdialog"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
+
+ <application
+ android:name=".InjectedApplication"
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:theme="@style/AppTheme">
+
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".SettingsActivity"
+ android:label="@string/action_settings" />
+ </application>
+</manifest>
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintAuthenticationDialogFragment.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintAuthenticationDialogFragment.java
new file mode 100644
index 00000000..d40661c6
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintAuthenticationDialogFragment.java
@@ -0,0 +1,316 @@
+/*
+ * 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.asymmetricfingerprintdialog;
+
+import com.example.android.asymmetricfingerprintdialog.server.StoreBackend;
+import com.example.android.asymmetricfingerprintdialog.server.Transaction;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.content.SharedPreferences;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+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;
+
+import java.io.IOException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.inject.Inject;
+
+/**
+ * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
+ * authentication if fingerprint is not available.
+ */
+public class FingerprintAuthenticationDialogFragment extends DialogFragment
+ implements TextView.OnEditorActionListener, FingerprintUiHelper.Callback {
+
+ private Button mCancelButton;
+ private Button mSecondDialogButton;
+ private View mFingerprintContent;
+ private View mBackupContent;
+ private EditText mPassword;
+ private CheckBox mUseFingerprintFutureCheckBox;
+ private TextView mPasswordDescriptionTextView;
+ private TextView mNewFingerprintEnrolledTextView;
+
+ private Stage mStage = Stage.FINGERPRINT;
+
+ private FingerprintManager.CryptoObject mCryptoObject;
+ private FingerprintUiHelper mFingerprintUiHelper;
+ private MainActivity mActivity;
+
+ @Inject FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder;
+ @Inject InputMethodManager mInputMethodManager;
+ @Inject SharedPreferences mSharedPreferences;
+ @Inject StoreBackend mStoreBackend;
+
+ @Inject
+ public FingerprintAuthenticationDialogFragment() {}
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Do not create a new Fragment when the Activity is re-created such as orientation changes.
+ setRetainInstance(true);
+ setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
+
+ // We register a new user account here. Real apps should do this with proper UIs.
+ enroll();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ getDialog().setTitle(getString(R.string.sign_in));
+ View v = inflater.inflate(R.layout.fingerprint_dialog_container, container, false);
+ mCancelButton = (Button) v.findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ dismiss();
+ }
+ });
+
+ mSecondDialogButton = (Button) v.findViewById(R.id.second_dialog_button);
+ mSecondDialogButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mStage == Stage.FINGERPRINT) {
+ goToBackup();
+ } else {
+ verifyPassword();
+ }
+ }
+ });
+ mFingerprintContent = v.findViewById(R.id.fingerprint_container);
+ 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);
+ updateStage();
+
+ // If fingerprint authentication is not available, switch immediately to the backup
+ // (password) screen.
+ if (!mFingerprintUiHelper.isFingerprintAuthAvailable()) {
+ goToBackup();
+ }
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mStage == Stage.FINGERPRINT) {
+ mFingerprintUiHelper.startListening(mCryptoObject);
+ }
+ }
+
+ public void setStage(Stage stage) {
+ mStage = stage;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mFingerprintUiHelper.stopListening();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mActivity = (MainActivity) activity;
+ }
+
+ /**
+ * Sets the crypto object to be passed in when authenticating with fingerprint.
+ */
+ public void setCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Switches to backup (password) screen. This either can happen when fingerprint is not
+ * available or the user chooses to use the password authentication method by pressing the
+ * button. This can also happen when the user had too many fingerprint attempts.
+ */
+ private void goToBackup() {
+ mStage = Stage.PASSWORD;
+ updateStage();
+ mPassword.requestFocus();
+
+ // Show the keyboard.
+ mPassword.postDelayed(mShowKeyboardRunnable, 500);
+
+ // Fingerprint is not used anymore. Stop listening for it.
+ mFingerprintUiHelper.stopListening();
+ }
+
+ /**
+ * Enrolls a user to the fake backend.
+ */
+ private void enroll() {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ PublicKey publicKey = keyStore.getCertificate(MainActivity.KEY_NAME).getPublicKey();
+ // Provide the public key to the backend. In most cases, the key needs to be transmitted
+ // to the backend over the network, for which Key.getEncoded provides a suitable wire
+ // format (X.509 DER-encoded). The backend can then create a PublicKey instance from the
+ // X.509 encoded form using KeyFactory.generatePublic. This conversion is also currently
+ // needed on API Level 23 (Android M) due to a platform bug which prevents the use of
+ // Android Keystore public keys when their private keys require user authentication.
+ // This conversion creates a new public key which is not backed by Android Keystore and
+ // thus is not affected by the bug.
+ KeyFactory factory = KeyFactory.getInstance(publicKey.getAlgorithm());
+ X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey.getEncoded());
+ PublicKey verificationKey = factory.generatePublic(spec);
+ mStoreBackend.enroll("user", "password", verificationKey);
+ } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException |
+ IOException | InvalidKeySpecException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Checks whether the current entered password is correct, and dismisses the the dialog and lets
+ * the activity know about the result.
+ */
+ private void verifyPassword() {
+ Transaction transaction = new Transaction("user", 1);
+ if (!mStoreBackend.verify(transaction, mPassword.getText().toString())) {
+ return;
+ }
+ 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.
+ mActivity.createKeyPair();
+ mStage = Stage.FINGERPRINT;
+ }
+ }
+ mPassword.setText("");
+ mActivity.onPurchased(null);
+ dismiss();
+ }
+
+ private final Runnable mShowKeyboardRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mInputMethodManager.showSoftInput(mPassword, 0);
+ }
+ };
+
+ private void updateStage() {
+ switch (mStage) {
+ case FINGERPRINT:
+ mCancelButton.setText(R.string.cancel);
+ mSecondDialogButton.setText(R.string.use_password);
+ 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;
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_GO) {
+ verifyPassword();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onAuthenticated() {
+ // Callback from FingerprintUiHelper. Let the activity know that authentication was
+ // successful.
+ mPassword.setText("");
+ Signature signature = mCryptoObject.getSignature();
+ Transaction transaction = new Transaction("user", 1);
+ try {
+ signature.update(transaction.toByteArray());
+ byte[] sigBytes = signature.sign();
+ if (mStoreBackend.verify(transaction, sigBytes)) {
+ mActivity.onPurchased(sigBytes);
+ dismiss();
+ } else {
+ mActivity.onPurchaseFailed();
+ dismiss();
+ }
+ } catch (SignatureException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void onError() {
+ goToBackup();
+ }
+
+ /**
+ * Enumeration to indicate which authentication method the user is trying to authenticate with.
+ */
+ public enum Stage {
+ FINGERPRINT,
+ NEW_FINGERPRINT_ENROLLED,
+ PASSWORD
+ }
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintModule.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintModule.java
new file mode 100644
index 00000000..ae3acf80
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintModule.java
@@ -0,0 +1,111 @@
+/*
+ * 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.asymmetricfingerprintdialog;
+
+import com.example.android.asymmetricfingerprintdialog.server.StoreBackend;
+import com.example.android.asymmetricfingerprintdialog.server.StoreBackendImpl;
+
+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.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module for Fingerprint APIs.
+ */
+@Module(
+ library = true,
+ injects = {MainActivity.class}
+)
+public class FingerprintModule {
+
+ private final Context mContext;
+
+ public FingerprintModule(Context context) {
+ mContext = context;
+ }
+
+ @Provides
+ public Context providesContext() {
+ return mContext;
+ }
+
+ @Provides
+ public FingerprintManager providesFingerprintManager(Context context) {
+ return context.getSystemService(FingerprintManager.class);
+ }
+
+ @Provides
+ public KeyguardManager providesKeyguardManager(Context context) {
+ return context.getSystemService(KeyguardManager.class);
+ }
+
+ @Provides
+ public KeyStore providesKeystore() {
+ try {
+ return KeyStore.getInstance("AndroidKeyStore");
+ } catch (KeyStoreException e) {
+ throw new RuntimeException("Failed to get an instance of KeyStore", e);
+ }
+ }
+
+ @Provides
+ public KeyPairGenerator providesKeyPairGenerator() {
+ try {
+ return KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ throw new RuntimeException("Failed to get an instance of KeyPairGenerator", e);
+ }
+ }
+
+ @Provides
+ public Signature providesSignature(KeyStore keyStore) {
+ try {
+ return Signature.getInstance("SHA256withECDSA");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Failed to get an instance of Signature", e);
+ }
+ }
+
+ @Provides
+ public InputMethodManager providesInputMethodManager(Context context) {
+ return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
+
+ @Provides
+ public SharedPreferences providesSharedPreferences(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ @Provides
+ public StoreBackend providesStoreBackend() {
+ return new StoreBackendImpl();
+ }
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintUiHelper.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintUiHelper.java
new file mode 100644
index 00000000..f6548116
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/FingerprintUiHelper.java
@@ -0,0 +1,165 @@
+/*
+ * 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.asymmetricfingerprintdialog;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import javax.inject.Inject;
+
+/**
+ * Small helper class to manage text/icon around fingerprint authentication UI.
+ */
+public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
+
+ @VisibleForTesting static final long ERROR_TIMEOUT_MILLIS = 1600;
+ @VisibleForTesting static final long SUCCESS_DELAY_MILLIS = 1300;
+
+ private final FingerprintManager mFingerprintManager;
+ private final ImageView mIcon;
+ private final TextView mErrorTextView;
+ private final Callback mCallback;
+ private CancellationSignal mCancellationSignal;
+
+ @VisibleForTesting boolean mSelfCancelled;
+
+ /**
+ * Builder class for {@link FingerprintUiHelper} in which injected fields from Dagger
+ * holds its fields and takes other arguments in the {@link #build} method.
+ */
+ public static class FingerprintUiHelperBuilder {
+ private final FingerprintManager mFingerPrintManager;
+
+ @Inject
+ public FingerprintUiHelperBuilder(FingerprintManager fingerprintManager) {
+ mFingerPrintManager = fingerprintManager;
+ }
+
+ public FingerprintUiHelper build(ImageView icon, TextView errorTextView, Callback callback) {
+ return new FingerprintUiHelper(mFingerPrintManager, icon, errorTextView,
+ callback);
+ }
+ }
+
+ /**
+ * Constructor for {@link FingerprintUiHelper}. This method is expected to be called from
+ * only the {@link FingerprintUiHelperBuilder} class.
+ */
+ private FingerprintUiHelper(FingerprintManager fingerprintManager,
+ ImageView icon, TextView errorTextView, Callback callback) {
+ mFingerprintManager = fingerprintManager;
+ mIcon = icon;
+ mErrorTextView = errorTextView;
+ mCallback = callback;
+ }
+
+ public boolean isFingerprintAuthAvailable() {
+ return mFingerprintManager.isHardwareDetected()
+ && mFingerprintManager.hasEnrolledFingerprints();
+ }
+
+ public void startListening(FingerprintManager.CryptoObject cryptoObject) {
+ if (!isFingerprintAuthAvailable()) {
+ return;
+ }
+ mCancellationSignal = new CancellationSignal();
+ mSelfCancelled = false;
+ mFingerprintManager
+ .authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
+ mIcon.setImageResource(R.drawable.ic_fp_40px);
+ }
+
+ public void stopListening() {
+ if (mCancellationSignal != null) {
+ mSelfCancelled = true;
+ mCancellationSignal.cancel();
+ mCancellationSignal = null;
+ }
+ }
+
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ if (!mSelfCancelled) {
+ showError(errString);
+ mIcon.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onError();
+ }
+ }, ERROR_TIMEOUT_MILLIS);
+ }
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ showError(helpString);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ showError(mIcon.getResources().getString(
+ R.string.fingerprint_not_recognized));
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+ mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
+ mIcon.setImageResource(R.drawable.ic_fingerprint_success);
+ mErrorTextView.setTextColor(
+ mErrorTextView.getResources().getColor(R.color.success_color, null));
+ mErrorTextView.setText(
+ mErrorTextView.getResources().getString(R.string.fingerprint_success));
+ mIcon.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAuthenticated();
+ }
+ }, SUCCESS_DELAY_MILLIS);
+ }
+
+ private void showError(CharSequence error) {
+ mIcon.setImageResource(R.drawable.ic_fingerprint_error);
+ mErrorTextView.setText(error);
+ mErrorTextView.setTextColor(
+ mErrorTextView.getResources().getColor(R.color.warning_color, null));
+ mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
+ mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
+ }
+
+ @VisibleForTesting
+ Runnable mResetErrorTextRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mErrorTextView.setTextColor(
+ mErrorTextView.getResources().getColor(R.color.hint_color, null));
+ mErrorTextView.setText(
+ mErrorTextView.getResources().getString(R.string.fingerprint_hint));
+ mIcon.setImageResource(R.drawable.ic_fp_40px);
+ }
+ };
+
+ public interface Callback {
+
+ void onAuthenticated();
+
+ void onError();
+ }
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/InjectedApplication.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/InjectedApplication.java
new file mode 100644
index 00000000..1c3ed7e2
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/InjectedApplication.java
@@ -0,0 +1,59 @@
+/*
+ * 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.asymmetricfingerprintdialog;
+
+import android.app.Application;
+import android.util.Log;
+
+import dagger.ObjectGraph;
+
+/**
+ * The Application class of the sample which holds the ObjectGraph in Dagger and enables
+ * dependency injection.
+ */
+public class InjectedApplication extends Application {
+
+ private static final String TAG = InjectedApplication.class.getSimpleName();
+
+ private ObjectGraph mObjectGraph;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ initObjectGraph(new FingerprintModule(this));
+ }
+
+ /**
+ * Initialize the Dagger module. Passing null or mock modules can be used for testing.
+ *
+ * @param module for Dagger
+ */
+ public void initObjectGraph(Object module) {
+ mObjectGraph = module != null ? ObjectGraph.create(module) : null;
+ }
+
+ public void inject(Object object) {
+ if (mObjectGraph == null) {
+ // This usually happens during tests.
+ Log.i(TAG, "Object graph is not initialized.");
+ return;
+ }
+ mObjectGraph.inject(object);
+ }
+
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java
new file mode 100644
index 00000000..e6031c52
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/MainActivity.java
@@ -0,0 +1,230 @@
+/*
+ * 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.asymmetricfingerprintdialog;
+
+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;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.util.Base64;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.spec.ECGenParameterSpec;
+
+import javax.inject.Inject;
+
+/**
+ * Main entry point for the sample, showing a backpack and "Purchase" button.
+ */
+public class MainActivity extends Activity {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ private static final String DIALOG_FRAGMENT_TAG = "myFragment";
+ private static final String SECRET_MESSAGE = "Very secret message";
+ /** Alias for our key in the Android Key Store */
+ public 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 KeyPairGenerator mKeyPairGenerator;
+ @Inject Signature mSignature;
+ @Inject SharedPreferences mSharedPreferences;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ((InjectedApplication) getApplication()).inject(this);
+
+ requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
+ FINGERPRINT_PERMISSION_REQUEST_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
+ 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()) {
+ // Show a message that the user hasn't set up a fingerprint or lock screen.
+ Toast.makeText(this,
+ "Secure lock screen hasn't set up.\n"
+ + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
+ Toast.LENGTH_LONG).show();
+ 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;
+ }
+ createKeyPair();
+ purchaseButton.setEnabled(true);
+ purchaseButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ 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 (initSignature()) {
+
+ // 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(mSignature));
+ 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.setStage(
+ FingerprintAuthenticationDialogFragment.Stage.NEW_FINGERPRINT_ENROLLED);
+ mFragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Initialize the {@link Signature} instance with the created key in the
+ * {@link #createKeyPair()} 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 initSignature() {
+ try {
+ mKeyStore.load(null);
+ PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_NAME, null);
+ mSignature.initSign(key);
+ return true;
+ } catch (KeyPermanentlyInvalidatedException e) {
+ return false;
+ } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
+ | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException("Failed to init Cipher", e);
+ }
+ }
+
+ public void onPurchased(byte[] signature) {
+ showConfirmation(signature);
+ }
+
+ public void onPurchaseFailed() {
+ Toast.makeText(this, R.string.purchase_fail, Toast.LENGTH_SHORT).show();
+ }
+
+ // Show confirmation, if fingerprint was used show crypto information.
+ private void showConfirmation(byte[] encrypted) {
+ findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE);
+ if (encrypted != null) {
+ TextView v = (TextView) findViewById(R.id.encrypted_message);
+ v.setVisibility(View.VISIBLE);
+ v.setText(Base64.encodeToString(encrypted, 0 /* flags */));
+ }
+ }
+
+ /**
+ * Generates an asymmetric key pair in the Android Keystore. Every use of the private key must
+ * be authorized by the user authenticating with fingerprint. Public key use is unrestricted.
+ */
+ public void createKeyPair() {
+ // 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.
+ try {
+ // Set the alias of the entry in Android KeyStore where the key will appear
+ // and the constrains (purposes) in the constructor of the Builder
+ mKeyPairGenerator.initialize(
+ new KeyGenParameterSpec.Builder(KEY_NAME,
+ KeyProperties.PURPOSE_SIGN)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
+ // Require the user to authenticate with a fingerprint to authorize
+ // every use of the private key
+ .setUserAuthenticationRequired(true)
+ .build());
+ mKeyPairGenerator.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException e) {
+ 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/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/SettingsActivity.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/SettingsActivity.java
new file mode 100644
index 00000000..acc5e378
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/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.asymmetricfingerprintdialog;
+
+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);
+ }
+ }
+}
+
+
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackend.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackend.java
new file mode 100644
index 00000000..dde81519
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackend.java
@@ -0,0 +1,61 @@
+/*
+ * 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.asymmetricfingerprintdialog.server;
+
+import java.security.PublicKey;
+
+/**
+ * An interface that defines the methods required for the store backend.
+ */
+public interface StoreBackend {
+
+ /**
+ * Verifies the authenticity of the provided transaction by confirming that it was signed with
+ * the private key enrolled for the userId.
+ *
+ * @param transaction the contents of the purchase transaction, its contents are
+ * signed
+ * by the
+ * private key in the client side.
+ * @param transactionSignature the signature of the transaction's contents.
+ * @return true if the signedSignature was verified, false otherwise. If this method returns
+ * true, the server can consider the transaction is successful.
+ */
+ boolean verify(Transaction transaction, byte[] transactionSignature);
+
+ /**
+ * Verifies the authenticity of the provided transaction by password.
+ *
+ * @param transaction the contents of the purchase transaction, its contents are signed by the
+ * private key in the client side.
+ * @param password the password for the user associated with the {@code transaction}.
+ * @return true if the password is verified.
+ */
+ boolean verify(Transaction transaction, String password);
+
+ /**
+ * Enrolls a public key associated with the userId
+ *
+ * @param userId the unique ID of the user within the app including server side
+ * implementation
+ * @param password the password for the user for the server side
+ * @param publicKey the public key object to verify the signature from the user
+ * @return true if the enrollment was successful, false otherwise
+ */
+ boolean enroll(String userId, String password, PublicKey publicKey);
+
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackendImpl.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackendImpl.java
new file mode 100644
index 00000000..8bf48d89
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/StoreBackendImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.asymmetricfingerprintdialog.server;
+
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A fake backend implementation of {@link StoreBackend}.
+ */
+public class StoreBackendImpl implements StoreBackend {
+
+ private final Map<String, PublicKey> mPublicKeys = new HashMap<>();
+
+ @Override
+ public boolean verify(Transaction transaction, byte[] transactionSignature) {
+ try {
+ PublicKey publicKey = mPublicKeys.get(transaction.getUserId());
+ Signature verificationFunction = Signature.getInstance("SHA256withECDSA");
+ verificationFunction.initVerify(publicKey);
+ verificationFunction.update(transaction.toByteArray());
+ if (verificationFunction.verify(transactionSignature)) {
+ // Transaction is verified with the public key associated with the user
+ // Do some post purchase processing in the server
+ return true;
+ }
+ } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
+ // In a real world, better to send some error message to the user
+ }
+ return false;
+ }
+
+ @Override
+ public boolean verify(Transaction transaction, String password) {
+ // As this is just a sample, we always assume that the password is right.
+ return true;
+ }
+
+ @Override
+ public boolean enroll(String userId, String password, PublicKey publicKey) {
+ if (publicKey != null) {
+ mPublicKeys.put(userId, publicKey);
+ }
+ // We just ignore the provided password here, but in real life, it is registered to the
+ // backend.
+ return true;
+ }
+
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/Transaction.java b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/Transaction.java
new file mode 100644
index 00000000..33b65ef8
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/java/com/example/android/asymmetricfingerprintdialog/server/Transaction.java
@@ -0,0 +1,66 @@
+/*
+ * 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.asymmetricfingerprintdialog.server;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * An entity that represents a single transaction (purchase) of an item.
+ */
+public class Transaction {
+
+ /** The unique ID of the item of the purchase */
+ private final Long mItemId;
+
+ /** The unique user ID who made the transaction */
+ private final String mUserId;
+
+ public Transaction(String userId, long itemId) {
+ mItemId = itemId;
+ mUserId = userId;
+ }
+
+ public String getUserId() {
+ return mUserId;
+ }
+
+ public byte[] toByteArray() {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ DataOutputStream dataOutputStream = null;
+ try {
+ dataOutputStream = new DataOutputStream(byteArrayOutputStream);
+ dataOutputStream.writeLong(mItemId);
+ dataOutputStream.writeUTF(mUserId);
+ return byteArrayOutputStream.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ if (dataOutputStream != null) {
+ dataOutputStream.close();
+ }
+ } catch (IOException ignore) {
+ }
+ try {
+ byteArrayOutputStream.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+}
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.png
new file mode 100644
index 00000000..48ebd8ad
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-hdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.png
new file mode 100644
index 00000000..122f4425
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-mdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.png
new file mode 100644
index 00000000..40bf934b
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-nodpi/android_robot.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.png
new file mode 100644
index 00000000..e1c9590b
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xhdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.png
new file mode 100644
index 00000000..f7e87240
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.png
new file mode 100644
index 00000000..0fb85452
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable-xxxhdpi/ic_fp_40px.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/card.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/card.xml
new file mode 100644
index 00000000..691a4c55
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/card.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <solid
+ android:color="#fefefe"/>
+
+ <corners
+ android:radius="2dp" />
+</shape> \ No newline at end of file
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml
new file mode 100644
index 00000000..be46116d
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_error.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40.0dp"
+ android:height="40.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0">
+ <path
+ android:pathData="M20.0,0.0C8.96,0.0 0.0,8.95 0.0,20.0s8.96,20.0 20.0,20.0c11.04,0.0 20.0,-8.95 20.0,-20.0S31.04,0.0 20.0,0.0z"
+ android:fillColor="#F4511E"/>
+ <path
+ android:pathData="M21.33,29.33l-2.67,0.0l0.0,-2.67l2.67,0.0L21.33,29.33zM21.33,22.67l-2.67,0.0l0.0,-12.0l2.67,0.0L21.33,22.67z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml
new file mode 100644
index 00000000..261f3e7f
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/drawable/ic_fingerprint_success.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40.0dp"
+ android:height="40.0dp"
+ android:viewportWidth="40.0"
+ android:viewportHeight="40.0">
+ <path
+ android:pathData="M20.0,20.0m-20.0,0.0a20.0,20.0 0.0,1.0 1.0,40.0 0.0a20.0,20.0 0.0,1.0 1.0,-40.0 0.0"
+ android:fillColor="#009688"/>
+ <path
+ android:pathData="M11.2,21.41l1.63,-1.619999 4.17,4.169998 10.59,-10.589999 1.619999,1.63 -12.209999,12.209999z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/activity_main.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..8f30557b
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ android:layout_marginTop="32dp"
+ android:layout_marginBottom="32dp"
+ android:layout_gravity="center_horizontal"
+ android:scaleType="fitCenter"
+ android:src="@drawable/android_robot"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:orientation="vertical"
+ android:background="@drawable/card"
+ android:elevation="4dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Headline"
+ android:text="@string/item_title"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/item_price"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body1"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/item_description"/>
+
+ </LinearLayout>
+ <Button style="@android:style/Widget.Material.Button.Colored"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="4dp"
+ android:layout_gravity="end"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ android:text="@string/purchase"
+ android:id="@+id/purchase_button"
+ android:layout_alignParentEnd="true"/>
+
+ <TextView
+ android:id="@+id/confirmation_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/purchase_done"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/encrypted_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:textAppearance="@android:style/TextAppearance.Material.Body2"
+ android:textColor="?android:attr/colorAccent"
+ android:text="@string/purchase_done"
+ android:visibility="gone"/>
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml
new file mode 100644
index 00000000..2be05b11
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_backup.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/backup_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp">
+
+ <FrameLayout
+ android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginStart="24dp"
+ android:layout_marginEnd="24dp"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/password_description"
+ android:id="@+id/password_description"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/new_fingerprint_enrolled_description"
+ android:id="@+id/new_fingerprint_enrolled_description"
+ android:visibility="gone"
+ android:textColor="?android:attr/textColorSecondary" />
+ </FrameLayout>
+
+ <EditText
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:ems="10"
+ android:hint="@string/password"
+ android:imeOptions="actionGo"
+ android:id="@+id/password"
+ android:layout_below="@+id/description"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="20dp"
+ android:layout_alignParentStart="true" />
+
+ <CheckBox
+ android:id="@+id/use_fingerprint_in_future_check"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/password"
+ android:layout_alignParentStart="true"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="20dp"
+ android:checked="true"
+ android:visibility="gone"
+ android:text="@string/use_fingerprint_in_future" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml
new file mode 100644
index 00000000..08bb1bbb
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_container.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/fingerprint_dialog_content" />
+
+ <include
+ layout="@layout/fingerprint_dialog_backup"
+ android:visibility="gone" />
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:gravity="bottom"
+ style="?android:attr/buttonBarStyle">
+
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+ <Button
+ android:id="@+id/cancel_button"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:id="@+id/second_dialog_button"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml
new file mode 100644
index 00000000..3929ebae
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/layout/fingerprint_dialog_content.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fingerprint_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="8dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:paddingTop="16dp">
+
+ <TextView
+ android:id="@+id/fingerprint_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:text="@string/fingerprint_description"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+
+ <ImageView
+ android:id="@+id/fingerprint_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@+id/fingerprint_description"
+ android:layout_marginTop="20dp"
+ android:src="@drawable/ic_fp_40px" />
+
+ <TextView
+ android:id="@+id/fingerprint_status"
+ style="@android:style/TextAppearance.Material.Body1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@+id/fingerprint_icon"
+ android:layout_alignTop="@+id/fingerprint_icon"
+ android:layout_marginStart="16dp"
+ android:layout_toEndOf="@+id/fingerprint_icon"
+ android:gravity="center_vertical"
+ android:text="@string/fingerprint_hint"
+ android:textColor="@color/hint_color" />
+</RelativeLayout> \ No newline at end of file
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/menu/menu_main.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/menu/menu_main.xml
new file mode 100644
index 00000000..73f5e89a
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/menu/menu_main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..b82d7af3
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..b0d26bae
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..135858d1
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..02960da8
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..d4254591
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/values/colors.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/values/colors.xml
new file mode 100644
index 00000000..a24f3c8f
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<resources>
+ <color name="warning_color">#f4511e</color>
+ <color name="hint_color">#42000000</color>
+ <color name="success_color">#009688</color>
+</resources>
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/values/strings.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/values/strings.xml
new file mode 100644
index 00000000..f44c06d6
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/values/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<resources>
+ <string name="action_settings">Settings</string>
+ <string name="cancel">Cancel</string>
+ <string name="use_password">Use password</string>
+ <string name="sign_in">Sign in</string>
+ <string name="ok">Ok</string>
+ <string name="password">Password</string>
+ <string name="fingerprint_description">Confirm fingerprint to continue</string>
+ <string name="fingerprint_hint">Touch sensor</string>
+ <string name="password_description">Enter your store password to continue</string>
+ <string name="purchase">Purchase</string>
+ <string name="fingerprint_not_recognized">Fingerprint not recognized. Try again</string>
+ <string name="fingerprint_success">Fingerprint recognized</string>
+ <string name="item_title">White Mesh Pluto Backpack</string>
+ <string name="item_price">$62.68</string>
+ <string name="item_description">Mesh backpack in white. Black textile trim throughout.</string>
+ <string name="purchase_done">Purchase successful</string>
+ <string name="purchase_fail">Purchase failed</string>
+ <string name="new_fingerprint_enrolled_description">A new fingerprint was added to this device, so your password is required.</string>
+ <string name="use_fingerprint_in_future">Use fingerprint in the future</string>
+ <string name="use_fingerprint_to_authenticate_title">Use fingerprint to authenticate</string>
+ <string name="use_fingerprint_to_authenticate_key" >use_fingerprint_to_authenticate_key</string>
+</resources>
diff --git a/security/AsymmetricFingerprintDialog/Application/src/main/res/xml/preferences.xml b/security/AsymmetricFingerprintDialog/Application/src/main/res/xml/preferences.xml
new file mode 100644
index 00000000..761391d5
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/main/res/xml/preferences.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <CheckBoxPreference
+ android:key="@string/use_fingerprint_to_authenticate_key"
+ android:title="@string/use_fingerprint_to_authenticate_title"
+ android:persistent="true"
+ android:defaultValue="true" />
+</PreferenceScreen> \ No newline at end of file
diff --git a/security/AsymmetricFingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java b/security/AsymmetricFingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java
new file mode 100644
index 00000000..a5a91085
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/Application/src/test/java/com/example/android/fingerprintdialog/FingerprintUiHelperTest.java
@@ -0,0 +1,111 @@
+package com.example.android.asymmetricfingerprintdialog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import android.content.res.Resources;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link FingerprintUiHelper}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class FingerprintUiHelperTest {
+
+ private static final int ERROR_MSG_ID = 1;
+ private static final CharSequence ERR_STRING = "ERROR_STRING";
+ private static final int HINT_COLOR = 10;
+
+ @Mock private FingerprintManager mockFingerprintManager;
+ @Mock private ImageView mockIcon;
+ @Mock private TextView mockTextView;
+ @Mock private FingerprintUiHelper.Callback mockCallback;
+ @Mock private FingerprintManager.CryptoObject mockCryptoObject;
+ @Mock private Resources mockResources;
+
+ @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+
+ @InjectMocks private FingerprintUiHelper.FingerprintUiHelperBuilder mockBuilder;
+
+ private FingerprintUiHelper mFingerprintUiHelper;
+
+ @Before
+ public void setUp() {
+ mFingerprintUiHelper = mockBuilder.build(mockIcon, mockTextView, mockCallback);
+
+ when(mockFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mockFingerprintManager.hasEnrolledFingerprints()).thenReturn(true);
+ when(mockTextView.getResources()).thenReturn(mockResources);
+ when(mockResources.getColor(R.color.hint_color, null)).thenReturn(HINT_COLOR);
+ }
+
+ @Test
+ public void testStartListening_fingerprintAuthAvailable() {
+ mFingerprintUiHelper.startListening(mockCryptoObject);
+
+ verify(mockFingerprintManager).authenticate(eq(mockCryptoObject),
+ isA(CancellationSignal.class), eq(0), eq(mFingerprintUiHelper),
+ any(Handler.class));
+ verify(mockIcon).setImageResource(R.drawable.ic_fp_40px);
+ }
+
+ @Test
+ public void testStartListening_fingerprintAuthNotAvailable() {
+ when(mockFingerprintManager.isHardwareDetected()).thenReturn(false);
+
+ mFingerprintUiHelper.startListening(mockCryptoObject);
+
+ verify(mockFingerprintManager, never()).authenticate(
+ any(FingerprintManager.CryptoObject.class), any(CancellationSignal.class), eq(0),
+ any(FingerprintUiHelper.class), any(Handler.class));
+ }
+
+ @Test
+ public void testOnAuthenticationError() {
+ mFingerprintUiHelper.mSelfCancelled = false;
+
+ mFingerprintUiHelper.onAuthenticationError(ERROR_MSG_ID, ERR_STRING);
+
+ verify(mockIcon).setImageResource(R.drawable.ic_fingerprint_error);
+ verify(mockTextView).removeCallbacks(mFingerprintUiHelper.mResetErrorTextRunnable);
+ verify(mockTextView).postDelayed(mFingerprintUiHelper.mResetErrorTextRunnable,
+ FingerprintUiHelper.ERROR_TIMEOUT_MILLIS);
+ verify(mockIcon).postDelayed(mRunnableArgumentCaptor.capture(),
+ eq(FingerprintUiHelper.ERROR_TIMEOUT_MILLIS));
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mockCallback).onError();
+ }
+
+ @Test
+ public void testOnAuthenticationSucceeded() {
+ mFingerprintUiHelper.onAuthenticationSucceeded(null);
+
+ verify(mockIcon).setImageResource(R.drawable.ic_fingerprint_success);
+ verify(mockTextView).removeCallbacks(mFingerprintUiHelper.mResetErrorTextRunnable);
+ verify(mockIcon).postDelayed(mRunnableArgumentCaptor.capture(),
+ eq(FingerprintUiHelper.SUCCESS_DELAY_MILLIS));
+
+ mRunnableArgumentCaptor.getValue().run();
+
+ verify(mockCallback).onAuthenticated();
+ }
+}
diff --git a/security/AsymmetricFingerprintDialog/build.gradle b/security/AsymmetricFingerprintDialog/build.gradle
new file mode 100644
index 00000000..2b8d1ef1
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/build.gradle
@@ -0,0 +1,11 @@
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+ pathToBuild "../../../../build"
+ pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/security/AsymmetricFingerprintDialog/buildSrc/build.gradle b/security/AsymmetricFingerprintDialog/buildSrc/build.gradle
new file mode 100644
index 00000000..8c294c23
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+ main {
+ groovy {
+ srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+ }
+ }
+}
+
diff --git a/security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.jar b/security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..8c0fb64a
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.properties b/security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..f943b1e7
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Apr 27 11:28:32 JST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/security/AsymmetricFingerprintDialog/gradlew b/security/AsymmetricFingerprintDialog/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/security/AsymmetricFingerprintDialog/gradlew.bat b/security/AsymmetricFingerprintDialog/gradlew.bat
new file mode 100644
index 00000000..aec99730
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/security/AsymmetricFingerprintDialog/screenshots/1-purchase-screen.png b/security/AsymmetricFingerprintDialog/screenshots/1-purchase-screen.png
new file mode 100644
index 00000000..0bf03bd0
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/screenshots/1-purchase-screen.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/screenshots/2-fingerprint-dialog.png b/security/AsymmetricFingerprintDialog/screenshots/2-fingerprint-dialog.png
new file mode 100644
index 00000000..5e681f96
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/screenshots/2-fingerprint-dialog.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/screenshots/3-fingerprint-authenticated.png b/security/AsymmetricFingerprintDialog/screenshots/3-fingerprint-authenticated.png
new file mode 100644
index 00000000..d485b1dd
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/screenshots/3-fingerprint-authenticated.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/screenshots/4-new-fingerprint-enrolled.png b/security/AsymmetricFingerprintDialog/screenshots/4-new-fingerprint-enrolled.png
new file mode 100644
index 00000000..7dcf0800
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/screenshots/4-new-fingerprint-enrolled.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/screenshots/big-icon.png b/security/AsymmetricFingerprintDialog/screenshots/big-icon.png
new file mode 100644
index 00000000..1063d0b6
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/screenshots/big-icon.png
Binary files differ
diff --git a/security/AsymmetricFingerprintDialog/settings.gradle b/security/AsymmetricFingerprintDialog/settings.gradle
new file mode 100644
index 00000000..9464a359
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/settings.gradle
@@ -0,0 +1 @@
+include 'Application'
diff --git a/security/AsymmetricFingerprintDialog/template-params.xml b/security/AsymmetricFingerprintDialog/template-params.xml
new file mode 100644
index 00000000..205f5e66
--- /dev/null
+++ b/security/AsymmetricFingerprintDialog/template-params.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 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.
+-->
+<!-- TODO(thagikura) Add tests for Activity and Fragment once InstrumentationTests can be run
+ on an emulator or a device.
+ At this moment, due to the different API between the image and the SDK, they can't be launched.
+ E.g. Skipping device 'Nexus 5 - MNC', due to different API preview 'MNC' and 'android-MNC'
+ -->
+<sample>
+ <name>Asymmetric Fingerprint Dialog Sample</name>
+ <group>security</group>
+ <package>com.example.android.asymmetricfingerprintdialog</package>
+
+ <minSdk>23</minSdk>
+ <targetSdkVersion>23</targetSdkVersion>
+ <compileSdkVersion>23</compileSdkVersion>
+
+ <dependency>com.squareup.dagger:dagger:1.2.2</dependency>
+ <dependency>com.squareup.dagger:dagger-compiler:1.2.2</dependency>
+
+ <!-- TODO(thagikura) These dependencies should be created as testCompile instead of compile but
+ the template system doesn't allow androidTestCompile dependencies. Change it once fixed.
+ -->
+ <dependency>junit:junit:4.12</dependency>
+ <dependency>org.mockito:mockito-core:1.10.19</dependency>
+
+ <strings>
+ <intro>
+ <![CDATA[
+This sample demonstrates how you can use registered fingerprints to authenticate the user
+before proceeding some actions such as purchasing an item. This version uses asymmetric keys.
+ ]]>
+ </intro>
+ </strings>
+
+ <template src="base" />
+
+ <metadata>
+ <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+ <status>DRAFT</status>
+ <categories>security</categories>
+ <technologies>Android</technologies>
+ <languages>Java</languages>
+ <solutions>Mobile</solutions>
+ <level>INTERMEDIATE</level>
+ <icon>screenshots/big-icon.png</icon>
+ <screenshots>
+ <img>screenshots/1-purchase-screen.png</img>
+ <img>screenshots/2-fingerprint-dialog.png</img>
+ <img>screenshots/3-fingerprint-authenticated.png</img>
+ <img>screenshots/4-new-fingerprint-enrolled.png</img>
+ </screenshots>
+
+ <api_refs>
+ <android>android.hardware.fingerprint.FingerprintManager</android>
+ <android>android.hardware.fingerprint.FingerprintManager.AuthenticationCallback</android>
+ <android>android.hardware.fingerprint.FingerprintManager.CryptoObject</android>
+ <android>android.security.KeyGenParameterSpec</android>
+ <android>java.security.KeyStore</android>
+ <android>java.security.Signature</android>
+ <android>java.security.KeyPairGenerator</android>
+ </api_refs>
+
+ <description>
+<![CDATA[
+A sample that demonstrates to use registered fingerprints to authenticate the user in your app
+]]>
+ </description>
+
+ <intro>
+<![CDATA[
+This sample demonstrates how you can use registered fingerprints in your app to authenticate the
+user before proceeding some actions such as purchasing an item.
+
+First you need to create an asymmetric key pair in the Android Key Store using [KeyPairGenerator][1]
+in the way that its private key can only be used after the user has authenticated with fingerprint
+and transmit the public key to your backend with the user verified password (In a real world, the
+app should show proper UIs).
+
+By setting [KeyGeneratorSpec.Builder.setUserAuthenticationRequired][2] to true, you can permit the
+use of the key only after the user authenticate it including when authenticated with the user's
+fingerprint.
+
+Then start listening to a fingerprint on the fingerprint sensor by calling
+[FingerprintManager.authenticate][3] with a [Signature][4] initialized with the asymmetric key pair
+created. Or alternatively you can fall back to server-side verified password as an authenticator.
+
+Once the fingerprint (or password) is verified, the
+[FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded()][5] callback is called.
+
+Then you can verify the purchase transaction on server side with the public key passed from the
+client, by verifying the piece of data signed by the Signature.
+
+[1]: https://developer.android.com/reference/java/security/KeyPairGenerator.html
+[2]: https://developer.android.com/reference/android/security/KeyGenParameterSpec.Builder#setUserAuthenticationRequired().html
+[3]: https://developer.android.com/reference/android/hardware/FingerprintManager#authenticate().html
+[4]: https://developer.android.com/reference/java/security/Signature.html
+[5]: https://developer.android.com/reference/android/hardware/FingerprintManager.AuthenticationCallback#onAuthenticationSucceeded().html
+]]>
+ </intro>
+ </metadata>
+</sample>