Added the options to change the Security Lock option between PIN and None, and force the UI to show the correct one when it is changed.

This commit is contained in:
Severiano Jaramillo 2019-02-14 15:17:29 -06:00
parent b4965d8a01
commit e8e1259314
6 changed files with 150 additions and 26 deletions

View file

@ -18,8 +18,9 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
/** Used to denote that the user is in the step of creating the preferred security lock option */ /** Used to denote that the user is in the step of creating the preferred security lock option */
const val STEP_SECURITY_LOCK_CREATE = 1 const val STEP_SECURITY_LOCK_CREATE = 1
/** Used to denote that the user is in the step of confirming the newly created security lock option */ /** Used to denote that the user is in the step of confirming the newly created security lock option,
private const val STEP_SECURITY_LOCK_CONFIRM = 2 * this option should only be used internally */
const val STEP_SECURITY_LOCK_CONFIRM = 2
/** Used to denote that the user is in the step of verifying the current security lock option, to give /** Used to denote that the user is in the step of verifying the current security lock option, to give
* permission to do a security constrained action like sending a transaction or trying to change the * permission to do a security constrained action like sending a transaction or trying to change the
@ -45,10 +46,10 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
protected var mCallback: OnPINPatternEnteredListener? = null protected var mCallback: OnPINPatternEnteredListener? = null
/** Keeps track of the current step, can be create, confirm of verify */ /** Keeps track of the current step, can be create, confirm of verify */
protected var currentStep: Int = 1 protected var currentStep: Int = -1
/** Used so the calling object knows what was the intention to ask for the Security Lock */ /** Used so the calling object knows what was the intention to ask for the Security Lock */
protected var actionIdentifier: Int = 0 protected var actionIdentifier: Int = -1
/** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */ /** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */
protected var mDisposables = CompositeDisposable() protected var mDisposables = CompositeDisposable()
@ -64,9 +65,9 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
currentEncryptedPINPattern = PreferenceManager.getDefaultSharedPreferences(context) currentEncryptedPINPattern = PreferenceManager.getDefaultSharedPreferences(context)
.getString(Constants.KEY_ENCRYPTED_PIN, "")?.trim() .getString(Constants.KEY_ENCRYPTED_PIN, "")?.trim()
currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: 0 currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: -1
actionIdentifier = arguments?.getInt(KEY_ACTION_IDENTIFIER) ?: 0 actionIdentifier = arguments?.getInt(KEY_ACTION_IDENTIFIER) ?: -1
} }
/** /**

View file

@ -1,12 +1,14 @@
package cy.agorise.bitsybitshareswallet.fragments package cy.agorise.bitsybitshareswallet.fragments
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.dialog_pin_security_lock.* import kotlinx.android.synthetic.main.dialog_pin_security_lock.*
@ -25,23 +27,50 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
return inflater.inflate(R.layout.dialog_pin_security_lock, container, false) return inflater.inflate(R.layout.dialog_pin_security_lock, container, false)
} }
private var newPIN = ""
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupScreen()
// Listens to the event when the user clicks the 'Enter' button in the keyboard and acts accordingly // Listens to the event when the user clicks the 'Enter' button in the keyboard and acts accordingly
tietPIN.setOnEditorActionListener { v, actionId, _ -> tietPIN.setOnEditorActionListener { v, actionId, _ ->
var handled = false var handled = false
if (actionId == EditorInfo.IME_ACTION_GO) { if (actionId == EditorInfo.IME_ACTION_GO) {
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim()
if (currentStep == STEP_SECURITY_LOCK_VERIFY) { if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
// The user just wants to verify the current encrypted PIN/Pattern // The user just wants to verify the current encrypted PIN/Pattern
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim()
if (encryptedPIN == currentEncryptedPINPattern) { if (encryptedPIN == currentEncryptedPINPattern) {
// PIN is correct, proceed // PIN is correct, proceed
dismiss() dismiss()
mCallback?.onPINPatternEntered(actionIdentifier) mCallback?.onPINPatternEntered(actionIdentifier)
} else { } else {
tilPIN.error = "Wrong PIN" tilPIN.error = getString(R.string.error__wrong_pin)
}
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
// The user is trying to create a new PIN
if (v.text.toString().trim().length >= Constants.MIN_PIN_LENGTH) {
// Proceed to the next step only if the PIN has the min length
newPIN = v.text.toString().trim()
currentStep = STEP_SECURITY_LOCK_CONFIRM
setupScreen()
}
} else if (currentStep == STEP_SECURITY_LOCK_CONFIRM) {
val pinConfirm = v.text.toString().trim()
if (pinConfirm != newPIN) {
tvTitle.text = getString(R.string.title__pins_dont_match)
} else {
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim()
// Stores the newly selected PIN, encrypted
PreferenceManager.getDefaultSharedPreferences(v.context).edit()
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN)
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> PIN
dismiss()
mCallback?.onPINPatternChanged()
} }
} }
@ -50,11 +79,47 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
handled handled
} }
// Use RxBindings to clear the error when the user edits the PIN
mDisposables.add( mDisposables.add(
tietPIN.textChanges() tietPIN.textChanges()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { tilPIN.isErrorEnabled = false } .subscribe {
if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
// Make sure the error is removed when the user types again
tilPIN.isErrorEnabled = false
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
// Show the min length requirement for the PIN only when it has not been fulfilled
if (it.trim().length >= Constants.MIN_PIN_LENGTH) {
tilPIN.helperText = ""
} else {
tilPIN.helperText = getString(R.string.msg__min_pin_length)
}
}
}
) )
} }
private fun setupScreen() {
when (currentStep) {
STEP_SECURITY_LOCK_VERIFY -> {
tvTitle.text = getString(R.string.title__re_enter_your_pin)
tvSubTitle.text = getString(R.string.msg__enter_your_pin)
tilPIN.helperText = ""
tilPIN.isErrorEnabled = false
}
STEP_SECURITY_LOCK_CREATE -> {
tvTitle.text = getString(R.string.title__set_bitsy_screen_lock)
tvSubTitle.text = getString(R.string.msg__set_bitsy_pin)
tilPIN.helperText = getString(R.string.msg__min_pin_length)
tilPIN.isErrorEnabled = false
}
STEP_SECURITY_LOCK_CONFIRM -> {
tvTitle.text = getString(R.string.title__re_enter_your_pin)
tvSubTitle.text = ""
tvSubTitle.visibility = View.GONE
tietPIN.setText("")
tilPIN.helperText = ""
tilPIN.isErrorEnabled = false
}
}
}
} }

View file

@ -21,7 +21,6 @@ import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
import cy.agorise.bitsybitshareswallet.utils.toast
import cy.agorise.graphenej.BrainKey import cy.agorise.graphenej.BrainKey
import cy.agorise.graphenej.api.android.NetworkService import cy.agorise.graphenej.api.android.NetworkService
import cy.agorise.graphenej.api.android.RxBus import cy.agorise.graphenej.api.android.RxBus
@ -108,15 +107,12 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
// 1 -> Pattern // 1 -> Pattern
// 2 -> None // 2 -> None
btnViewBrainKey.setOnClickListener {
if (!verifySecurityLock(securityLockSelected, ACTION_SHOW_BRAINKEY))
getBrainkey()
}
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected] tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
tvSecurityLock.setOnClickListener { onSecurityLockTextSelected(securityLockSelected) } tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() }
tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected(securityLockSelected) } tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() }
btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() }
// Connect to the RxBus, which receives events from the NetworkService // Connect to the RxBus, which receives events from the NetworkService
mDisposables.add( mDisposables.add(
@ -216,7 +212,14 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
} }
} }
private fun onSecurityLockTextSelected(securityLockSelected: Int) { private fun onSecurityLockTextSelected() {
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options
// 0 -> PIN
// 1 -> Pattern
// 2 -> None
if (!verifySecurityLock(securityLockSelected, ACTION_CHANGE_SECURITY_LOCK)) if (!verifySecurityLock(securityLockSelected, ACTION_CHANGE_SECURITY_LOCK))
showChooseSecurityLockDialog() showChooseSecurityLockDialog()
} }
@ -231,7 +234,7 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
*/ */
private fun verifySecurityLock(securityLockSelected: Int, actionIdentifier: Int): Boolean { private fun verifySecurityLock(securityLockSelected: Int, actionIdentifier: Int): Boolean {
return when (securityLockSelected) { return when (securityLockSelected) {
0 /* PIN */ -> { 0 -> { /* PIN */
val pinFrag = PINSecurityLockDialog() val pinFrag = PINSecurityLockDialog()
val args = Bundle() val args = Bundle()
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK, args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
@ -241,7 +244,7 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
pinFrag.show(childFragmentManager, "pin_security_lock_tag") pinFrag.show(childFragmentManager, "pin_security_lock_tag")
true true
} }
1 /* Pattern */ -> { 1 -> { /* Pattern */
true true
} }
@ -260,7 +263,15 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
} }
override fun onPINPatternChanged() { override fun onPINPatternChanged() {
// Obtain the new Security Lock Option selected and display it in the screen
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options
// 0 -> PIN
// 1 -> Pattern
// 2 -> None
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
} }
/** /**
@ -271,11 +282,43 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
MaterialDialog(it).show { MaterialDialog(it).show {
title(R.string.title__security_dialog) title(R.string.title__security_dialog)
listItems(R.array.security_lock_options) {dialog, index, text -> listItems(R.array.security_lock_options) {dialog, index, text ->
dialog.context.toast("$text selected!") when (index) {
0 -> { /* PIN */
val pinFrag = PINSecurityLockDialog()
val args = Bundle()
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE)
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1)
pinFrag.arguments = args
pinFrag.show(childFragmentManager, "pin_security_lock_tag")
}
1 -> { /* Pattern */
}
else -> { /* None */
PreferenceManager.getDefaultSharedPreferences(context).edit()
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2).apply() // 2 -> None
// Call this function to update the UI
onPINPatternChanged()
} }
} }
} }
} }
}
}
private fun onShowBrainKeyButtonSelected() {
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
// Security Lock Options
// 0 -> PIN
// 1 -> Pattern
// 2 -> None
if (!verifySecurityLock(securityLockSelected, ACTION_SHOW_BRAINKEY))
getBrainkey()
}
/** /**
* Obtains the brainKey from the authorities db table for the current user account and if it is not null it passes * Obtains the brainKey from the authorities db table for the current user account and if it is not null it passes

View file

@ -19,14 +19,15 @@
android:layout_marginTop="@dimen/spacing_same_topic" android:layout_marginTop="@dimen/spacing_same_topic"
android:src="@drawable/ic_lock" android:src="@drawable/ic_lock"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/> app:layout_constraintStart_toStartOf="parent"
android:contentDescription="@string/title__re_enter_your_pin" />
<TextView <TextView
android:id="@+id/tvTitle" android:id="@+id/tvTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_same_topic" android:layout_marginTop="@dimen/spacing_same_topic"
android:text="Enter your PIN" tools:text="Enter your PIN"
android:textAppearance="@style/TextAppearance.Bitsy.Headline5" android:textAppearance="@style/TextAppearance.Bitsy.Headline5"
app:layout_constraintTop_toBottomOf="@id/ivLock" /> app:layout_constraintTop_toBottomOf="@id/ivLock" />
@ -35,7 +36,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="Enter your BiTSy PIN to continue" tools:text="Enter your BiTSy PIN to continue"
android:textAppearance="@style/TextAppearance.Bitsy.Body1" android:textAppearance="@style/TextAppearance.Bitsy.Body1"
app:layout_constraintTop_toBottomOf="@id/tvTitle"/> app:layout_constraintTop_toBottomOf="@id/tvTitle"/>

View file

@ -144,5 +144,12 @@
<string name="text__pin">PIN</string> <string name="text__pin">PIN</string>
<string name="text__pattern">Patrón</string> <string name="text__pattern">Patrón</string>
<string name="text__none">Ninguno</string> <string name="text__none">Ninguno</string>
<string name="title__re_enter_your_pin">Reingresa tu PIN</string>
<string name="msg__enter_your_pin">Digita tu PIN de BiTSy para continuar</string>
<string name="error__wrong_pin">PIN incorrecto</string>
<string name="title__set_bitsy_screen_lock">Crea un bloqueo de seguridad para BiTSy</string>
<string name="msg__set_bitsy_pin">Por seguridad, crea un PIN para BiTSy</string>
<string name="msg__min_pin_length">El PIN debe tener al menos 6 dígitos</string>
<string name="title__pins_dont_match">El PIN no concuerda</string>
</resources> </resources>

View file

@ -145,5 +145,12 @@
<string name="text__pin">PIN</string> <string name="text__pin">PIN</string>
<string name="text__pattern">Pattern</string> <string name="text__pattern">Pattern</string>
<string name="text__none">None</string> <string name="text__none">None</string>
<string name="title__re_enter_your_pin">Re-enter your PIN</string>
<string name="msg__enter_your_pin">Enter your BiTSy PIN to continue</string>
<string name="error__wrong_pin">Wrong PIN</string>
<string name="title__set_bitsy_screen_lock">Set BiTSy screen lock</string>
<string name="msg__set_bitsy_pin">For security, set BiTSy PIN</string>
<string name="msg__min_pin_length">PIN must be at least 6 digits</string>
<string name="title__pins_dont_match">PINs don\'t match</string>
</resources> </resources>