mirror of
https://github.com/M66B/FairEmail.git
synced 2026-01-05 04:19:21 +01:00
Added biometric authentication
This commit is contained in:
@@ -113,6 +113,9 @@ abstract class ActivityBase extends AppCompatActivity implements SharedPreferenc
|
||||
Log.i("Contacts permission=" + contacts);
|
||||
finish();
|
||||
startActivity(getIntent());
|
||||
} else if (!this.getClass().equals(ActivityMain.class) && Helper.shouldAuthenticate(this)) {
|
||||
finish();
|
||||
startActivity(new Intent(this, ActivityMain.class));
|
||||
}
|
||||
|
||||
super.onResume();
|
||||
@@ -122,6 +125,9 @@ abstract class ActivityBase extends AppCompatActivity implements SharedPreferenc
|
||||
protected void onPause() {
|
||||
Log.i("Pause " + this.getClass().getName());
|
||||
super.onPause();
|
||||
|
||||
if (!this.getClass().equals(ActivityMain.class) && Helper.shouldAuthenticate(this))
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,19 +47,24 @@ public class ActivityMain extends AppCompatActivity implements FragmentManager.O
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean eula = prefs.getBoolean("eula", false);
|
||||
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
if (prefs.getBoolean("eula", false)) {
|
||||
if (eula) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
final SimpleTask start = new SimpleTask<Boolean>() {
|
||||
@Override
|
||||
public void run() {
|
||||
getWindow().setBackgroundDrawableResource(R.drawable.splash);
|
||||
protected void onPreExecute(Bundle args) {
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getWindow().setBackgroundDrawableResource(R.drawable.splash);
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
new SimpleTask<Boolean>() {
|
||||
@Override
|
||||
protected Boolean onExecute(Context context, Bundle args) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@@ -83,7 +88,6 @@ public class ActivityMain extends AppCompatActivity implements FragmentManager.O
|
||||
ServiceSend.boot(ActivityMain.this);
|
||||
} else
|
||||
startActivity(new Intent(ActivityMain.this, ActivitySetup.class));
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@@ -91,7 +95,24 @@ public class ActivityMain extends AppCompatActivity implements FragmentManager.O
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(getSupportFragmentManager(), ex);
|
||||
}
|
||||
}.execute(this, new Bundle(), "main:accounts");
|
||||
};
|
||||
|
||||
if (Helper.shouldAuthenticate(this))
|
||||
Helper.authenticate(ActivityMain.this, null,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
start.execute(ActivityMain.this, new Bundle(), "main:accounts");
|
||||
}
|
||||
},
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
else
|
||||
start.execute(this, new Bundle(), "main:accounts");
|
||||
} else {
|
||||
// Enable compact view on small screens
|
||||
if (!getResources().getConfiguration().isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_NORMAL))
|
||||
|
||||
@@ -201,6 +201,14 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
}
|
||||
}));
|
||||
|
||||
menus.add(new NavMenuItem(R.drawable.baseline_fingerprint_24, R.string.title_setup_biometrics, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
drawerLayout.closeDrawer(drawerContainer);
|
||||
onMenuBiometrics();
|
||||
}
|
||||
}));
|
||||
|
||||
menus.add(new NavMenuItem(R.drawable.baseline_person_24, R.string.menu_contacts, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -405,6 +413,29 @@ public class ActivitySetup extends ActivityBase implements FragmentManager.OnBac
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuBiometrics() {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivitySetup.this);
|
||||
final boolean biometrics = prefs.getBoolean("biometrics", false);
|
||||
final boolean pro = Helper.isPro(this);
|
||||
|
||||
Helper.authenticate(this, biometrics, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (pro)
|
||||
prefs.edit().putBoolean("biometrics", !biometrics).apply();
|
||||
|
||||
Toast.makeText(ActivitySetup.this,
|
||||
pro ? R.string.title_setup_done : R.string.title_pro_feature,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onMenuContacts() {
|
||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
|
||||
getSupportFragmentManager().popBackStack("contacts", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
|
||||
@@ -21,9 +21,11 @@ package eu.faircode.email;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
@@ -33,7 +35,9 @@ import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcel;
|
||||
import android.provider.Settings;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.format.DateUtils;
|
||||
@@ -54,10 +58,12 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@@ -82,10 +88,13 @@ import java.text.DateFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -129,6 +138,8 @@ public class Helper {
|
||||
}
|
||||
};
|
||||
|
||||
final static ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// Features
|
||||
|
||||
static boolean hasPermission(Context context, String name) {
|
||||
@@ -644,6 +655,92 @@ public class Helper {
|
||||
return Objects.equals(signed, expected);
|
||||
}
|
||||
|
||||
static boolean shouldAuthenticate(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean biometrics = prefs.getBoolean("biometrics", false);
|
||||
if (!biometrics)
|
||||
return false;
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
int screen_timeout = Settings.System.getInt(resolver, Settings.System.SCREEN_OFF_TIMEOUT, -1);
|
||||
Log.i("Screen timeout=" + screen_timeout);
|
||||
|
||||
long now = new Date().getTime();
|
||||
long last_authentication = prefs.getLong("last_authentication", 0);
|
||||
prefs.edit().putLong("last_authentication", now).apply();
|
||||
|
||||
return (last_authentication + screen_timeout < now);
|
||||
}
|
||||
|
||||
static void authenticate(final FragmentActivity activity,
|
||||
Boolean enabled, final
|
||||
Runnable authenticated, final Runnable cancelled) {
|
||||
final Handler handler = new Handler();
|
||||
|
||||
BiometricPrompt.PromptInfo.Builder info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(activity.getString(enabled == null ? R.string.app_name : R.string.title_setup_biometrics))
|
||||
.setNegativeButtonText(activity.getString(android.R.string.cancel));
|
||||
|
||||
if (enabled != null)
|
||||
info.setSubtitle(activity.getString(enabled
|
||||
? R.string.title_setup_biometrics_disable
|
||||
: R.string.title_setup_biometrics_enable));
|
||||
|
||||
BiometricPrompt prompt = new BiometricPrompt(activity, executor,
|
||||
new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(final int errorCode, @NonNull final CharSequence errString) {
|
||||
Log.w("Biometric error " + errorCode + ": " + errString);
|
||||
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON ||
|
||||
errorCode == BiometricPrompt.ERROR_CANCELED ||
|
||||
errorCode == BiometricPrompt.ERROR_USER_CANCELED)
|
||||
cancelled.run();
|
||||
else
|
||||
Toast.makeText(activity,
|
||||
errString + " (" + errorCode + ")",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
Log.i("Biometric succeeded");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
prefs.edit().putLong("last_authentication", new Date().getTime()).apply();
|
||||
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
authenticated.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
Log.w("Biometric failed");
|
||||
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(activity,
|
||||
R.string.title_unexpected_error,
|
||||
Toast.LENGTH_LONG).show();
|
||||
cancelled.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
prompt.authenticate(info.build());
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
static String sanitizeKeyword(String keyword) {
|
||||
|
||||
Reference in New Issue
Block a user