Commit 8ad3047f authored by akwizgran's avatar akwizgran
Browse files

Merge branch '1247-pin-lock-fingerprint' into 'master'

Implement fingerprint unlocking with BiometricPromptCompat

See merge request !882
parents 0cffaf86 24c030f0
......@@ -75,8 +75,8 @@ def getStdout = { command, defaultValue ->
}
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
compileSdkVersion 28
buildToolsVersion '28.0.2'
defaultConfig {
minSdkVersion 15
......
......@@ -17,6 +17,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
<application
android:name="org.briarproject.briar.android.BriarApplicationImpl"
......
......@@ -2,10 +2,15 @@ package org.briarproject.briar.android.login;
import android.app.KeyguardManager;
import android.content.Intent;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
import android.hardware.biometrics.BiometricPrompt.Builder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.widget.Button;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
......@@ -18,8 +23,13 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.INVISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_KEYGUARD_UNLOCK;
import static org.briarproject.briar.android.util.UiUtils.hasKeyguardLock;
import static org.briarproject.briar.android.util.UiUtils.hasUsableFingerprint;
@RequiresApi(21)
@MethodsNotNullByDefault
......@@ -45,9 +55,10 @@ public class UnlockActivity extends BaseActivity {
overridePendingTransition(0, 0);
setContentView(R.layout.activity_unlock);
Button button = findViewById(R.id.unlock);
button.setOnClickListener(view -> requestKeyguardUnlock());
if (!hasUsableFingerprint(this)) {
getWindow().setBackgroundDrawable(null);
findViewById(R.id.image).setVisibility(INVISIBLE);
}
keyguardShown = state != null && state.getBoolean(KEYGUARD_SHOWN);
}
......@@ -83,18 +94,79 @@ public class UnlockActivity extends BaseActivity {
// Check if app is still locked, lockable
// and not finishing (which is possible if recreated)
if (!keyguardShown && lockManager.isLocked() && !isFinishing()) {
requestKeyguardUnlock();
requestUnlock();
} else if (!lockManager.isLocked()) {
setResult(RESULT_OK);
finish();
}
}
private void requestUnlock() {
if (SDK_INT >= 28 && hasUsableFingerprint(this)) {
requestFingerprintUnlock();
} else {
requestKeyguardUnlock();
}
}
@Override
public void onBackPressed() {
moveTaskToBack(true);
}
@RequiresApi(api = 28)
private void requestFingerprintUnlock() {
BiometricPrompt biometricPrompt = new Builder(this)
.setTitle(getString(R.string.lock_unlock))
.setDescription(
getString(R.string.lock_unlock_fingerprint_description))
.setNegativeButton(getString(R.string.lock_unlock_password),
getMainExecutor(),
(dialog, which) -> requestKeyguardUnlock())
.build();
CancellationSignal signal = new CancellationSignal();
AuthenticationCallback callback = new AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode,
@Nullable CharSequence errString) {
// when back button is pressed while fingerprint dialog shows
if (errorCode == BIOMETRIC_ERROR_CANCELED ||
errorCode == BIOMETRIC_ERROR_USER_CANCELED) {
finish();
}
// e.g. 5 failed attempts
else {
if (hasKeyguardLock(UnlockActivity.this)) {
requestKeyguardUnlock();
} else {
// normally fingerprints require a screen lock, but
// who knows if that's true for all devices out there
if (errString != null) {
Toast.makeText(UnlockActivity.this, errString,
Toast.LENGTH_LONG).show();
}
finish();
}
}
}
@Override
public void onAuthenticationHelp(int helpCode,
@Nullable CharSequence helpString) {
}
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
unlock();
}
@Override
public void onAuthenticationFailed() {
}
};
biometricPrompt.authenticate(signal, getMainExecutor(), callback);
}
private void requestKeyguardUnlock() {
KeyguardManager keyguardManager =
(KeyguardManager) getSystemService(KEYGUARD_SERVICE);
......
......@@ -14,6 +14,7 @@ import android.support.annotation.ColorRes;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v7.app.AlertDialog;
import android.text.Html;
import android.text.Spannable;
......@@ -261,6 +262,10 @@ public class UiUtils {
}
public static boolean hasScreenLock(Context ctx) {
return hasKeyguardLock(ctx) || hasUsableFingerprint(ctx);
}
public static boolean hasKeyguardLock(Context ctx) {
if (SDK_INT < 21) return false;
KeyguardManager keyguardManager =
(KeyguardManager) ctx.getSystemService(KEYGUARD_SERVICE);
......@@ -271,6 +276,12 @@ public class UiUtils {
(SDK_INT >= 23 && keyguardManager.isDeviceSecure());
}
public static boolean hasUsableFingerprint(Context ctx) {
if (SDK_INT < 28) return false;
FingerprintManagerCompat fm = FingerprintManagerCompat.from(ctx);
return fm.hasEnrolledFingerprints() && fm.isHardwareDetected();
}
public static void triggerFeedback(AndroidExecutor androidExecutor) {
androidExecutor.runOnBackgroundThread(
() -> ACRA.getErrorReporter()
......
......@@ -14,36 +14,11 @@
android:layout_height="150dp"
android:layout_margin="@dimen/margin_large"
android:src="@drawable/splash_screen"
app:layout_constraintBottom_toTopOf="@+id/is_locked"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintVertical_bias="0.1"
app:tint="?attr/colorControlNormal"/>
<TextView
android:id="@+id/is_locked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_large"
android:gravity="center"
android:text="@string/lock_is_locked"
android:textSize="@dimen/text_size_xlarge"
app:layout_constraintBottom_toTopOf="@+id/unlock"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image"
app:layout_constraintVertical_chainStyle="spread"/>
<Button
android:id="@+id/unlock"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_large"
android:text="@string/lock_unlock"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -475,6 +475,8 @@
<!-- App Locking -->
<string name="lock_unlock">Unlock Briar</string>
<string name="lock_unlock_verbose">Enter your device PIN, pattern or password to unlock Briar</string>
<string name="lock_unlock_fingerprint_description">Touch your fingerprint sensor with the registered finger to continue</string>
<string name="lock_unlock_password">Use Password</string>
<string name="lock_is_locked">Briar is locked</string>
<string name="lock_tap_to_unlock">Tap to unlock</string>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment