Skip to content

Commit

Permalink
Allow to unlock with biometric authentication
Browse files Browse the repository at this point in the history
Issue: #11
  • Loading branch information
2bllw8 committed Jan 28, 2023
1 parent d1f4a6f commit a2f6492
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 4 deletions.
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="exe.bbllw8.anemo">

<uses-permission android:name="android.permission.USE_BIOMETRIC" />

<application
android:allowBackup="true"
android:allowClearUserData="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Switch;
import android.widget.TextView;

Expand All @@ -25,6 +26,7 @@ public final class ConfigurationActivity extends Activity {

private TextView passwordSetView;
private TextView changeLockView;
private Switch biometricSwitch;

private LockStore lockStore;

Expand All @@ -44,8 +46,6 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
shortcutSwitch.setOnCheckedChangeListener(
(v, isChecked) -> AnemoShell.setEnabled(getApplication(), isChecked));

setupPasswordViews();

changeLockView = findViewById(R.id.configuration_lock);
changeLockView.setText(lockStore.isLocked()
? R.string.configuration_storage_unlock
Expand All @@ -62,6 +62,15 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
autoLockSwitch.setChecked(lockStore.isAutoLockEnabled());
autoLockSwitch.setOnCheckedChangeListener(
(v, isChecked) -> lockStore.setAutoLockEnabled(isChecked));

biometricSwitch = findViewById(R.id.configuration_biometric_unlock);
biometricSwitch
.setVisibility(lockStore.canAuthenticateBiometric() ? View.VISIBLE : View.GONE);
biometricSwitch.setChecked(lockStore.isBiometricUnlockEnabled());
biometricSwitch.setOnCheckedChangeListener(
(v, isChecked) -> lockStore.setBiometricUnlockEnabled(isChecked));

setupPasswordViews();
}

@Override
Expand All @@ -81,11 +90,14 @@ private void setupPasswordViews() {
passwordSetView.setOnClickListener(
$ -> new SetPasswordDialog(this, lockStore, this::setupPasswordViews).show());
}
passwordSetView.setEnabled(!lockStore.isLocked());
final boolean enableViews = !lockStore.isLocked();
passwordSetView.setEnabled(enableViews);
biometricSwitch.setEnabled(enableViews);
}

private final Consumer<Boolean> onLockChanged = isLocked -> {
passwordSetView.setEnabled(!isLocked);
biometricSwitch.setEnabled(!isLocked);
changeLockView.setText(isLocked
? R.string.configuration_storage_unlock
: R.string.configuration_storage_lock);
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/exe/bbllw8/anemo/lock/LockStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.biometrics.BiometricManager;
import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;
Expand All @@ -28,6 +30,7 @@ public final class LockStore implements SharedPreferences.OnSharedPreferenceChan
private static final String KEY_LOCK = "is_locked";
private static final String KEY_PASSWORD = "password_hash";
private static final String KEY_AUTO_LOCK = "auto_lock";
private static final String KEY_BIOMETRIC_UNLOCK = "biometric_unlock";
private static final boolean DEFAULT_LOCK_VALUE = false;
private static final boolean DEFAULT_AUTO_LOCK_VALUE = false;

Expand All @@ -39,6 +42,7 @@ public final class LockStore implements SharedPreferences.OnSharedPreferenceChan

private final SharedPreferences preferences;
private final List<Consumer<Boolean>> listeners = new ArrayList<>();
private final BiometricManager biometricManager;

private final JobScheduler jobScheduler;
private final ComponentName autoLockComponent;
Expand All @@ -61,6 +65,9 @@ private LockStore(Context context) {
preferences.registerOnSharedPreferenceChangeListener(this);

jobScheduler = context.getSystemService(JobScheduler.class);
biometricManager = Build.VERSION.SDK_INT >= 29
? context.getSystemService(BiometricManager.class)
: null;
autoLockComponent = new ComponentName(context, AutoLockJobService.class);
}

Expand Down Expand Up @@ -134,6 +141,19 @@ public synchronized void setAutoLockEnabled(boolean enabled) {
}
}

