diff --git a/briar-android/build.gradle b/briar-android/build.gradle index bb4988e825110c3dd3de940a1a6a73311c485924..ef9427921fe63c69a84009ffda49f91078a66808 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -75,8 +75,8 @@ def getStdout = { command, defaultValue -> } android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 28 + buildToolsVersion '28.0.2' defaultConfig { minSdkVersion 15 diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 82d4b83cfca31940105b2a64a3b4b57544bba5b8..79eb8b41a5dd723babfa06d22499a193d1e3865f 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -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" diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java index 49286c21f9e48bfb858be32868b4955e80935736..2842ecb3e5bdbf9f8d32be6c2cba19ccf6a9ebf7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/UnlockActivity.java @@ -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); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index 7fb316a622e387df0c0b837f2a2ee857f96196ce..4cb49aac5d7a00752ef32e646fd0c32d8f10504f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -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() diff --git a/briar-android/src/main/res/layout/activity_unlock.xml b/briar-android/src/main/res/layout/activity_unlock.xml index 48413a19820d5251e01c1418ffd54fb48aedcb09..83c8d594b103d7bfd9d57dc849240888a07e9f19 100644 --- a/briar-android/src/main/res/layout/activity_unlock.xml +++ b/briar-android/src/main/res/layout/activity_unlock.xml @@ -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 diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index bdc04d7926923e84b0577bdc1b982e347dbfe1dc..722a2f50d1903ab75a89271ff9e36e967e755b5b 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -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>