diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 6237a890ed..6f93e68624 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -2643,204 +2643,209 @@ public class Helper { static void authenticate(final FragmentActivity activity, final LifecycleOwner owner, Boolean enabled, final Runnable authenticated, final Runnable cancelled) { - Log.i("Authenticate " + activity); + // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android12-release/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java#195 + ApplicationEx.getMainHandler().post(new RunnableEx("authenticate") { + @Override + public void delegate() { + Log.i("Authenticate " + activity); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); - String pin = prefs.getString("pin", null); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + String pin = prefs.getString("pin", null); - if (enabled != null || TextUtils.isEmpty(pin)) { - Log.i("Authenticate biometric enabled=" + enabled); - BiometricPrompt.PromptInfo.Builder info = new BiometricPrompt.PromptInfo.Builder() - .setTitle(activity.getString(enabled == null ? R.string.app_name : R.string.title_setup_biometrics)); + if (enabled != null || TextUtils.isEmpty(pin)) { + Log.i("Authenticate biometric enabled=" + enabled); + BiometricPrompt.PromptInfo.Builder info = new BiometricPrompt.PromptInfo.Builder() + .setTitle(activity.getString(enabled == null ? R.string.app_name : R.string.title_setup_biometrics)); - KeyguardManager kgm = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && kgm != null && kgm.isDeviceSecure()) - info.setDeviceCredentialAllowed(true); - else - info.setNegativeButtonText(activity.getString(android.R.string.cancel)); - - info.setConfirmationRequired(false); + KeyguardManager kgm = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && kgm != null && kgm.isDeviceSecure()) + info.setDeviceCredentialAllowed(true); + else + info.setNegativeButtonText(activity.getString(android.R.string.cancel)); - info.setSubtitle(activity.getString(enabled == null ? R.string.title_setup_biometrics_unlock - : enabled - ? R.string.title_setup_biometrics_disable - : R.string.title_setup_biometrics_enable)); + info.setConfirmationRequired(false); - final BiometricPrompt prompt = new BiometricPrompt(activity, executor, - new BiometricPrompt.AuthenticationCallback() { - private int fails = 0; + info.setSubtitle(activity.getString(enabled == null ? R.string.title_setup_biometrics_unlock + : enabled + ? R.string.title_setup_biometrics_disable + : R.string.title_setup_biometrics_enable)); - @Override - public void onAuthenticationError(final int errorCode, @NonNull final CharSequence errString) { - if (isCancelled(errorCode) || errorCode == BiometricPrompt.ERROR_UNABLE_TO_PROCESS) - Log.w("Authenticate biometric error " + errorCode + ": " + errString); - else - Log.e("Authenticate biometric error " + errorCode + ": " + errString); + final BiometricPrompt prompt = new BiometricPrompt(activity, executor, + new BiometricPrompt.AuthenticationCallback() { + private int fails = 0; - if (isHardwareFailure(errorCode)) { - prefs.edit().remove("biometrics").apply(); - ApplicationEx.getMainHandler().post(authenticated); - return; - } + @Override + public void onAuthenticationError(final int errorCode, @NonNull final CharSequence errString) { + if (isCancelled(errorCode) || errorCode == BiometricPrompt.ERROR_UNABLE_TO_PROCESS) + Log.w("Authenticate biometric error " + errorCode + ": " + errString); + else + Log.e("Authenticate biometric error " + errorCode + ": " + errString); - if (!isCancelled(errorCode)) - ApplicationEx.getMainHandler().post(new RunnableEx("auth:error") { - @Override - public void delegate() { - ToastEx.makeText(activity, - "Error " + errorCode + ": " + errString, - Toast.LENGTH_LONG).show(); + if (isHardwareFailure(errorCode)) { + prefs.edit().remove("biometrics").apply(); + ApplicationEx.getMainHandler().post(authenticated); + return; } - }); - - ApplicationEx.getMainHandler().post(cancelled); - } - - @Override - public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { - Log.i("Authenticate biometric succeeded"); - setAuthenticated(activity); - ApplicationEx.getMainHandler().post(authenticated); - } - - @Override - public void onAuthenticationFailed() { - Log.w("Authenticate biometric failed"); - if (++fails >= 3) - ApplicationEx.getMainHandler().post(cancelled); - } - - private boolean isCancelled(int errorCode) { - return (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON || - errorCode == BiometricPrompt.ERROR_CANCELED || - errorCode == BiometricPrompt.ERROR_USER_CANCELED); - } - private boolean isHardwareFailure(int errorCode) { - return (errorCode == BiometricPrompt.ERROR_HW_UNAVAILABLE || - errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS || // No fingerprints enrolled. - errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT || - errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL); - } - }); - - prompt.authenticate(info.build()); + if (!isCancelled(errorCode)) + ApplicationEx.getMainHandler().post(new RunnableEx("auth:error") { + @Override + public void delegate() { + ToastEx.makeText(activity, + "Error " + errorCode + ": " + errString, + Toast.LENGTH_LONG).show(); + } + }); + + ApplicationEx.getMainHandler().post(cancelled); + } - final Runnable cancelPrompt = new RunnableEx("auth:cancelprompt") { - @Override - public void delegate() { - try { - prompt.cancelAuthentication(); - } catch (Throwable ex) { - Log.e(ex); - } - } - }; + @Override + public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { + Log.i("Authenticate biometric succeeded"); + setAuthenticated(activity); + ApplicationEx.getMainHandler().post(authenticated); + } - ApplicationEx.getMainHandler().postDelayed(cancelPrompt, 60 * 1000L); + @Override + public void onAuthenticationFailed() { + Log.w("Authenticate biometric failed"); + if (++fails >= 3) + ApplicationEx.getMainHandler().post(cancelled); + } - owner.getLifecycle().addObserver(new LifecycleObserver() { - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - public void onDestroy() { - Log.i("Authenticate destroyed"); - ApplicationEx.getMainHandler().removeCallbacks(cancelPrompt); - try { - prompt.cancelAuthentication(); - } catch (Throwable ex) { - Log.e(ex); - } - owner.getLifecycle().removeObserver(this); - } - }); + private boolean isCancelled(int errorCode) { + return (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON || + errorCode == BiometricPrompt.ERROR_CANCELED || + errorCode == BiometricPrompt.ERROR_USER_CANCELED); + } - } else { - Log.i("Authenticate PIN"); - final View dview = LayoutInflater.from(activity).inflate(R.layout.dialog_pin_ask, null); - final EditText etPin = dview.findViewById(R.id.etPin); + private boolean isHardwareFailure(int errorCode) { + return (errorCode == BiometricPrompt.ERROR_HW_UNAVAILABLE || + errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS || // No fingerprints enrolled. + errorCode == BiometricPrompt.ERROR_HW_NOT_PRESENT || + errorCode == BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL); + } + }); - etPin.setEnabled(false); + prompt.authenticate(info.build()); - final AlertDialog dialog = new AlertDialog.Builder(activity) - .setView(dview) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + final Runnable cancelPrompt = new RunnableEx("auth:cancelprompt") { @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); - String pin = prefs.getString("pin", ""); - String entered = etPin.getText().toString(); - - Log.i("Authenticate PIN ok=" + pin.equals(entered)); - if (pin.equals(entered)) { - prefs.edit() - .remove("pin_failure_at") - .remove("pin_failure_count") - .apply(); - setAuthenticated(activity); - ApplicationEx.getMainHandler().post(authenticated); - } else { - int count = prefs.getInt("pin_failure_count", 0) + 1; - prefs.edit() - .putLong("pin_failure_at", new Date().getTime()) - .putInt("pin_failure_count", count) - .apply(); - ApplicationEx.getMainHandler().post(cancelled); + public void delegate() { + try { + Log.i("Authenticate cancel prompt"); + prompt.cancelAuthentication(); + } catch (Throwable ex) { + Log.e(ex); } } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Log.i("Authenticate PIN cancelled"); - ApplicationEx.getMainHandler().post(cancelled); - } - }) - .setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - Log.i("Authenticate PIN dismissed"); - if (shouldAuthenticate(activity, false)) // Some Android versions call dismiss on OK - ApplicationEx.getMainHandler().post(cancelled); + }; + + ApplicationEx.getMainHandler().postDelayed(cancelPrompt, 60 * 1000L); + + owner.getLifecycle().addObserver(new LifecycleObserver() { + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + public void onDestroy() { + Log.i("Authenticate destroyed"); + ApplicationEx.getMainHandler().removeCallbacks(cancelPrompt); + try { + prompt.cancelAuthentication(); + } catch (Throwable ex) { + Log.e(ex); + } + owner.getLifecycle().removeObserver(this); } - }) - .create(); + }); - etPin.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); - return true; - } else - return false; - } - }); + } else { + Log.i("Authenticate PIN"); + final View dview = LayoutInflater.from(activity).inflate(R.layout.dialog_pin_ask, null); + final EditText etPin = dview.findViewById(R.id.etPin); + + etPin.setEnabled(false); + + final AlertDialog dialog = new AlertDialog.Builder(activity) + .setView(dview) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + String pin = prefs.getString("pin", ""); + String entered = etPin.getText().toString(); + + Log.i("Authenticate PIN ok=" + pin.equals(entered)); + if (pin.equals(entered)) { + prefs.edit() + .remove("pin_failure_at") + .remove("pin_failure_count") + .apply(); + setAuthenticated(activity); + ApplicationEx.getMainHandler().post(authenticated); + } else { + int count = prefs.getInt("pin_failure_count", 0) + 1; + prefs.edit() + .putLong("pin_failure_at", new Date().getTime()) + .putInt("pin_failure_count", count) + .apply(); + ApplicationEx.getMainHandler().post(cancelled); + } + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Log.i("Authenticate PIN cancelled"); + ApplicationEx.getMainHandler().post(cancelled); + } + }) + .setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + Log.i("Authenticate PIN dismissed"); + if (shouldAuthenticate(activity, false)) // Some Android versions call dismiss on OK + ApplicationEx.getMainHandler().post(cancelled); + } + }) + .create(); - try { - dialog.show(); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - - long pin_failure_at = prefs.getLong("pin_failure_at", 0); - int pin_failure_count = prefs.getInt("pin_failure_count", 0); - long wait = (long) Math.pow(PIN_FAILURE_DELAY, pin_failure_count) * 1000L; - long delay = pin_failure_at + wait - new Date().getTime(); - Log.i("PIN wait=" + wait + " delay=" + delay); - dview.postDelayed(new Runnable() { - @Override - public void run() { - try { - etPin.setCompoundDrawables(null, null, null, null); - etPin.setEnabled(true); - etPin.requestFocus(); - showKeyboard(etPin); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); - } catch (Throwable ex) { - Log.e(ex); + etPin.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + return true; + } else + return false; } - } - }, delay < 0 ? 0 : delay); + }); - } catch (Throwable ex) { - Log.e(ex); + try { + dialog.show(); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + + long pin_failure_at = prefs.getLong("pin_failure_at", 0); + int pin_failure_count = prefs.getInt("pin_failure_count", 0); + long wait = (long) Math.pow(PIN_FAILURE_DELAY, pin_failure_count) * 1000L; + long delay = pin_failure_at + wait - new Date().getTime(); + Log.i("PIN wait=" + wait + " delay=" + delay); + dview.postDelayed(new Runnable() { + @Override + public void run() { + try { + etPin.setCompoundDrawables(null, null, null, null); + etPin.setEnabled(true); + etPin.requestFocus(); + showKeyboard(etPin); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); + } catch (Throwable ex) { + Log.e(ex); + } + } + }, delay < 0 ? 0 : delay); + + } catch (Throwable ex) { + Log.e(ex); /* java.lang.RuntimeException: Unable to start activity ComponentInfo{eu.faircode.email/eu.faircode.email.ActivityMain}: java.lang.RuntimeException: InputChannel is not initialized. at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3477) @@ -2867,8 +2872,10 @@ public class Helper { at eu.faircode.email.ActivityMain.onCreate(SourceFile:24) at android.app.Activity.performCreate(Activity.java:7822) */ + } + } } - } + }); } static void setAuthenticated(Context context) {