add app lock secruity and support for saving mib credentials

This commit is contained in:
2026-05-12 09:53:43 +05:00
parent 7209e9dca0
commit 81a2be150f
17 changed files with 1259 additions and 35 deletions
@@ -0,0 +1,168 @@
package sh.sar.basedbank
import android.content.Intent
import android.os.Bundle
import android.util.Base64
import android.view.View
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.google.android.material.button.MaterialButton
import sh.sar.basedbank.databinding.ActivityLockBinding
import sh.sar.basedbank.ui.login.LoginActivity
import java.security.MessageDigest
class LockActivity : AppCompatActivity() {
private lateinit var binding: ActivityLockBinding
private val pinDigits = mutableListOf<Int>()
private lateinit var method: String
private lateinit var salt: String
private lateinit var storedHash: String
private var biometricsEnabled = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLockBinding.inflate(layoutInflater)
setContentView(binding.root)
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
method = prefs.getString("security_method", "pin") ?: "pin"
salt = prefs.getString("security_salt", "") ?: ""
storedHash = prefs.getString("security_hash", "") ?: ""
biometricsEnabled = prefs.getBoolean("biometrics_enabled", false)
if (method == "pin") {
binding.viewPin.visibility = View.VISIBLE
buildNumpad()
updateDots()
if (biometricsEnabled) binding.btnLockBiometricFromPin.visibility = View.VISIBLE
binding.btnLockBiometricFromPin.setOnClickListener { triggerBiometric() }
} else {
binding.viewPattern.visibility = View.VISIBLE
if (biometricsEnabled) binding.btnLockBiometricFromPattern.visibility = View.VISIBLE
binding.btnLockBiometricFromPattern.setOnClickListener { triggerBiometric() }
binding.lockPatternView.onPatternComplete = { pattern -> verifyPattern(pattern) }
}
if (biometricsEnabled) triggerBiometric()
}
private fun buildNumpad() {
val rows = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf("", "0", "")
)
rows.forEach { keys ->
val row = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f
)
}
keys.forEach { key ->
val style = if (key == "")
com.google.android.material.R.attr.materialButtonStyle
else
com.google.android.material.R.attr.materialButtonOutlinedStyle
val btn = MaterialButton(this, null, style).apply {
text = key
textSize = 20f
insetTop = 0; insetBottom = 0
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
.also { it.setMargins(4, 4, 4, 4) }
}
btn.setOnClickListener { handleKey(key) }
row.addView(btn)
}
binding.lockNumpadContainer.addView(row)
}
}
private fun handleKey(key: String) {
when (key) {
"" -> if (pinDigits.isNotEmpty()) { pinDigits.removeLast(); updateDots() }
"" -> if (pinDigits.size >= 4) verifyPin()
else -> if (pinDigits.size < 8) { pinDigits.add(key.toInt()); updateDots() }
}
}
private fun updateDots() {
val n = pinDigits.size
binding.tvLockPinDots.text = "".repeat(n) + "".repeat(maxOf(4 - n, 0))
}
private fun verifyPin() {
val entered = pinDigits.joinToString("")
if (verify(entered)) {
proceed()
} else {
binding.tvLockPinDots.text = getString(R.string.unlock_failed)
pinDigits.clear()
binding.root.postDelayed({ updateDots() }, 1200)
}
}
private fun verifyPattern(pattern: List<Int>) {
if (pattern.size < 4) {
binding.lockPatternView.showError()
binding.tvPatternHint.text = getString(R.string.pattern_min_dots)
return
}
if (verify(pattern.joinToString(""))) {
proceed()
} else {
binding.lockPatternView.showError()
binding.tvPatternHint.text = getString(R.string.unlock_failed)
binding.root.postDelayed({ binding.tvPatternHint.text = "" }, 1500)
}
}
private fun verify(input: String): Boolean {
val hash = sha256(salt + input)
return hash == storedHash
}
private fun triggerBiometric() {
val canAuth = BiometricManager.from(this)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
if (canAuth != BiometricManager.BIOMETRIC_SUCCESS) return
val prompt = BiometricPrompt(
this,
ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
proceed()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
// User cancelled or error — fall back to PIN/pattern (already visible)
}
}
)
val info = BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.unlock_app))
.setSubtitle(getString(R.string.biometric_prompt_subtitle))
.setNegativeButtonText(getString(R.string.biometric_negative_btn))
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.build()
prompt.authenticate(info)
}
private fun proceed() {
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
private fun sha256(input: String) = MessageDigest.getInstance("SHA-256")
.digest(input.toByteArray()).joinToString("") { "%02x".format(it) }
override fun onBackPressed() {
// Lock screen cannot be dismissed
moveTaskToBack(true)
}
}