mirror of
https://codeberg.org/timelimit/opentimelimit-android.git
synced 2025-10-03 17:59:46 +02:00
Add Biometric Authentication (#16)
This allows using the biometric authentication to authenticate as parent user. Co-authored-by: Marcel Voigt <ycram@mailbox.org> Reviewed-on: https://codeberg.org/timelimit/opentimelimit-android/pulls/16 Co-Authored-By: ycram <ycram@noreply.codeberg.org> Co-Committed-By: ycram <ycram@noreply.codeberg.org>
This commit is contained in:
parent
921ada1156
commit
1df18a2a76
21 changed files with 843 additions and 23 deletions
9
CONTRIBUTORS.md
Normal file
9
CONTRIBUTORS.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# TimeLimit Contributors
|
||||||
|
|
||||||
|
- [Jonas Lochmann](https://codeberg.org/jonas-l)
|
||||||
|
|
||||||
|
- Author and maintainer
|
||||||
|
|
||||||
|
- [Marcel Voigt](https://codeberg.org/ycram)
|
||||||
|
|
||||||
|
- Biometric Authentication
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Open TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
* Open TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||||
|
* Copyright <C> 2020 Marcel Voigt
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -103,6 +104,8 @@ dependencies {
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
|
||||||
|
|
||||||
|
implementation 'androidx.biometric:biometric:1.1.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
* Copyright <C> 2020 Marcel Voigt
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -127,6 +128,9 @@ data class User(
|
||||||
val allowSelfLimitAdding: Boolean
|
val allowSelfLimitAdding: Boolean
|
||||||
get() = flags and UserFlags.ALLOW_SELF_LIMIT_ADD == UserFlags.ALLOW_SELF_LIMIT_ADD
|
get() = flags and UserFlags.ALLOW_SELF_LIMIT_ADD == UserFlags.ALLOW_SELF_LIMIT_ADD
|
||||||
|
|
||||||
|
val biometricAuthEnabled
|
||||||
|
get() = flags and UserFlags.BIOMETRIC_AUTH_ENABLED == UserFlags.BIOMETRIC_AUTH_ENABLED
|
||||||
|
|
||||||
override fun serialize(writer: JsonWriter) {
|
override fun serialize(writer: JsonWriter) {
|
||||||
writer.beginObject()
|
writer.beginObject()
|
||||||
|
|
||||||
|
@ -175,5 +179,6 @@ class UserTypeConverter {
|
||||||
object UserFlags {
|
object UserFlags {
|
||||||
const val RESTRICT_VIEWING_TO_PARENTS = 1L
|
const val RESTRICT_VIEWING_TO_PARENTS = 1L
|
||||||
const val ALLOW_SELF_LIMIT_ADD = 2L
|
const val ALLOW_SELF_LIMIT_ADD = 2L
|
||||||
const val ALL_FLAGS = RESTRICT_VIEWING_TO_PARENTS or ALLOW_SELF_LIMIT_ADD
|
const val BIOMETRIC_AUTH_ENABLED = 4L
|
||||||
|
const val ALL_FLAGS = RESTRICT_VIEWING_TO_PARENTS or ALLOW_SELF_LIMIT_ADD or BIOMETRIC_AUTH_ENABLED
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||||
|
* Copyright <C> 2020 Marcel Voigt
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -36,6 +37,7 @@ import io.timelimit.android.sync.actions.ChildSignInAction
|
||||||
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.AuthenticatedUser
|
import io.timelimit.android.ui.main.AuthenticatedUser
|
||||||
|
import io.timelimit.android.ui.main.AuthenticationMethod
|
||||||
import io.timelimit.android.ui.manage.parent.key.ScannedKey
|
import io.timelimit.android.ui.manage.parent.key.ScannedKey
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
@ -79,6 +81,7 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
}
|
}
|
||||||
private val isCheckingPassword = MutableLiveData<Boolean>().apply { value = false }
|
private val isCheckingPassword = MutableLiveData<Boolean>().apply { value = false }
|
||||||
private val wasPasswordWrong = MutableLiveData<Boolean>().apply { value = false }
|
private val wasPasswordWrong = MutableLiveData<Boolean>().apply { value = false }
|
||||||
|
var biometricPromptDismissed = false
|
||||||
private val isLoginDone = MutableLiveData<Boolean>().apply { value = false }
|
private val isLoginDone = MutableLiveData<Boolean>().apply { value = false }
|
||||||
private val loginLock = Mutex()
|
private val loginLock = Mutex()
|
||||||
|
|
||||||
|
@ -95,15 +98,15 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
wasPasswordWrong.map { wasPasswordWrong ->
|
wasPasswordWrong.map { wasPasswordWrong ->
|
||||||
ParentUserLogin(
|
ParentUserLogin(
|
||||||
isCheckingPassword = isCheckingPassword,
|
isCheckingPassword = isCheckingPassword,
|
||||||
wasPasswordWrong = wasPasswordWrong
|
wasPasswordWrong = wasPasswordWrong,
|
||||||
|
biometricAuthEnabled = selectedUser.biometricAuthEnabled,
|
||||||
|
userName = selectedUser.name
|
||||||
) as LoginDialogStatus
|
) as LoginDialogStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AllowUserLoginStatusUtil.calculateLive(logic, selectedUser.id).switchMap { status ->
|
AllowUserLoginStatusUtil.calculateLive(logic, selectedUser.id).switchMap { status ->
|
||||||
if (status is AllowUserLoginStatus.Allow) {
|
if (status is AllowUserLoginStatus.ForbidByCategory) {
|
||||||
loginScreen
|
|
||||||
} else if (status is AllowUserLoginStatus.ForbidByCategory) {
|
|
||||||
liveDataFromValue(
|
liveDataFromValue(
|
||||||
ParentUserLoginBlockedByCategory(
|
ParentUserLoginBlockedByCategory(
|
||||||
categoryTitle = status.categoryTitle,
|
categoryTitle = status.categoryTitle,
|
||||||
|
@ -146,6 +149,7 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startSignIn(user: User) {
|
fun startSignIn(user: User) {
|
||||||
|
biometricPromptDismissed = false
|
||||||
selectedUserId.value = user.id
|
selectedUserId.value = user.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +175,9 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
if (shouldSignIn) {
|
if (shouldSignIn) {
|
||||||
model.setAuthenticatedUser(AuthenticatedUser(
|
model.setAuthenticatedUser(AuthenticatedUser(
|
||||||
userId = user.id,
|
userId = user.id,
|
||||||
passwordHash = user.password
|
passwordHash = user.password,
|
||||||
|
isPasswordDisabled = emptyPasswordValid,
|
||||||
|
authenticatedBy = AuthenticationMethod.Password
|
||||||
))
|
))
|
||||||
|
|
||||||
isLoginDone.value = true
|
isLoginDone.value = true
|
||||||
|
@ -229,7 +235,9 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
if (shouldSignIn) {
|
if (shouldSignIn) {
|
||||||
model.setAuthenticatedUser(AuthenticatedUser(
|
model.setAuthenticatedUser(AuthenticatedUser(
|
||||||
userId = user.id,
|
userId = user.id,
|
||||||
passwordHash = user.password
|
passwordHash = user.password,
|
||||||
|
isPasswordDisabled = Threads.crypto.executeAndWait { PasswordHashing.validateSync("", user.password) },
|
||||||
|
authenticatedBy = AuthenticationMethod.KeyCode
|
||||||
))
|
))
|
||||||
|
|
||||||
isLoginDone.value = true
|
isLoginDone.value = true
|
||||||
|
@ -269,7 +277,9 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
|
|
||||||
val authenticatedUser = AuthenticatedUser(
|
val authenticatedUser = AuthenticatedUser(
|
||||||
userId = userEntry.id,
|
userId = userEntry.id,
|
||||||
passwordHash = userEntry.password
|
passwordHash = userEntry.password,
|
||||||
|
isPasswordDisabled = Threads.crypto.executeAndWait { PasswordHashing.validateSync("", userEntry.password) },
|
||||||
|
authenticatedBy = AuthenticationMethod.Password
|
||||||
)
|
)
|
||||||
|
|
||||||
val allowLoginStatus = Threads.database.executeAndWait {
|
val allowLoginStatus = Threads.database.executeAndWait {
|
||||||
|
@ -337,6 +347,37 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun performBiometricLogin(activityViewModel: ActivityViewModel) {
|
||||||
|
runAsync {
|
||||||
|
loginLock.withLock {
|
||||||
|
selectedUser.waitForNullableValue()?.loginRelatedData?.user?.let { user: User ->
|
||||||
|
if (!user.biometricAuthEnabled) {
|
||||||
|
return@runAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
val allowLoginStatus = Threads.database.executeAndWait {
|
||||||
|
AllowUserLoginStatusUtil.calculateSync(
|
||||||
|
logic = logic,
|
||||||
|
userId = user.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (allowLoginStatus !is AllowUserLoginStatus.Allow) {
|
||||||
|
Toast.makeText(getApplication(), formatAllowLoginStatusError(allowLoginStatus, getApplication()), Toast.LENGTH_SHORT).show()
|
||||||
|
return@runAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
activityViewModel.setAuthenticatedUser(AuthenticatedUser(
|
||||||
|
userId = user.id,
|
||||||
|
passwordHash = user.password,
|
||||||
|
isPasswordDisabled = Threads.crypto.executeAndWait { PasswordHashing.validateSync("", user.password) },
|
||||||
|
authenticatedBy = AuthenticationMethod.Biometric
|
||||||
|
))
|
||||||
|
isLoginDone.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun resetPasswordWrong() {
|
fun resetPasswordWrong() {
|
||||||
if (wasPasswordWrong.value == true) {
|
if (wasPasswordWrong.value == true) {
|
||||||
wasPasswordWrong.value = false
|
wasPasswordWrong.value = false
|
||||||
|
@ -359,7 +400,9 @@ data class UserListLoginDialogStatus(val usersToShow: List<User>): LoginDialogSt
|
||||||
data class ParentUserLoginBlockedByCategory(val categoryTitle: String, val reason: BlockingReason): LoginDialogStatus()
|
data class ParentUserLoginBlockedByCategory(val categoryTitle: String, val reason: BlockingReason): LoginDialogStatus()
|
||||||
data class ParentUserLogin(
|
data class ParentUserLogin(
|
||||||
val isCheckingPassword: Boolean,
|
val isCheckingPassword: Boolean,
|
||||||
val wasPasswordWrong: Boolean
|
val wasPasswordWrong: Boolean,
|
||||||
|
val biometricAuthEnabled: Boolean,
|
||||||
|
val userName: String
|
||||||
): LoginDialogStatus()
|
): LoginDialogStatus()
|
||||||
object LoginDialogDone: LoginDialogStatus()
|
object LoginDialogDone: LoginDialogStatus()
|
||||||
data class CanNotSignInChildHasNoPassword(val childName: String): LoginDialogStatus()
|
data class CanNotSignInChildHasNoPassword(val childName: String): LoginDialogStatus()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
* Copyright <C> 2020 Marcel Voigt
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,6 +25,8 @@ import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
@ -159,6 +162,10 @@ class NewLoginFragment: DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
password.setOnEnterListenr { go() }
|
password.setOnEnterListenr { go() }
|
||||||
|
|
||||||
|
biometricAuthButton.setOnClickListener {
|
||||||
|
tryBiometricLogin()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.childPassword.apply {
|
binding.childPassword.apply {
|
||||||
|
@ -192,9 +199,13 @@ class NewLoginFragment: DialogFragment() {
|
||||||
binding.switcher.setInAnimation(context!!, R.anim.wizard_open_step_in)
|
binding.switcher.setInAnimation(context!!, R.anim.wizard_open_step_in)
|
||||||
binding.switcher.setOutAnimation(context!!, R.anim.wizard_open_step_out)
|
binding.switcher.setOutAnimation(context!!, R.anim.wizard_open_step_out)
|
||||||
binding.switcher.displayedChild = PARENT_AUTH
|
binding.switcher.displayedChild = PARENT_AUTH
|
||||||
|
if (status.biometricAuthEnabled && !model.biometricPromptDismissed) {
|
||||||
|
tryBiometricLogin()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.enterPassword.password.isEnabled = !status.isCheckingPassword
|
binding.enterPassword.password.isEnabled = !status.isCheckingPassword
|
||||||
|
binding.enterPassword.biometricAuthEnabled = status.biometricAuthEnabled
|
||||||
|
|
||||||
if (!binding.enterPassword.showCustomKeyboard) {
|
if (!binding.enterPassword.showCustomKeyboard) {
|
||||||
binding.enterPassword.password.requestFocus()
|
binding.enterPassword.password.requestFocus()
|
||||||
|
@ -272,4 +283,46 @@ class NewLoginFragment: DialogFragment() {
|
||||||
fun tryCodeLogin(code: ScannedKey) {
|
fun tryCodeLogin(code: ScannedKey) {
|
||||||
model.tryCodeLogin(code, getActivityViewModel(activity!!))
|
model.tryCodeLogin(code, getActivityViewModel(activity!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryBiometricLogin() {
|
||||||
|
model.biometricPromptDismissed = false
|
||||||
|
model.status.value?.let { status ->
|
||||||
|
if (status is ParentUserLogin) {
|
||||||
|
BiometricPrompt(this, object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
model.biometricPromptDismissed = true
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
getString(R.string.biometric_auth_failed, status.userName) + "\n" +
|
||||||
|
getString(R.string.biometric_auth_failed_reason, errString),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
model.biometricPromptDismissed = true
|
||||||
|
model.performBiometricLogin(getActivityViewModel(requireActivity()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
model.biometricPromptDismissed = true
|
||||||
|
Toast.makeText(context, getString(R.string.biometric_auth_failed, status.userName), Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}).authenticate(
|
||||||
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(getString(R.string.biometric_login_prompt_title))
|
||||||
|
.setSubtitle(status.userName)
|
||||||
|
.setDescription(getString(R.string.biometric_login_prompt_description, status.userName))
|
||||||
|
.setNegativeButtonText(getString(R.string.generic_cancel))
|
||||||
|
.setConfirmationRequired(false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
* Copyright <C> 2020 Marcel Voigt
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -152,5 +153,10 @@ class ActivityViewModel(application: Application): AndroidViewModel(application)
|
||||||
|
|
||||||
data class AuthenticatedUser(
|
data class AuthenticatedUser(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val passwordHash: String
|
val passwordHash: String,
|
||||||
|
val isPasswordDisabled: Boolean,
|
||||||
|
val authenticatedBy: AuthenticationMethod
|
||||||
)
|
)
|
||||||
|
enum class AuthenticationMethod {
|
||||||
|
Password, KeyCode, Biometric
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
* Copyright <C> 2020 Marcel Voigt
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -40,6 +41,7 @@ import io.timelimit.android.ui.manage.child.advanced.timezone.UserTimezoneView
|
||||||
import io.timelimit.android.ui.manage.parent.delete.DeleteParentView
|
import io.timelimit.android.ui.manage.parent.delete.DeleteParentView
|
||||||
import io.timelimit.android.ui.manage.parent.key.ManageUserKeyView
|
import io.timelimit.android.ui.manage.parent.key.ManageUserKeyView
|
||||||
import io.timelimit.android.ui.manage.parent.limitlogin.ParentLimitLoginView
|
import io.timelimit.android.ui.manage.parent.limitlogin.ParentLimitLoginView
|
||||||
|
import io.timelimit.android.ui.manage.parent.password.biometric.ManageUserBiometricAuthView
|
||||||
|
|
||||||
class ManageParentFragment : Fragment(), FragmentWithCustomTitle {
|
class ManageParentFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
private val activity: ActivityViewModelHolder by lazy { getActivity() as ActivityViewModelHolder }
|
private val activity: ActivityViewModelHolder by lazy { getActivity() as ActivityViewModelHolder }
|
||||||
|
@ -119,6 +121,14 @@ class ManageParentFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
fragmentManager = parentFragmentManager
|
fragmentManager = parentFragmentManager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ManageUserBiometricAuthView.bind(
|
||||||
|
view = binding.biometricAuth,
|
||||||
|
user = parentUser,
|
||||||
|
auth = activity.getActivityViewModel(),
|
||||||
|
fragmentManager = parentFragmentManager,
|
||||||
|
fragment = this
|
||||||
|
)
|
||||||
|
|
||||||
binding.handlers = object: ManageParentFragmentHandlers {
|
binding.handlers = object: ManageParentFragmentHandlers {
|
||||||
override fun onChangePasswordClicked() {
|
override fun onChangePasswordClicked() {
|
||||||
navigation.safeNavigate(
|
navigation.safeNavigate(
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.manage.parent.password.biometric
|
||||||
|
|
||||||
|
import io.timelimit.android.R
|
||||||
|
|
||||||
|
class DisableBiometricAuthDeniedPasswordRequiredDialog :
|
||||||
|
ManageBiometricAuthDialog(DisableBiometricAuthDeniedPasswordRequiredDialog::class.java.simpleName) {
|
||||||
|
override val titleText by lazy { getString(R.string.biometric_manage_disable_password_required_dialog_title) }
|
||||||
|
override val messageText by lazy { getString(R.string.biometric_manage_disable_password_required_dialog_text) }
|
||||||
|
override val positiveButtonText by lazy { getString(R.string.generic_logout) }
|
||||||
|
override val negativeButtonText by lazy { getString(R.string.generic_cancel) }
|
||||||
|
|
||||||
|
override fun onPositiveButtonClicked() {
|
||||||
|
auth.logOut()
|
||||||
|
super.onPositiveButtonClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = DisableBiometricAuthDeniedPasswordRequiredDialog()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.manage.parent.password.biometric
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import io.timelimit.android.R
|
||||||
|
import io.timelimit.android.data.model.UserFlags
|
||||||
|
import io.timelimit.android.sync.actions.UpdateUserFlagsAction
|
||||||
|
|
||||||
|
class EnableBiometricAuthConfirmDialog :
|
||||||
|
ManageBiometricAuthDialog(EnableBiometricAuthConfirmDialog::class.java.simpleName) {
|
||||||
|
override val titleText by lazy { getString(R.string.biometric_manage_enable_dialog_title) }
|
||||||
|
override val messageText by lazy { getString(R.string.biometric_manage_enable_dialog_text, userName) }
|
||||||
|
override val positiveButtonText by lazy { getString(R.string.generic_enable) }
|
||||||
|
override val negativeButtonText by lazy { getString(R.string.generic_cancel) }
|
||||||
|
|
||||||
|
private lateinit var biometricPrompt: BiometricPrompt
|
||||||
|
private val userName by lazy { requireArguments().getString(ARG_USER_NAME) }
|
||||||
|
|
||||||
|
override fun onPositiveButtonClicked() {
|
||||||
|
showBiometricPrompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
biometricPrompt = BiometricPrompt(requireActivity(), object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
getString(R.string.biometric_auth_failed, userName) + "\n" +
|
||||||
|
getString(R.string.biometric_auth_failed_reason, errString),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
if (setUserFlag()) dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
getString(R.string.biometric_auth_failed, userName),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBiometricPrompt() {
|
||||||
|
biometricPrompt.authenticate(
|
||||||
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(getString(R.string.biometric_enable_prompt_title))
|
||||||
|
.setSubtitle(userName)
|
||||||
|
.setDescription(getString(R.string.biometric_enable_prompt_description, userName))
|
||||||
|
.setNegativeButtonText(getString(R.string.generic_cancel))
|
||||||
|
.setConfirmationRequired(false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUserFlag(): Boolean {
|
||||||
|
val userId = requireArguments().getString(ARG_USER_ID) ?: return false
|
||||||
|
return auth.tryDispatchParentAction(
|
||||||
|
UpdateUserFlagsAction(
|
||||||
|
userId = userId,
|
||||||
|
modifiedBits = UserFlags.BIOMETRIC_AUTH_ENABLED,
|
||||||
|
newValues = UserFlags.BIOMETRIC_AUTH_ENABLED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_USER_ID = "userId"
|
||||||
|
private const val ARG_USER_NAME = "userName"
|
||||||
|
fun newInstance(userId: String, userName: String) = EnableBiometricAuthConfirmDialog().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putString(ARG_USER_ID, userId)
|
||||||
|
putString(ARG_USER_NAME, userName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.manage.parent.password.biometric
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import io.timelimit.android.R
|
||||||
|
|
||||||
|
class EnableBiometricAuthDeniedNoCredentialsDialog :
|
||||||
|
ManageBiometricAuthDialog(EnableBiometricAuthDeniedNoCredentialsDialog::class.java.simpleName) {
|
||||||
|
override val titleText by lazy { getString(R.string.biometric_manage_no_credentials_dialog_title) }
|
||||||
|
override val messageText by lazy { getString(R.string.biometric_manage_no_credentials_dialog_text) }
|
||||||
|
override val positiveButtonText by lazy { getString(R.string.biometric_manage_no_credentials_dialog_action) }
|
||||||
|
override val negativeButtonText by lazy { getString(R.string.generic_cancel) }
|
||||||
|
|
||||||
|
override fun onPositiveButtonClicked() {
|
||||||
|
super.onPositiveButtonClicked()
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
|
||||||
|
Settings.ACTION_BIOMETRIC_ENROLL
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ->
|
||||||
|
Settings.ACTION_FINGERPRINT_ENROLL
|
||||||
|
else ->
|
||||||
|
Settings.ACTION_SECURITY_SETTINGS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = EnableBiometricAuthDeniedNoCredentialsDialog()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.manage.parent.password.biometric
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import io.timelimit.android.R
|
||||||
|
|
||||||
|
class ManageBiometricAuthDeniedNotOwnerDialog :
|
||||||
|
ManageBiometricAuthDialog(ManageBiometricAuthDeniedNotOwnerDialog::class.java.simpleName) {
|
||||||
|
override val titleText by lazy { getString(R.string.biometric_manage_not_owner_dialog_title) }
|
||||||
|
override val messageText by lazy {
|
||||||
|
getString(R.string.biometric_manage_not_owner_dialog_text, requireArguments().getString(ARG_USER_NAME))
|
||||||
|
}
|
||||||
|
override val positiveButtonText by lazy { getString(R.string.generic_logout) }
|
||||||
|
override val negativeButtonText by lazy { getString(R.string.generic_cancel) }
|
||||||
|
|
||||||
|
override fun onPositiveButtonClicked() {
|
||||||
|
auth.logOut()
|
||||||
|
super.onPositiveButtonClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_USER_NAME = "userName"
|
||||||
|
|
||||||
|
fun newInstance(userName: String) = ManageBiometricAuthDeniedNotOwnerDialog().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putString(ARG_USER_NAME, userName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.manage.parent.password.biometric
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import io.timelimit.android.databinding.ManageUserBiometricAuthDialogBinding
|
||||||
|
import io.timelimit.android.extensions.showSafe
|
||||||
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
|
|
||||||
|
interface ManageBiometricAuthDialogHandler {
|
||||||
|
fun onPositiveButtonClicked()
|
||||||
|
fun onNegativeButtonClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ManageBiometricAuthDialog(private val dialogTag: String) :
|
||||||
|
BottomSheetDialogFragment(), ManageBiometricAuthDialogHandler {
|
||||||
|
protected abstract val titleText: String
|
||||||
|
protected abstract val messageText: String
|
||||||
|
protected abstract val positiveButtonText: String
|
||||||
|
protected abstract val negativeButtonText: String
|
||||||
|
|
||||||
|
protected val auth by lazy { getActivityViewModel(requireActivity()) }
|
||||||
|
protected lateinit var binding: ManageUserBiometricAuthDialogBinding
|
||||||
|
|
||||||
|
var handleCancelAsNegativeButton = true
|
||||||
|
|
||||||
|
override fun onPositiveButtonClicked() = dismiss()
|
||||||
|
override fun onNegativeButtonClicked() = dismiss()
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = ManageUserBiometricAuthDialogBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.title = titleText
|
||||||
|
binding.text = messageText
|
||||||
|
binding.positiveButtonText = positiveButtonText
|
||||||
|
binding.negativeButtonText = negativeButtonText
|
||||||
|
binding.handler = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(dialog: DialogInterface) {
|
||||||
|
if (handleCancelAsNegativeButton) onNegativeButtonClicked()
|
||||||
|
super.onCancel(dialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun show(fragmentManager: FragmentManager) {
|
||||||
|
showSafe(fragmentManager, dialogTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.manage.parent.password.biometric
|
||||||
|
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import io.timelimit.android.R
|
||||||
|
import io.timelimit.android.data.model.User
|
||||||
|
import io.timelimit.android.data.model.UserFlags
|
||||||
|
import io.timelimit.android.databinding.ManageUserBiometricAuthViewBinding
|
||||||
|
import io.timelimit.android.sync.actions.UpdateUserFlagsAction
|
||||||
|
import io.timelimit.android.ui.extension.bindHelpDialog
|
||||||
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
|
import io.timelimit.android.ui.main.AuthenticationMethod
|
||||||
|
|
||||||
|
object ManageUserBiometricAuthView {
|
||||||
|
fun bind(
|
||||||
|
view: ManageUserBiometricAuthViewBinding,
|
||||||
|
user: LiveData<User?>,
|
||||||
|
auth: ActivityViewModel,
|
||||||
|
fragmentManager: FragmentManager,
|
||||||
|
fragment: Fragment
|
||||||
|
) {
|
||||||
|
user.observe(view.lifecycleOwner ?: fragment.viewLifecycleOwner) {
|
||||||
|
if (it != null) {
|
||||||
|
view.userName = it.name
|
||||||
|
view.biometricAuthEnabled = it.biometricAuthEnabled
|
||||||
|
view.errorText = when (BiometricManager.from(fragment.requireContext()).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) {
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> fragment.getString(R.string.biometric_manage_error_no_hw)
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED ->
|
||||||
|
fragment.getString(R.string.biometric_manage_error_hw_not_available)
|
||||||
|
//BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> "" //Handled later by a dialog if necessary
|
||||||
|
//BiometricManager.BIOMETRIC_SUCCESS -> ""
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleUserFlag() {
|
||||||
|
user.value?.let { user ->
|
||||||
|
auth.tryDispatchParentAction(
|
||||||
|
UpdateUserFlagsAction(
|
||||||
|
userId = user.id,
|
||||||
|
modifiedBits = UserFlags.BIOMETRIC_AUTH_ENABLED,
|
||||||
|
newValues = if (!view.biometricAuthEnabled) UserFlags.BIOMETRIC_AUTH_ENABLED else 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.toggleBiometricAuthSwitch.setOnCheckedChangeListener { v, isChecked ->
|
||||||
|
// Checked state of the switch view shall always reflect the currently active setting:
|
||||||
|
// when it's the same there's nothing to do (just updating the UI from user data);
|
||||||
|
// when it differs (changed via UI) just reset the UI state, which is updated whenever the user's flag actually has changed.
|
||||||
|
if (isChecked == view.biometricAuthEnabled)
|
||||||
|
return@setOnCheckedChangeListener
|
||||||
|
else
|
||||||
|
v.isChecked = view.biometricAuthEnabled
|
||||||
|
|
||||||
|
if (auth.requestAuthenticationOrReturnTrue()) {
|
||||||
|
@Suppress("NAME_SHADOWING") val user = user.value ?: return@setOnCheckedChangeListener
|
||||||
|
val authenticatedUser = auth.authenticatedUser.value ?: return@setOnCheckedChangeListener
|
||||||
|
|
||||||
|
if (authenticatedUser.id != user.id) {
|
||||||
|
ManageBiometricAuthDeniedNotOwnerDialog.newInstance(userName = user.name).show(fragmentManager)
|
||||||
|
} else if (!view.biometricAuthEnabled) {
|
||||||
|
when (BiometricManager.from(fragment.requireContext()).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) {
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
|
||||||
|
EnableBiometricAuthDeniedNoCredentialsDialog.newInstance().show(fragmentManager)
|
||||||
|
else ->
|
||||||
|
EnableBiometricAuthConfirmDialog.newInstance(userId = user.id, userName = user.name).show(fragmentManager)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (auth.getAuthenticatedUser()?.authenticatedBy == AuthenticationMethod.Password || auth.getAuthenticatedUser()?.isPasswordDisabled == true) {
|
||||||
|
toggleUserFlag()
|
||||||
|
} else {
|
||||||
|
DisableBiometricAuthDeniedPasswordRequiredDialog.newInstance().show(fragmentManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.titleView.bindHelpDialog(
|
||||||
|
titleRes = R.string.biometric_manage_title,
|
||||||
|
textRes = R.string.biometric_manage_info,
|
||||||
|
fragmentManager = fragmentManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/res/drawable/ic_fingerprint_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_fingerprint_24dp.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
|
||||||
|
</vector>
|
|
@ -1,5 +1,6 @@
|
||||||
<!--
|
<!--
|
||||||
Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
Copyright <C> 2020 Marcel Voigt
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -74,6 +75,9 @@
|
||||||
<include android:id="@+id/user_key"
|
<include android:id="@+id/user_key"
|
||||||
layout="@layout/manage_user_key_view" />
|
layout="@layout/manage_user_key_view" />
|
||||||
|
|
||||||
|
<include android:id="@+id/biometric_auth"
|
||||||
|
layout="@layout/manage_user_biometric_auth_view" />
|
||||||
|
|
||||||
<include android:id="@+id/timezone"
|
<include android:id="@+id/timezone"
|
||||||
layout="@layout/user_timezone_view" />
|
layout="@layout/user_timezone_view" />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation version 3 of the License.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="title"
|
||||||
|
type="String" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="text"
|
||||||
|
type="String" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="positiveButtonText"
|
||||||
|
type="String" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="negativeButtonText"
|
||||||
|
type="String" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="handler"
|
||||||
|
type="io.timelimit.android.ui.manage.parent.password.biometric.ManageBiometricAuthDialogHandler" />
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:paddingTop="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@{title}"
|
||||||
|
android:textAppearance="?android:textAppearanceLarge"
|
||||||
|
tools:text="@string/biometric_manage_enable_dialog_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@{text}"
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"
|
||||||
|
tools:text="@string/biometric_manage_enable_dialog_text" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="?buttonBarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="?buttonBarNegativeButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="@{() -> handler.onNegativeButtonClicked()}"
|
||||||
|
android:text="@{negativeButtonText}"
|
||||||
|
android:visibility="@{negativeButtonText.isEmpty() ? View.GONE : View.VISIBLE}"
|
||||||
|
tools:text="Cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="?buttonBarPositiveButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="@{() -> handler.onPositiveButtonClicked()}"
|
||||||
|
android:text="@{positiveButtonText}"
|
||||||
|
android:visibility="@{positiveButtonText.isEmpty() ? View.GONE : View.VISIBLE}"
|
||||||
|
tools:text="Activate" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</layout>
|
85
app/src/main/res/layout/manage_user_biometric_auth_view.xml
Normal file
85
app/src/main/res/layout/manage_user_biometric_auth_view.xml
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
TimeLimit Copyright <C> 2020 Marcel Voigt
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation version 3 of the License.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="userName"
|
||||||
|
type="String" />
|
||||||
|
<variable
|
||||||
|
name="biometricAuthEnabled"
|
||||||
|
type="boolean" />
|
||||||
|
<variable
|
||||||
|
name="errorText"
|
||||||
|
type="String" />
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardUseCompatPadding="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_view"
|
||||||
|
android:text="@string/biometric_manage_title"
|
||||||
|
android:textAppearance="?android:textAppearanceLarge"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
app:drawableEndCompat="@drawable/ic_info_outline_black_24dp"
|
||||||
|
app:drawableTint="?colorOnSurface"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:text="@{biometricAuthEnabled ? @string/biometric_manage_description_enabled(userName) : @string/biometric_manage_description_disabled(userName)}"
|
||||||
|
tools:text="@string/biometric_manage_description_disabled"
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"
|
||||||
|
android:visibility="@{errorText == null || errorText.isEmpty() ? View.VISIBLE : View.GONE}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:text="@{errorText}"
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"
|
||||||
|
android:visibility="@{errorText == null || errorText.isEmpty() ? View.GONE : View.VISIBLE}"
|
||||||
|
app:drawableStartCompat="@android:drawable/ic_dialog_alert"
|
||||||
|
app:drawableTint="?android:attr/textColorSecondary"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
tools:text="@string/biometric_manage_error_hw_not_available"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/toggle_biometric_auth_switch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/biometric_manage_switch_text"
|
||||||
|
android:checked="@{biometricAuthEnabled}"
|
||||||
|
android:enabled="@{errorText == null || errorText.isEmpty()}" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</layout>
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
Copyright <C> 2020 Marcel Voigt
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -18,6 +19,9 @@
|
||||||
<variable
|
<variable
|
||||||
name="showCustomKeyboard"
|
name="showCustomKeyboard"
|
||||||
type="boolean" />
|
type="boolean" />
|
||||||
|
<variable
|
||||||
|
name="biometricAuthEnabled"
|
||||||
|
type="boolean" />
|
||||||
|
|
||||||
<import type="android.view.View" />
|
<import type="android.view.View" />
|
||||||
</data>
|
</data>
|
||||||
|
@ -56,6 +60,16 @@
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp" />
|
android:layout_height="48dp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/biometric_auth_button"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:contentDescription="@string/biometric_login_button_description"
|
||||||
|
android:src="@drawable/ic_fingerprint_24dp"
|
||||||
|
android:tint="?colorOnBackground"
|
||||||
|
android:visibility="@{biometricAuthEnabled ? View.VISIBLE : View.GONE}" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<io.timelimit.android.ui.view.KeyboardView
|
<io.timelimit.android.ui.view.KeyboardView
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Open TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
Open TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||||
|
Copyright <C> 2020 Marcel Voigt
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,6 +30,8 @@
|
||||||
<string name="generic_no">Nein</string>
|
<string name="generic_no">Nein</string>
|
||||||
<string name="generic_yes">Ja</string>
|
<string name="generic_yes">Ja</string>
|
||||||
<string name="generic_skip">Überspringen</string>
|
<string name="generic_skip">Überspringen</string>
|
||||||
|
<string name="generic_login">Anmelden</string>
|
||||||
|
<string name="generic_logout">Abmelden</string>
|
||||||
|
|
||||||
<string name="generic_swipe_to_dismiss">Sie können diesen Hinweis entfernen, indem Sie ihn zur Seite wischen</string>
|
<string name="generic_swipe_to_dismiss">Sie können diesen Hinweis entfernen, indem Sie ihn zur Seite wischen</string>
|
||||||
|
|
||||||
|
@ -1077,4 +1080,24 @@
|
||||||
<string name="task_review_last_grant">Diese Aufgabe wurde zuletzt bestätigt am %s</string>
|
<string name="task_review_last_grant">Diese Aufgabe wurde zuletzt bestätigt am %s</string>
|
||||||
|
|
||||||
<string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string>
|
<string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string>
|
||||||
|
|
||||||
|
<string name="biometric_title">Biometrische Authentifizierung</string>
|
||||||
|
<string name="biometric_title_enable">Biometrische Authentifizierung aktivieren</string>
|
||||||
|
<string name="biometric_title_disable">Biometrische Authentifizierung deaktivieren</string>
|
||||||
|
<string name="biometric_enable_prompt_description">Bestätigen Sie die Verwendung Ihrer biometrischen Anmeldemerkmale, um diese für die Anmeldung als %s zuzulassen.</string>
|
||||||
|
<string name="biometric_login_prompt_description">Bestätigen Sie Ihre biometrischen Anmeldemerkmale, um sich als %s anzumelden.</string>
|
||||||
|
<string name="biometric_login_button_description">Biometrische Anmeldemerkmale erfassen</string>
|
||||||
|
<string name="biometric_auth_failed">Authentifizierung als %s mit biometrischen Anmeldemerkmalen fehlgeschlagen.</string>
|
||||||
|
<string name="biometric_auth_failed_reason">Grund: %s</string>
|
||||||
|
<string name="biometric_manage_description_enabled">%s kann sich mit den im Gerät hinterlegten biometrischen Merkmalen anmelden.</string>
|
||||||
|
<string name="biometric_manage_description_disabled">Biometrische Authententifizierung als %s ist nicht aktiv.</string>
|
||||||
|
<string name="biometric_manage_info">Erlaubt es sich an ein (Eltern-)Konto mit biometrischen Anmeldemerkmalen, die auf diesem Gerät registriert sind, anzumelden, z.B. einen Fingerabdruck scannen statt das Passwort einzugeben.</string>
|
||||||
|
<string name="biometric_manage_enable_dialog_text">Hiermit werden ALLE auf dem Gerät hinterlegten biometrischen Merkmale akzeptiert. Jede Person mit registrierten biometrischen Merkmalen könnte sich als %s anmelden, auch Kindkontonutzer oder in TimeLimit nicht erfasste Personen.</string>
|
||||||
|
<string name="biometric_manage_disable_password_required_dialog_text">Um zu verhindern, dass Sie sich selbst aus diesem Konto aussperren, ist es erforderlich sich mit dem Passwort dieses Kontos anzumelden, um die biometrische Authentifizierung zu deaktivieren.</string>
|
||||||
|
<string name="biometric_manage_not_owner_dialog_text">Es ist nicht erlaubt die Einstellungen zur biometrischen Authentifizierung für andere Nutzer zu ändern. Bitte melden Sie sich als %s an, um Zugriff zu bekommen.</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_text">Keine biometrischen Merkmale registriert. Es müssen erst biometrische Merkmale für dieses Gerät hinterlegt werden, um diese Funktion nutzen zu können.</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_action_default">Einstellungen öffnen</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_action_v28">Anmeldemerkmale erfassen</string>
|
||||||
|
<string name="biometric_manage_error_no_hw">Kein biometrisches Erfassungsgerät erkannt.</string>
|
||||||
|
<string name="biometric_manage_error_hw_not_available">Das Gerät zur biometrischen Merkmalserfassung ist gerade nicht verfügbar.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
20
app/src/main/res/values-v28/strings.xml
Normal file
20
app/src/main/res/values-v28/strings.xml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Open TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
Copyright <C> Marcel Voigt
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation version 3 of the License.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_action" translatable="false">@string/biometric_manage_no_credentials_dialog_action_v28</string>
|
||||||
|
</resources>
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Open TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
Open TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||||
|
Copyright <C> 2020 Marcel Voigt
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,6 +30,8 @@
|
||||||
<string name="generic_no">No</string>
|
<string name="generic_no">No</string>
|
||||||
<string name="generic_yes">Yes</string>
|
<string name="generic_yes">Yes</string>
|
||||||
<string name="generic_skip">Skip</string>
|
<string name="generic_skip">Skip</string>
|
||||||
|
<string name="generic_login">Login</string>
|
||||||
|
<string name="generic_logout">Logout</string>
|
||||||
|
|
||||||
<string name="generic_swipe_to_dismiss">Swipe to the side to remove this message</string>
|
<string name="generic_swipe_to_dismiss">Swipe to the side to remove this message</string>
|
||||||
|
|
||||||
|
@ -1019,7 +1022,7 @@
|
||||||
<string name="setup_terms_title">Welcome to TimeLimit</string>
|
<string name="setup_terms_title">Welcome to TimeLimit</string>
|
||||||
<string name="about_terms_title">Legal</string>
|
<string name="about_terms_title">Legal</string>
|
||||||
<string name="terms_text" translatable="false">
|
<string name="terms_text" translatable="false">
|
||||||
Open TimeLimit Copyright © 2019 - 2021 Jonas Lochmann
|
Open TimeLimit Copyright © 2019 - 2021 Jonas Lochmann and the TimeLimit contributors
|
||||||
|
|
||||||
\n\nThis program is free software: you can redistribute it and/or modify
|
\n\nThis program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1126,4 +1129,33 @@
|
||||||
<string name="task_review_last_grant">This task was confirmed last time at %s</string>
|
<string name="task_review_last_grant">This task was confirmed last time at %s</string>
|
||||||
|
|
||||||
<string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string>
|
<string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string>
|
||||||
|
|
||||||
|
<string name="biometric_title">Biometric Authentication</string>
|
||||||
|
<string name="biometric_title_enable">Enable Biometric Authentication</string>
|
||||||
|
<string name="biometric_title_disable">Disable Biometric Authentication</string>
|
||||||
|
<string name="biometric_enable_prompt_title" translatable="false">@string/biometric_title_enable</string>
|
||||||
|
<string name="biometric_enable_prompt_description">Authenticate using your biometric credentials to allow them to be used to login as %s.</string>
|
||||||
|
<string name="biometric_login_prompt_title" translatable="false">@string/generic_login</string>
|
||||||
|
<string name="biometric_login_prompt_description">Authenticate using your biometric credentials to login as %s.</string>
|
||||||
|
<string name="biometric_login_button_description">Prompt for biometric credentials</string>
|
||||||
|
<string name="biometric_auth_failed">Failed to authenticate as %s using biometric credentials.</string>
|
||||||
|
<string name="biometric_auth_failed_reason">Reason: %s</string>
|
||||||
|
<string name="biometric_manage_title" translatable="false">@string/biometric_title</string>
|
||||||
|
<string name="biometric_manage_description_enabled">%s can login using the biometric credentials registered on the device.</string>
|
||||||
|
<string name="biometric_manage_description_disabled">%s has no biometric authentication enabled.</string>
|
||||||
|
<string name="biometric_manage_switch_text" translatable="false">@string/biometric_title_enable</string>
|
||||||
|
<string name="biometric_manage_info">Allows a (parent) user to login using the configured biometric credentials of this device, e.g. scanning a fingerprint instead of typing the password.</string>
|
||||||
|
<string name="biometric_manage_enable_dialog_title" translatable="false">@string/biometric_title_enable</string>
|
||||||
|
<string name="biometric_manage_enable_dialog_text">This enables ALL biometric credentials enrolled on this device to be accepted. Everyone who has biometric credentials registered could login as %s, even child account users or persons not managed by TimeLimit.</string>
|
||||||
|
<string name="biometric_manage_disable_password_required_dialog_title" translatable="false">@string/biometric_title_disable</string>
|
||||||
|
<string name="biometric_manage_disable_password_required_dialog_text">To avoid locking you out of your account please login using your password to allow disabling biometric authentication for this account.</string>
|
||||||
|
<string name="biometric_manage_not_owner_dialog_title" translatable="false">@string/biometric_title</string>
|
||||||
|
<string name="biometric_manage_not_owner_dialog_text">It is not permitted to change the biometric authentication settings of other users. Please login as %s to get access.</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_title" translatable="false">@string/biometric_title</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_text">No biometric credentials enrolled. First configure any biometric credentials for this device to use this feature.</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_action" translatable="false">@string/biometric_manage_no_credentials_dialog_action_default</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_action_default">Open Settings</string>
|
||||||
|
<string name="biometric_manage_no_credentials_dialog_action_v28">Enroll Credentials</string>
|
||||||
|
<string name="biometric_manage_error_no_hw">No biometric hardware detected.</string>
|
||||||
|
<string name="biometric_manage_error_hw_not_available">The biometric hardware is somehow not available at the moment.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue