feat: biometric | etc
This commit is contained in:
parent
7717974b9e
commit
ce488ea536
7 changed files with 192 additions and 6 deletions
|
@ -101,6 +101,8 @@ dependencies {
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
implementation 'androidx.webkit:webkit:1.11.0'
|
implementation 'androidx.webkit:webkit:1.11.0'
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
implementation "com.anggrayudi:storage:1.5.5"
|
||||||
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
|
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
ext.glide_version = '4.16.0'
|
ext.glide_version = '4.16.0'
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package ani.dantotsu.others.calc
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.util.Logger
|
||||||
|
|
||||||
|
object BiometricPromptUtils {
|
||||||
|
private const val TAG = "BiometricPromptUtils"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a BiometricPrompt instance
|
||||||
|
* @param activity: AppCompatActivity
|
||||||
|
* @param processSuccess: success callback
|
||||||
|
*/
|
||||||
|
fun createBiometricPrompt(
|
||||||
|
activity: AppCompatActivity,
|
||||||
|
processSuccess: (BiometricPrompt.AuthenticationResult) -> Unit
|
||||||
|
): BiometricPrompt {
|
||||||
|
val executor = ContextCompat.getMainExecutor(activity)
|
||||||
|
|
||||||
|
val callback = object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
|
||||||
|
override fun onAuthenticationError(errCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errCode, errString)
|
||||||
|
Logger.log("$TAG errCode is $errCode and errString is: $errString")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Logger.log("$TAG User biometric rejected.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
Log.d(TAG, "Authentication was successful")
|
||||||
|
processSuccess(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BiometricPrompt(activity, executor, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a BiometricPrompt.PromptInfo instance
|
||||||
|
* @param activity: AppCompatActivity
|
||||||
|
* @return BiometricPrompt.PromptInfo: instance
|
||||||
|
*/
|
||||||
|
fun createPromptInfo(activity: AppCompatActivity): BiometricPrompt.PromptInfo =
|
||||||
|
BiometricPrompt.PromptInfo.Builder().apply {
|
||||||
|
setTitle(activity.getString(R.string.bio_prompt_info_title))
|
||||||
|
setDescription(activity.getString(R.string.bio_prompt_info_desc))
|
||||||
|
setConfirmationRequired(false)
|
||||||
|
setNegativeButtonText(activity.getString(R.string.cancel))
|
||||||
|
}.build()
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
package ani.dantotsu.others.calc
|
package ani.dantotsu.others.calc
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -16,6 +20,8 @@ import ani.dantotsu.databinding.ActivityCalcBinding
|
||||||
import ani.dantotsu.getThemeColor
|
import ani.dantotsu.getThemeColor
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.util.NumberConverter.Companion.toBinary
|
import ani.dantotsu.util.NumberConverter.Companion.toBinary
|
||||||
|
@ -24,7 +30,13 @@ import ani.dantotsu.util.NumberConverter.Companion.toHex
|
||||||
class CalcActivity : AppCompatActivity() {
|
class CalcActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityCalcBinding
|
private lateinit var binding: ActivityCalcBinding
|
||||||
private lateinit var code: String
|
private lateinit var code: String
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private val runnable = Runnable {
|
||||||
|
success()
|
||||||
|
}
|
||||||
private val stack = CalcStack()
|
private val stack = CalcStack()
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
|
@ -73,6 +85,29 @@ class CalcActivity : AppCompatActivity() {
|
||||||
binding.displayHex.text = ""
|
binding.displayHex.text = ""
|
||||||
binding.display.text = "0"
|
binding.display.text = "0"
|
||||||
}
|
}
|
||||||
|
if (PrefManager.getVal(PrefName.OverridePassword, false)) {
|
||||||
|
buttonClear.setOnTouchListener { v, event ->
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
handler.postDelayed(runnable, 10000)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
v.performClick()
|
||||||
|
handler.removeCallbacks(runnable)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_CANCEL -> {
|
||||||
|
handler.removeCallbacks(runnable)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
buttonBackspace.setOnClickListener {
|
buttonBackspace.setOnClickListener {
|
||||||
stack.remove()
|
stack.remove()
|
||||||
updateDisplay()
|
updateDisplay()
|
||||||
|
@ -81,6 +116,20 @@ class CalcActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (hasPermission) {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
if (PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()) {
|
||||||
|
val bioMetricPrompt = BiometricPromptUtils.createBiometricPrompt(this) {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
val promptInfo = BiometricPromptUtils.createPromptInfo(this)
|
||||||
|
bioMetricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun success() {
|
private fun success() {
|
||||||
hasPermission = true
|
hasPermission = true
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
|
|
|
@ -8,9 +8,12 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.CheckBox
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -21,6 +24,7 @@ import ani.dantotsu.databinding.DialogUserAgentBinding
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.others.calc.BiometricPromptUtils
|
||||||
import ani.dantotsu.restartApp
|
import ani.dantotsu.restartApp
|
||||||
import ani.dantotsu.savePrefsToDownloads
|
import ani.dantotsu.savePrefsToDownloads
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
@ -39,6 +43,7 @@ import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
class SettingsCommonActivity : AppCompatActivity() {
|
class SettingsCommonActivity : AppCompatActivity() {
|
||||||
|
@ -183,14 +188,47 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
(dialog as AlertDialog).findViewById<EditText>(R.id.passwordInput)
|
(dialog as AlertDialog).findViewById<EditText>(R.id.passwordInput)
|
||||||
val confirmPasswordInput =
|
val confirmPasswordInput =
|
||||||
dialog.findViewById<EditText>(R.id.confirmPasswordInput)
|
dialog.findViewById<EditText>(R.id.confirmPasswordInput)
|
||||||
|
val forgotPasswordCheckbox =
|
||||||
|
dialog.findViewById<CheckBox>(R.id.forgotPasswordCheckbox)
|
||||||
|
if (forgotPasswordCheckbox?.isChecked == true) {
|
||||||
|
PrefManager.setVal(PrefName.OverridePassword, true)
|
||||||
|
}
|
||||||
|
|
||||||
val password = passwordInput?.text.toString()
|
val password = passwordInput?.text.toString()
|
||||||
val confirmPassword = confirmPasswordInput?.text.toString()
|
val confirmPassword = confirmPasswordInput?.text.toString()
|
||||||
|
|
||||||
if (password == confirmPassword && password.isNotEmpty()) {
|
if (password == confirmPassword && password.isNotEmpty()) {
|
||||||
PrefManager.setVal(PrefName.AppPassword, password)
|
PrefManager.setVal(PrefName.AppPassword, password)
|
||||||
|
if (dialog.findViewById<CheckBox>(R.id.biometricCheckbox)?.isChecked == true) {
|
||||||
|
val canBiometricPrompt =
|
||||||
|
BiometricManager.from(applicationContext)
|
||||||
|
.canAuthenticate(
|
||||||
|
BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||||
|
) == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
if (canBiometricPrompt) {
|
||||||
|
val biometricPrompt =
|
||||||
|
BiometricPromptUtils.createBiometricPrompt(
|
||||||
|
this@SettingsCommonActivity
|
||||||
|
) { _ ->
|
||||||
|
val token = UUID.randomUUID().toString()
|
||||||
|
PrefManager.setVal(
|
||||||
|
PrefName.BiometricToken,
|
||||||
|
token
|
||||||
|
)
|
||||||
toast(R.string.success)
|
toast(R.string.success)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
val promptInfo =
|
||||||
|
BiometricPromptUtils.createPromptInfo(
|
||||||
|
this@SettingsCommonActivity
|
||||||
|
)
|
||||||
|
biometricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PrefManager.setVal(PrefName.BiometricToken, "")
|
||||||
|
toast(R.string.success)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.password_mismatch)
|
toast(R.string.password_mismatch)
|
||||||
}
|
}
|
||||||
|
@ -200,6 +238,8 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.remove) { dialog, _ ->
|
.setNeutralButton(R.string.remove) { dialog, _ ->
|
||||||
PrefManager.setVal(PrefName.AppPassword, "")
|
PrefManager.setVal(PrefName.AppPassword, "")
|
||||||
|
PrefManager.setVal(PrefName.BiometricToken, "")
|
||||||
|
PrefManager.setVal(PrefName.OverridePassword, false)
|
||||||
toast(R.string.success)
|
toast(R.string.success)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -209,6 +249,19 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
passwordDialog.setOnShowListener {
|
passwordDialog.setOnShowListener {
|
||||||
passwordDialog.findViewById<EditText>(R.id.passwordInput)
|
passwordDialog.findViewById<EditText>(R.id.passwordInput)
|
||||||
?.requestFocus()
|
?.requestFocus()
|
||||||
|
val biometricCheckbox =
|
||||||
|
passwordDialog.findViewById<CheckBox>(R.id.biometricCheckbox)
|
||||||
|
val forgotPasswordCheckbox =
|
||||||
|
passwordDialog.findViewById<CheckBox>(R.id.forgotPasswordCheckbox)
|
||||||
|
val canAuthenticate =
|
||||||
|
BiometricManager.from(applicationContext).canAuthenticate(
|
||||||
|
BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||||
|
) == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
biometricCheckbox?.isVisible = canAuthenticate
|
||||||
|
biometricCheckbox?.isChecked =
|
||||||
|
PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()
|
||||||
|
forgotPasswordCheckbox?.isChecked =
|
||||||
|
PrefManager.getVal(PrefName.OverridePassword)
|
||||||
}
|
}
|
||||||
passwordDialog.show()
|
passwordDialog.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,4 +202,6 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
|
||||||
MALCodeChallenge(Pref(Location.Protected, String::class, "")),
|
MALCodeChallenge(Pref(Location.Protected, String::class, "")),
|
||||||
MALToken(Pref(Location.Protected, MAL.ResponseToken::class, "")),
|
MALToken(Pref(Location.Protected, MAL.ResponseToken::class, "")),
|
||||||
AppPassword(Pref(Location.Protected, String::class, "")),
|
AppPassword(Pref(Location.Protected, String::class, "")),
|
||||||
|
BiometricToken(Pref(Location.Protected, String::class, "")),
|
||||||
|
OverridePassword(Pref(Location.Protected, Boolean::class, false)),
|
||||||
}
|
}
|
|
@ -10,16 +10,34 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/enter_password"
|
android:hint="@string/enter_password"
|
||||||
android:maxLength="32"
|
android:inputType="numberPassword"
|
||||||
android:inputType="numberPassword" />
|
android:maxLength="32" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/confirmPasswordInput"
|
android:id="@+id/confirmPasswordInput"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
android:hint="@string/confirm_password"
|
android:hint="@string/confirm_password"
|
||||||
android:inputType="numberPassword"
|
android:inputType="numberPassword"
|
||||||
android:maxLength="32"
|
android:maxLength="32" />
|
||||||
android:layout_marginTop="16dp" />
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/biometricCheckbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fontFamily="@font/poppins"
|
||||||
|
android:text="@string/enable_biometric"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/forgotPasswordCheckbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:fontFamily="@font/poppins"
|
||||||
|
android:text="@string/enable_forgot_password"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -1024,4 +1024,9 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="app_lock_desc">Lock the app with a password\n( ͡° ͜ʖ ͡°)</string>
|
<string name="app_lock_desc">Lock the app with a password\n( ͡° ͜ʖ ͡°)</string>
|
||||||
<string name="confirm_password">Confirm Password</string>
|
<string name="confirm_password">Confirm Password</string>
|
||||||
<string name="password_mismatch">Passwords do not match or are empty!</string>
|
<string name="password_mismatch">Passwords do not match or are empty!</string>
|
||||||
|
<string name="enable_biometric">Enable Biometric</string>
|
||||||
|
<string name="bio_prompt_info_title">Biometric Authentication</string>
|
||||||
|
<string name="bio_prompt_info_desc">Use your fingerprint or face to unlock the app</string>
|
||||||
|
<string name="enable_forgot_password">Enable Forgot Password (hold clear button for 10 seconds)</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue