forked from shihaam/thijooree
add app lock secruity and support for saving mib credentials
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user