Skip to content

Commit

Permalink
feat: option to require biometric authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
Bnyro committed Apr 16, 2024
1 parent 39db4cf commit f943c58
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 15 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ dependencies {
// Phone number formatting
implementation("com.googlecode.libphonenumber:libphonenumber:8.2.0")

// Biometrics
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha05")

// Markdown support for notes
implementation("com.halilibo.compose-richtext:richtext-ui-material3:0.17.0")
implementation("com.halilibo.compose-richtext:richtext-commonmark:0.17.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Telephony
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
import com.bnyro.contacts.ui.models.ContactsModel
Expand All @@ -17,7 +17,7 @@ import com.bnyro.contacts.ui.models.ThemeModel
import com.bnyro.contacts.util.NotificationHelper
import com.bnyro.contacts.util.PermissionHelper

abstract class BaseActivity : ComponentActivity() {
abstract class BaseActivity : FragmentActivity() {
lateinit var themeModel: ThemeModel
val contactsModel by viewModels<ContactsModel> {
ContactsModel.Factory
Expand Down
38 changes: 32 additions & 6 deletions app/src/main/java/com/bnyro/contacts/ui/activities/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ package com.bnyro.contacts.ui.activities

import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.ContactsContract.Intents
import android.provider.ContactsContract.QuickContact
import androidx.activity.compose.setContent
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.bnyro.contacts.ext.parcelable
import com.bnyro.contacts.nav.NavContainer
import com.bnyro.contacts.obj.ContactData
import com.bnyro.contacts.obj.ValueWithType
import com.bnyro.contacts.ui.components.ConfirmImportContactsDialog
import com.bnyro.contacts.ui.components.dialogs.AddToContactDialog
import com.bnyro.contacts.ui.theme.ConnectYouTheme
import com.bnyro.contacts.util.BackupHelper
import com.bnyro.contacts.util.BiometricAuthUtil
import com.bnyro.contacts.util.ContactsHelper
import com.bnyro.contacts.util.Preferences
import com.bnyro.contacts.util.IntentHelper
Expand All @@ -38,12 +45,31 @@ class MainActivity : BaseActivity() {
?: Preferences.getInt(Preferences.homeTabKey, 0)
setContent {
ConnectYouTheme(themeModel.themeMode) {
NavContainer(initialTabIndex)
getInsertOrEditNumber()?.let {
AddToContactDialog(it)
val context = LocalContext.current

var authSuccess by rememberSaveable {
mutableStateOf(!Preferences.getBoolean(Preferences.biometricAuthKey, false))
}

if (authSuccess) {
NavContainer(initialTabIndex)
getInsertOrEditNumber()?.let {
AddToContactDialog(it)
}
getSharedVcfUri()?.let {
ConfirmImportContactsDialog(contactsModel, it)
}
}
getSharedVcfUri()?.let {
ConfirmImportContactsDialog(contactsModel, it)

LaunchedEffect(Unit) {
if (authSuccess) return@LaunchedEffect

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
BiometricAuthUtil.requestAuth(context) { success ->
if (success) authSuccess = true
else finish()
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ import androidx.compose.ui.unit.sp
import com.bnyro.contacts.util.rememberPreference

@Composable
fun SwitchPref(
prefKey: String,
fun SwitchPrefBase(
title: String,
summary: String? = null,
defaultValue: Boolean = false,
onCheckedChange: (Boolean) -> Unit = {}
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
var checked by rememberPreference(key = prefKey, defaultValue = defaultValue)
val interactionSource = remember { MutableInteractionSource() }

Row(
Expand All @@ -39,7 +37,7 @@ fun SwitchPref(
interactionSource = interactionSource,
indication = null
) {
checked = !checked
onCheckedChange(!checked)
},
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
Expand All @@ -62,9 +60,24 @@ fun SwitchPref(
Switch(
checked = checked,
onCheckedChange = {
checked = it
onCheckedChange.invoke(it)
}
)
}
}

@Composable
fun SwitchPref(
prefKey: String,
title: String,
summary: String? = null,
defaultValue: Boolean = false,
onCheckedChange: (Boolean) -> Unit = {}
) {
var checked by rememberPreference(key = prefKey, defaultValue = defaultValue)

SwitchPrefBase(title = title, summary = summary, checked = checked) {
checked = it
onCheckedChange.invoke(it)
}
}
29 changes: 29 additions & 0 deletions app/src/main/java/com/bnyro/contacts/ui/screens/SettingsScreen.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bnyro.contacts.ui.screens

import android.os.Build
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
Expand All @@ -15,8 +16,13 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.bnyro.contacts.R
Expand All @@ -27,13 +33,16 @@ import com.bnyro.contacts.ui.components.prefs.BlockPreference
import com.bnyro.contacts.ui.components.prefs.EncryptBackupsPref
import com.bnyro.contacts.ui.components.prefs.SettingsCategory
import com.bnyro.contacts.ui.components.prefs.SwitchPref
import com.bnyro.contacts.ui.components.prefs.SwitchPrefBase
import com.bnyro.contacts.ui.models.SmsModel
import com.bnyro.contacts.ui.models.ThemeModel
import com.bnyro.contacts.util.BiometricAuthUtil
import com.bnyro.contacts.util.Preferences

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(themeModel: ThemeModel, smsModel: SmsModel, onBackPress: () -> Unit) {
val context = LocalContext.current

val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
rememberTopAppBarState()
Expand Down Expand Up @@ -121,6 +130,26 @@ fun SettingsScreen(themeModel: ThemeModel, smsModel: SmsModel, onBackPress: () -
modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
color = MaterialTheme.colorScheme.surfaceVariant
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
SettingsCategory(title = stringResource(R.string.security))

var biometricAuthEnabled by remember {
mutableStateOf(Preferences.getBoolean(Preferences.biometricAuthKey, false))
}

SwitchPrefBase(
title = stringResource(R.string.biometric_authentication),
summary = stringResource(R.string.biometric_authentication_summary),
checked = biometricAuthEnabled
) { newValue ->
BiometricAuthUtil.requestAuth(context) { authSuccess ->
if (authSuccess) {
Preferences.edit { putBoolean(Preferences.biometricAuthKey, newValue) }
biometricAuthEnabled = newValue
}
}
}
}
AutoBackupPref()
EncryptBackupsPref()
}
Expand Down
48 changes: 48 additions & 0 deletions app/src/main/java/com/bnyro/contacts/util/BiometricAuthUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.bnyro.contacts.util

import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.bnyro.contacts.R
import com.bnyro.contacts.ui.activities.BaseActivity

object BiometricAuthUtil {
private const val ALLOWED_AUTHENTICATORS =
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.BIOMETRIC_WEAK or
BiometricManager.Authenticators.DEVICE_CREDENTIAL

@RequiresApi(Build.VERSION_CODES.P)
fun requestAuth(context: Context, onResult: (Boolean) -> Unit) {
val executor = ContextCompat.getMainExecutor(context)

val biometricPrompt = BiometricPrompt(context as BaseActivity, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
onResult(false)
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
onResult(true)
}

override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
onResult(false)
}
}
)

val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(context.getString(R.string.biometric_authentication))
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
.build()

biometricPrompt.authenticate(promptInfo)
}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/bnyro/contacts/util/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ object Preferences {
const val encryptBackupPasswordKey = "encryptBackupsPassword"
const val storeSmsLocallyKey = "storeSmsLocally"
const val lastChosenAccount = "lastChosenAccount"
const val biometricAuthKey = "biometricAuth"

fun init(context: Context) {
preferences = context.getSharedPreferences(prefFile, Context.MODE_PRIVATE)
Expand Down
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 @@ -115,6 +115,9 @@
<string name="backup">Backup</string>
<string name="encrypt_backups">Encrypt backups as zip</string>
<string name="backup_password">Password</string>
<string name="security">Security</string>
<string name="biometric_authentication">Biometric Authentication</string>
<string name="biometric_authentication_summary">Require biometric authentication (e.g. fingerprint) before the app can be accessed.</string>
<!-- About -->
<string name="about">About</string>
<string name="license">License</string>
Expand Down

0 comments on commit f943c58

Please sign in to comment.