public boolean canAuthenticateBiometric() {
return Build.VERSION.SDK_INT >= 29 && biometricManager != null
&& biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS;
}

public synchronized boolean isBiometricUnlockEnabled() {
return canAuthenticateBiometric() && preferences.getBoolean(KEY_BIOMETRIC_UNLOCK, false);
}

public synchronized void setBiometricUnlockEnabled(boolean enabled) {
preferences.edit().putBoolean(KEY_BIOMETRIC_UNLOCK, enabled).apply();
}

public void addListener(Consumer<Boolean> listener) {
synchronized (listeners) {
listeners.add(listener);
Expand Down
40 changes: 39 additions & 1 deletion app/src/main/java/exe/bbllw8/anemo/lock/UnlockActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@

import android.app.Activity;
import android.content.Intent;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.util.concurrent.Executor;

import exe.bbllw8.anemo.R;
import exe.bbllw8.anemo.config.ConfigurationActivity;
Expand All @@ -32,7 +37,11 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
lockStore = LockStore.getInstance(this);
onUnlocked = getOnUnlocked(getIntent());
if (lockStore.hasPassword()) {
setupUI();
if (lockStore.isBiometricUnlockEnabled()) {
unlockViaBiometricAuthentication();
} else {
setupUI();
}
} else {
doUnlock();
}
Expand All @@ -52,6 +61,7 @@ private void setupUI() {

configBtn.setOnClickListener(v -> {
startActivity(new Intent(this, ConfigurationActivity.class));
setResult(Activity.RESULT_CANCELED);
finish();
});

Expand All @@ -76,6 +86,34 @@ private void doUnlock() {
onUnlocked.run();
}

@RequiresApi(29)
private void unlockViaBiometricAuthentication() {
final Executor executor = getMainExecutor();
final CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(this::finish);

final BiometricPrompt prompt = new BiometricPrompt.Builder(this)
.setTitle(getString(R.string.tile_unlock))
.setDescription(getString(R.string.password_input_biometric_message))
.setNegativeButton(getString(R.string.password_input_biometric_fallback), executor,
(dialog, which) -> setupUI())
.build();
prompt.authenticate(cancellationSignal, executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
doUnlock();
}

@Override
public void onAuthenticationFailed() {
setResult(Activity.RESULT_CANCELED);
finish();
}
});
}

private Runnable getOnUnlocked(Intent intent) {
if (intent.getBooleanExtra(OPEN_AFTER_UNLOCK, false)) {
return () -> {
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/layout/configuration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,15 @@
android:paddingHorizontal="16dp"
android:text="@string/configuration_storage_lock_auto"
android:textSize="16sp" />

<Switch
android:id="@+id/configuration_biometric_unlock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:minHeight="56dp"
android:paddingHorizontal="16dp"
android:text="@string/configuration_storage_unlock_biometric"
android:textSize="16sp" />
</LinearLayout>
</ScrollView>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

<string name="password_input_message">Type your Anemo password to unlock the Anemo Storage</string>
<string name="password_input_action">Unlock</string>
<string name="password_input_biometric_message">Authenticate to unlock the Anemo Storage</string>
<string name="password_input_biometric_fallback">Use password</string>

<string name="password_set_title">Add password protection</string>
<string name="password_set_message">Set a password to lock the access to the Anemo Storage</string>
Expand Down Expand Up @@ -59,5 +61,6 @@
<string name="configuration_password_change">Change password</string>
<string name="configuration_storage_lock">Lock storage access</string>
<string name="configuration_storage_unlock">Unlock storage access</string>
<string name="configuration_storage_unlock_biometric">Allow storage unlock with biometric access</string>
<string name="configuration_storage_lock_auto">Automatically lock access after 15 minutes</string>
</resources>

0 comments on commit a2f6492

Please sign in to comment.