bitsy-wallet/app/src/main/java/cy/agorise/bitsybitshareswallet/fragments/PatternSecurityLockDialog.kt

235 lines
9.9 KiB
Kotlin

package cy.agorise.bitsybitshareswallet.fragments
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.andrognito.patternlockview.PatternLockView
import com.andrognito.patternlockview.listener.PatternLockViewListener
import com.google.firebase.crashlytics.FirebaseCrashlytics
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.DialogPatternSecurityLockBinding
import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
/**
* Contains all the specific logic to create and confirm a new Pattern or verifying the validity of the current one.
*/
class PatternSecurityLockDialog : BaseSecurityLockDialog() {
companion object {
const val TAG = "PatternSecurityLockDialog"
}
private var _binding: DialogPatternSecurityLockBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogPatternSecurityLockBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private var newPattern = ""
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val crashlytics = FirebaseCrashlytics.getInstance()
crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG)
setupScreen()
binding.patternLockView.addPatternLockListener(mPatternLockViewListener)
binding.btnClear.setOnClickListener { setupScreen() }
}
private val mPatternLockViewListener = object : PatternLockViewListener {
override fun onStarted() {
// Make sure the button is hidden when the user starts a new pattern when it was incorrect
when (currentStep) {
STEP_SECURITY_LOCK_VERIFY -> {
setMessage("")
}
STEP_SECURITY_LOCK_CREATE -> {
binding.btnClear.visibility = View.INVISIBLE
setMessage(getString(R.string.msg__release_finger))
}
STEP_SECURITY_LOCK_CONFIRM -> {
binding.btnClear.visibility = View.INVISIBLE
setMessage(getString(R.string.msg__release_finger))
}
}
}
override fun onProgress(progressPattern: List<PatternLockView.Dot>) {
}
override fun onComplete(pattern: List<PatternLockView.Dot>) {
if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
val hashedPattern = CryptoUtils.createSHA256Hash(
currentPINPatternSalt +
getStringPattern(pattern)
)
if (hashedPattern == currentHashedPINPattern) {
// Pattern is correct, proceed
resetIncorrectSecurityLockAttemptsAndTime()
dismiss()
mCallback?.onPINPatternEntered(actionIdentifier)
} else {
increaseIncorrectSecurityLockAttemptsAndTime()
if (incorrectSecurityLockAttempts < Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) {
// Show the error only when the user has not reached the max attempts limit, because if that
// is the case another error is gonna be shown in the setupScreen() method
setError(getString(R.string.error__wront_pattern))
}
setupScreen()
}
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
binding.btnClear.visibility = View.VISIBLE
if (pattern.size < 4) {
setError(getString(R.string.error__connect_at_least_4_dots))
binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG)
} else {
setMessage(getString(R.string.text__pattern_recorded))
binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT)
binding.patternLockView.isInputEnabled = false
binding.btnNext.isEnabled = true
newPattern = getStringPattern(pattern)
binding.btnNext.setOnClickListener {
currentStep = STEP_SECURITY_LOCK_CONFIRM
setupScreen()
}
}
} else if (currentStep == STEP_SECURITY_LOCK_CONFIRM) {
val patternConfirm = getStringPattern(pattern)
if (patternConfirm != newPattern) {
setError(getString(R.string.error__wront_pattern))
binding.btnNext.isEnabled = false
binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG)
} else {
setMessage(getString(R.string.msg__your_new_unlock_pattern))
binding.patternLockView.isEnabled = false
binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.CORRECT)
binding.btnNext.isEnabled = true
binding.btnNext.setOnClickListener {
context?.let {
val salt = CryptoUtils.generateSalt()
val hashedPattern = CryptoUtils.createSHA256Hash(salt + patternConfirm)
// Stores the newly selected Pattern, encrypted
PreferenceManager.getDefaultSharedPreferences(it).edit()
.putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPattern)
.putString(Constants.KEY_PIN_PATTERN_SALT, salt)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2)
.apply() // 2 -> Pattern
dismiss()
mCallback?.onPINPatternChanged()
}
}
}
}
}
override fun onCleared() {
}
}
/**
* Converts the given pattern into a string representation of it.
*/
private fun getStringPattern(pattern: List<PatternLockView.Dot>): String {
val sb = StringBuilder()
for (dot in pattern)
sb.append(dot.id)
return sb.toString()
}
private fun setupScreen() {
when (currentStep) {
STEP_SECURITY_LOCK_VERIFY -> {
binding.tvTitle.text = getString(R.string.title__re_enter_your_pattern)
binding.tvSubTitle.text = getString(R.string.msg__enter_your_pattern)
binding.btnClear.visibility = View.GONE
binding.btnNext.visibility = View.GONE
binding.patternLockView.isInputEnabled = true
binding.patternLockView.isInStealthMode = true
if (incorrectSecurityLockAttempts >= Constants.MAX_INCORRECT_SECURITY_LOCK_ATTEMPTS) {
// User has entered the Pattern incorrectly too many times
val now = System.currentTimeMillis()
if (now <= incorrectSecurityLockTime + Constants.INCORRECT_SECURITY_LOCK_COOLDOWN) {
binding.patternLockView.isInputEnabled = false
startContDownTimer()
} else {
resetIncorrectSecurityLockAttemptsAndTime()
}
}
}
STEP_SECURITY_LOCK_CREATE -> {
binding.tvTitle.text = getString(R.string.title__set_bitsy_security_lock)
binding.tvSubTitle.text = getString(R.string.msg__set_a_pattern)
setMessage(getString(R.string.text__draw_an_unlock_pattern))
binding.patternLockView.clearPattern()
binding.patternLockView.isInputEnabled = true
binding.btnClear.visibility = View.INVISIBLE
binding.btnNext.isEnabled = false
}
STEP_SECURITY_LOCK_CONFIRM -> {
binding.tvTitle.text = getString(R.string.title__re_enter_your_pattern)
binding.tvSubTitle.text = ""
setMessage(getString(R.string.msg__draw_pattern_confirm))
binding.tvSubTitle.visibility = View.GONE
binding.patternLockView.clearPattern()
binding.patternLockView.isInputEnabled = true
binding.btnClear.visibility = View.INVISIBLE
binding.btnNext.isEnabled = false
binding.btnNext.text = getString(R.string.button__confirm)
}
}
}
@Suppress("DEPRECATION")
private fun setMessage(message: String) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
binding.tvMessage.setTextAppearance(context, R.style.TextAppearance_Bitsy_Body2)
} else {
binding.tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2)
}
binding.tvMessage.text = message
}
@Suppress("DEPRECATION")
private fun setError(error: String) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
binding.tvMessage.setTextAppearance(context, R.style.TextAppearance_Bitsy_Body2_Error)
} else {
binding.tvMessage.setTextAppearance(R.style.TextAppearance_Bitsy_Body2_Error)
}
binding.tvMessage.text = error
}
override fun onTimerSecondPassed(errorMessage: String) {
setError(errorMessage)
}
override fun onTimerFinished() {
setupScreen()
setMessage("")
}
}