/* * Copyright (C) 2015 The Android Open Source Project * Modifications copyright (C) 2016 Niklas Merz * * 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 de.niklasmerz.cordova.fingerprint; import android.app.DialogFragment; import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /** * 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 FingerprintUiHelper.Callback { private static final String TAG = "FingerprintAuthDialog"; private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1; private Button mCancelButton; private Button mSecondDialogButton; private View mFingerprintContent; private Stage mStage = Stage.FINGERPRINT; private KeyguardManager mKeyguardManager; private FingerprintManager.CryptoObject mCryptoObject; private FingerprintUiHelper mFingerprintUiHelper; FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder; boolean disableBackup; 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); mKeyguardManager = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); mFingerprintUiHelperBuilder = new FingerprintUiHelper.FingerprintUiHelperBuilder( getContext(), getContext().getSystemService(FingerprintManager.class)); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle args = getArguments(); disableBackup = args.getBoolean("disableBackup"); Log.d(TAG, "disableBackup: " + disableBackup); int fingerprint_auth_dialog_title_id = getResources() .getIdentifier("fingerprint_auth_dialog_title", "string", Fingerprint.packageName); getDialog().setTitle(getString(fingerprint_auth_dialog_title_id)); int fingerprint_dialog_container_id = getResources() .getIdentifier("fingerprint_dialog_container", "layout", Fingerprint.packageName); View v = inflater.inflate(fingerprint_dialog_container_id, container, false); int cancel_button_id = getResources() .getIdentifier("cancel_button", "id", Fingerprint.packageName); mCancelButton = (Button) v.findViewById(cancel_button_id); mCancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Fingerprint.onCancelled(); dismiss(); } }); int second_dialog_button_id = getResources() .getIdentifier("second_dialog_button", "id", Fingerprint.packageName); mSecondDialogButton = (Button) v.findViewById(second_dialog_button_id); if (disableBackup) { mSecondDialogButton.setVisibility(View.GONE); } mSecondDialogButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { goToBackup(); } }); int fingerprint_container_id = getResources() .getIdentifier("fingerprint_container", "id", Fingerprint.packageName); mFingerprintContent = v.findViewById(fingerprint_container_id); int new_fingerprint_enrolled_description_id = getResources() .getIdentifier("new_fingerprint_enrolled_description", "id", Fingerprint.packageName); int fingerprint_icon_id = getResources() .getIdentifier("fingerprint_icon", "id", Fingerprint.packageName); int fingerprint_status_id = getResources() .getIdentifier("fingerprint_status", "id", Fingerprint.packageName); mFingerprintUiHelper = mFingerprintUiHelperBuilder.build( (ImageView) v.findViewById(fingerprint_icon_id), (TextView) v.findViewById(fingerprint_status_id), 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(); } /** * 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() { if(disableBackup) { Fingerprint.onCancelled(); dismiss(); } else{ mStage = Stage.BACKUP; updateStage(); } } private void updateStage() { int cancel_id = getResources() .getIdentifier("fingerprint_cancel", "string", Fingerprint.packageName); switch (mStage) { case FINGERPRINT: mCancelButton.setText(cancel_id); int use_backup_id = getResources() .getIdentifier("fingerprint_use_backup", "string", Fingerprint.packageName); mSecondDialogButton.setText(use_backup_id); mFingerprintContent.setVisibility(View.VISIBLE); break; case NEW_FINGERPRINT_ENROLLED: // Intentional fall through case BACKUP: if (mStage == Stage.NEW_FINGERPRINT_ENROLLED) { } if (!mKeyguardManager.isKeyguardSecure()) { // Show a message that the user hasn't set up a lock screen. int secure_lock_screen_required_id = getResources() .getIdentifier("secure_lock_screen_required", "string", Fingerprint.packageName); Toast.makeText(getContext(), getString(secure_lock_screen_required_id), Toast.LENGTH_LONG).show(); return; } showAuthenticationScreen(); break; } } private void showAuthenticationScreen() { // Create the Confirm Credentials screen. You can customize the title and description. Or // we will provide a generic one for you if you leave it null Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); if (intent != null) { startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { // Challenge completed, proceed with using cipher if (resultCode == getActivity().RESULT_OK) { Fingerprint.onAuthenticated(false /* used backup */); } else { // The user canceled or didn’t complete the lock screen // operation. Go to error/cancellation flow. Fingerprint.onCancelled(); } dismiss(); } } @Override public void onAuthenticated() { // Callback from FingerprintUiHelper. Let the activity know that authentication was // successful. Fingerprint.onAuthenticated(true /* withFingerprint */); dismiss(); } @Override public void onError() { if(this.getActivity() != null) goToBackup(); } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); Fingerprint.onCancelled(); } /** * Enumeration to indicate which authentication method the user is trying to authenticate with. */ public enum Stage { FINGERPRINT, NEW_FINGERPRINT_ENROLLED, BACKUP } }