diff --git a/client/android/app/build.gradle b/client/android/app/build.gradle
index 38c23a189..d936d41a0 100644
--- a/client/android/app/build.gradle
+++ b/client/android/app/build.gradle
@@ -56,6 +56,7 @@ dependencies {
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation "androidx.browser:browser:$androidxBrowserVersion"
+ implementation "androidx.biometric:biometric:1.1.0"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
diff --git a/client/android/app/src/main/java/com/liketrek/trek/TrekShellPlugin.java b/client/android/app/src/main/java/com/liketrek/trek/TrekShellPlugin.java
index a12975fcd..b5ee2ec61 100644
--- a/client/android/app/src/main/java/com/liketrek/trek/TrekShellPlugin.java
+++ b/client/android/app/src/main/java/com/liketrek/trek/TrekShellPlugin.java
@@ -31,7 +31,7 @@ public class TrekShellPlugin extends Plugin {
*/
static final int SHELL_API = 2;
/** The native features by name, so the UI can turn each on by itself. */
- static final String[] FEATURES = { "capabilities" };
+ static final String[] FEATURES = { "capabilities", "biometricLock" };
@PluginMethod
public void getCapabilities(PluginCall call) {
@@ -55,6 +55,72 @@ public class TrekShellPlugin extends Plugin {
call.resolve(result);
}
+ // ---- App lock ----
+
+ private static final int AUTHENTICATORS =
+ androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
+ | androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+
+ /** Which biometry the device offers for the app lock, if any. */
+ @PluginMethod
+ public void biometryInfo(PluginCall call) {
+ int status = androidx.biometric.BiometricManager.from(getContext()).canAuthenticate(AUTHENTICATORS);
+ JSObject result = new JSObject();
+ result.put("available", status == androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS);
+ result.put("type", "biometric");
+ call.resolve(result);
+ }
+
+ /** The system biometric prompt with the screen lock as the way out. */
+ @PluginMethod
+ public void authenticateBiometric(PluginCall call) {
+ String reason = call.getString("reason", "Unlock TREK");
+ getActivity().runOnUiThread(() -> {
+ androidx.fragment.app.FragmentActivity activity = (androidx.fragment.app.FragmentActivity) getActivity();
+ androidx.biometric.BiometricPrompt prompt = new androidx.biometric.BiometricPrompt(
+ activity,
+ androidx.core.content.ContextCompat.getMainExecutor(getContext()),
+ new androidx.biometric.BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationSucceeded(androidx.biometric.BiometricPrompt.AuthenticationResult result) {
+ JSObject ok = new JSObject();
+ ok.put("ok", true);
+ call.resolve(ok);
+ }
+
+ @Override
+ public void onAuthenticationError(int code, CharSequence message) {
+ boolean cancelled = code == androidx.biometric.BiometricPrompt.ERROR_USER_CANCELED
+ || code == androidx.biometric.BiometricPrompt.ERROR_NEGATIVE_BUTTON
+ || code == androidx.biometric.BiometricPrompt.ERROR_CANCELED;
+ call.reject(message.toString(), cancelled ? "CANCELLED" : "FAILED");
+ }
+ }
+ );
+ androidx.biometric.BiometricPrompt.PromptInfo info = new androidx.biometric.BiometricPrompt.PromptInfo.Builder()
+ .setTitle(reason)
+ .setAllowedAuthenticators(AUTHENTICATORS)
+ .build();
+ prompt.authenticate(info);
+ });
+ }
+
+ /** While on, the recent-apps screen shows no snapshot of the app. */
+ @PluginMethod
+ public void setPrivacyCover(PluginCall call) {
+ boolean enabled = Boolean.TRUE.equals(call.getBoolean("enabled", false));
+ getActivity().runOnUiThread(() -> {
+ if (android.os.Build.VERSION.SDK_INT >= 33) {
+ getActivity().setRecentsScreenshotEnabled(!enabled);
+ } else if (enabled) {
+ getActivity().getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_SECURE);
+ } else {
+ getActivity().getWindow().clearFlags(android.view.WindowManager.LayoutParams.FLAG_SECURE);
+ }
+ });
+ call.resolve();
+ }
+
@PluginMethod
public void getServer(PluginCall call) {
String url = TrekServerStore.getUrl(getContext());
diff --git a/client/ios/App/App/Info.plist b/client/ios/App/App/Info.plist
index b4d095119..88d841452 100644
--- a/client/ios/App/App/Info.plist
+++ b/client/ios/App/App/Info.plist
@@ -71,6 +71,8 @@
NSPhotoLibraryAddUsageDescription
TREK saves photos and exports you download from your trips back to your photo library.
+ NSFaceIDUsageDescription
+ TREK uses Face ID to unlock the app when you turn on the app lock.
NSLocationWhenInUseUsageDescription
TREK uses your location to centre the map on you and to find places near you.