diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt index 8770925cb5..5da167b74b 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt @@ -8,17 +8,17 @@ import android.os.Bundle import androidx.annotation.XmlRes import androidx.preference.* import dagger.android.support.AndroidSupportInjection -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin import info.nightscout.androidaps.danaRv2.DanaRv2Plugin import info.nightscout.androidaps.danar.DanaRPlugin import info.nightscout.androidaps.danars.DanaRSPlugin import info.nightscout.androidaps.diaconn.DiaconnG8Plugin -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventRebuildTabs +import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin @@ -43,17 +43,12 @@ import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin -import info.nightscout.androidaps.plugins.source.DexcomPlugin -import info.nightscout.androidaps.plugins.source.EversensePlugin -import info.nightscout.androidaps.plugins.source.GlimpPlugin -import info.nightscout.androidaps.plugins.source.PoctechPlugin -import info.nightscout.androidaps.plugins.source.TomatoPlugin -import info.nightscout.androidaps.plugins.source.GlunovoPlugin -import info.nightscout.shared.SafeParse +import info.nightscout.androidaps.plugins.source.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog.show import info.nightscout.androidaps.utils.protection.PasswordCheck -import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.ProtectionType.* import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.SafeParse import info.nightscout.shared.sharedPreferences.SP import javax.inject.Inject @@ -239,7 +234,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang rh.gs(R.string.key_application_protection) == key || rh.gs(R.string.key_bolus_protection) == key) && sp.getString(R.string.key_master_password, "") == "" && - sp.getInt(key, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal + sp.getInt(key, NONE.ordinal) == BIOMETRIC.ordinal ) { activity?.let { val title = rh.gs(R.string.unsecure_fallback_biometric) @@ -249,9 +244,9 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang } // Master password erased with activated Biometric protection - val isBiometricActivated = sp.getInt(R.string.key_settings_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal || - sp.getInt(R.string.key_application_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal || - sp.getInt(R.string.key_bolus_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal + val isBiometricActivated = sp.getInt(R.string.key_settings_protection, NONE.ordinal) == BIOMETRIC.ordinal || + sp.getInt(R.string.key_application_protection, NONE.ordinal) == BIOMETRIC.ordinal || + sp.getInt(R.string.key_bolus_protection, NONE.ordinal) == BIOMETRIC.ordinal if (rh.gs(R.string.key_master_password) == key && sp.getString(key, "") == "" && isBiometricActivated) { activity?.let { val title = rh.gs(R.string.unsecure_fallback_biometric) @@ -322,26 +317,35 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang if (pref is ListPreference) { pref.setSummary(pref.entry) // Preferences - // Preferences if (pref.getKey() == rh.gs(R.string.key_settings_protection)) { val pass: Preference? = findPreference(rh.gs(R.string.key_settings_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() + val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString() + pass?.let { it.isVisible = usePassword } + val pin: Preference? = findPreference(rh.gs(R.string.key_settings_pin)) + val usePin = pref.value == CUSTOM_PIN.ordinal.toString() + pin?.let { it.isVisible = usePin } } // Application - // Application if (pref.getKey() == rh.gs(R.string.key_application_protection)) { val pass: Preference? = findPreference(rh.gs(R.string.key_application_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() + val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString() + pass?.let { it.isVisible = usePassword } + val pin: Preference? = findPreference(rh.gs(R.string.key_application_pin)) + val usePin = pref.value == CUSTOM_PIN.ordinal.toString() + pin?.let { it.isVisible = usePin } } // Bolus - // Bolus if (pref.getKey() == rh.gs(R.string.key_bolus_protection)) { val pass: Preference? = findPreference(rh.gs(R.string.key_bolus_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() + val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString() + pass?.let { it.isVisible = usePassword } + val pin: Preference? = findPreference(rh.gs(R.string.key_bolus_pin)) + val usePin = pref.value == CUSTOM_PIN.ordinal.toString() + pin?.let { it.isVisible = usePin } } } if (pref is EditTextPreference) { - if (pref.getKey().contains("password") || pref.getKey().contains("secret")) { + if (pref.getKey().contains("password") || pref.getKey().contains("pin") || pref.getKey().contains("secret")) { pref.setSummary("******") } else if (pref.text != null) { pref.dialogMessage = pref.dialogMessage @@ -357,7 +361,10 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang rh.gs(R.string.key_bolus_password), rh.gs(R.string.key_master_password), rh.gs(R.string.key_application_password), - rh.gs(R.string.key_settings_password) + rh.gs(R.string.key_settings_password), + rh.gs(R.string.key_bolus_pin), + rh.gs(R.string.key_application_pin), + rh.gs(R.string.key_settings_pin) ) if (pref is Preference) { @@ -365,7 +372,11 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang if (sp.getString(pref.key, "").startsWith("hmac:")) { pref.summary = "******" } else { - pref.summary = rh.gs(R.string.password_not_set) + if (pref.getKey().contains("pin")) { + pref.summary = rh.gs(R.string.pin_not_set) + }else { + pref.summary = rh.gs(R.string.password_not_set) + } } } } @@ -411,6 +422,18 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang passwordCheck.setPassword(context, R.string.application_password, R.string.key_application_password) return true } + if (preference.key == rh.gs(R.string.key_settings_pin)) { + passwordCheck.setPassword(context, R.string.settings_pin, R.string.key_settings_pin, pinInput = true) + return true + } + if (preference.key == rh.gs(R.string.key_bolus_pin)) { + passwordCheck.setPassword(context, R.string.bolus_pin, R.string.key_bolus_pin, pinInput = true) + return true + } + if (preference.key == rh.gs(R.string.key_application_pin)) { + passwordCheck.setPassword(context, R.string.application_pin, R.string.key_application_pin, pinInput = true) + return true + } // NSClient copy settings if (preference.key == rh.gs(R.string.key_statuslights_copy_ns)) { nsSettingStatus.copyStatusLightsNsSettings(context) diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 0453fe87bb..0e006e2bbf 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -136,6 +136,7 @@ @string/biometric @string/master_password @string/custom_password + @string/custom_pin @@ -143,6 +144,7 @@ 1 2 3 + 4 diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index ee1076fcab..6b43facf2d 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -46,10 +46,13 @@ android:title="@string/settings_protection" /> + + + + + + Unit)?, cancel: (() -> Unit)? = null, fail: (() -> Unit)? = null) { + fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ((String) -> Unit)?, cancel: (() -> Unit)? = null, fail: (() -> Unit)? = null, pinInput: Boolean = false) { val password = sp.getString(preference, "") if (password == "") { ok?.invoke("") return } - val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) val alertDialogBuilder = AlertDialogHelper.Builder(context, R.style.DialogTheme) alertDialogBuilder.setView(promptsView) @@ -48,7 +48,10 @@ class PasswordCheck @Inject constructor( val userInput2 = promptsView.findViewById(R.id.password_prompt_pass_confirm) as EditText userInput2.visibility = View.GONE - + if (pinInput) { + userInput.setHint(R.string.pin_hint) + userInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + } val autoFillHintPasswordKind = context.getString(preference) userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}") userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES @@ -60,7 +63,8 @@ class PasswordCheck @Inject constructor( val enteredPassword = userInput.text.toString() if (cryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword) else { - ToastUtils.errorToast(context, context.getString(R.string.wrongpassword)) + val msg = if (pinInput) R.string.wrongpin else R.string.wrongpassword + ToastUtils.errorToast(context, context.getString(msg)) fail?.invoke() } } @@ -74,14 +78,19 @@ class PasswordCheck @Inject constructor( } @SuppressLint("InflateParams") - fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ((String) -> Unit)? = null, cancel: (() -> Unit)? = null, clear: (() -> Unit)? = null) { + fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ((String) -> Unit)? = null, cancel: (() -> Unit)? = null, clear: (() -> Unit)? = null, pinInput: Boolean = false) { val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) val alertDialogBuilder = AlertDialogHelper.Builder(context, R.style.DialogTheme) alertDialogBuilder.setView(promptsView) val userInput = promptsView.findViewById(R.id.password_prompt_pass) as EditText val userInput2 = promptsView.findViewById(R.id.password_prompt_pass_confirm) as EditText - + if (pinInput) { + userInput.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + userInput2.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD + userInput.setHint(R.string.pin_hint) + userInput2.setHint(R.string.pin_hint) + } val autoFillHintPasswordKind = context.getString(preference) userInput.setAutofillHints(AUTOFILL_HINT_NEW_PASSWORD, "aaps_${autoFillHintPasswordKind}") userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES @@ -93,18 +102,22 @@ class PasswordCheck @Inject constructor( val enteredPassword = userInput.text.toString() val enteredPassword2 = userInput2.text.toString() if (enteredPassword != enteredPassword2) { - ToastUtils.errorToast(context, context.getString(R.string.passwords_dont_match)) + val msg = if (pinInput) R.string.pin_dont_match else R.string.passwords_dont_match + ToastUtils.errorToast(context, context.getString(msg)) } else if (enteredPassword.isNotEmpty()) { sp.putString(preference, cryptoUtil.hashPassword(enteredPassword)) - ToastUtils.okToast(context, context.getString(R.string.password_set)) + val msg = if (pinInput) R.string.pin_set else R.string.password_set + ToastUtils.okToast(context, context.getString(msg)) ok?.invoke(enteredPassword) } else { if (sp.contains(preference)) { sp.remove(preference) - ToastUtils.graphicalToast(context, context.getString(R.string.password_cleared), R.drawable.ic_toast_delete_confirm) + val msg = if (pinInput) R.string.pin_cleared else R.string.password_cleared + ToastUtils.graphicalToast(context, context.getString(msg), R.drawable.ic_toast_delete_confirm) clear?.invoke() } else { - ToastUtils.warnToast(context, context.getString(R.string.password_not_changed)) + val msg = if (pinInput) R.string.pin_not_changed else R.string.password_not_changed + ToastUtils.warnToast(context, context.getString(msg)) cancel?.invoke() } } @@ -112,7 +125,8 @@ class PasswordCheck @Inject constructor( } .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> - ToastUtils.infoToast(context, context.getString(R.string.password_not_changed)) + val msg = if (pinInput) R.string.pin_not_changed else R.string.password_not_changed + ToastUtils.infoToast(context, context.getString(msg)) cancel?.invoke() dialog.cancel() } diff --git a/core/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt b/core/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt index d06583e17d..7edc13830e 100644 --- a/core/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt @@ -22,7 +22,8 @@ class ProtectionCheck @Inject constructor( NONE, BIOMETRIC, MASTER_PASSWORD, - CUSTOM_PASSWORD + CUSTOM_PASSWORD, + CUSTOM_PIN } private val passwordsResourceIDs = listOf( @@ -30,22 +31,33 @@ class ProtectionCheck @Inject constructor( R.string.key_application_password, R.string.key_bolus_password) + private val pinsResourceIDs = listOf( + R.string.key_settings_pin, + R.string.key_application_pin, + R.string.key_bolus_pin) + private val protectionTypeResourceIDs = listOf( R.string.key_settings_protection, R.string.key_application_protection, R.string.key_bolus_protection) - private val titleResourceIDs = listOf( + private val titlePassResourceIDs = listOf( R.string.settings_password, R.string.application_password, R.string.bolus_password) + private val titlePinResourceIDs = listOf( + R.string.settings_pin, + R.string.application_pin, + R.string.bolus_pin) + fun isLocked(protection: Protection): Boolean { return when (ProtectionType.values()[sp.getInt(protectionTypeResourceIDs[protection.ordinal], ProtectionType.NONE.ordinal)]) { ProtectionType.NONE -> false ProtectionType.BIOMETRIC -> true ProtectionType.MASTER_PASSWORD -> sp.getString(R.string.key_master_password, "") != "" ProtectionType.CUSTOM_PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != "" + ProtectionType.CUSTOM_PIN -> sp.getString(pinsResourceIDs[protection.ordinal], "") != "" } } @@ -55,11 +67,13 @@ class ProtectionCheck @Inject constructor( ProtectionType.NONE -> ok?.run() ProtectionType.BIOMETRIC -> - BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail, passwordCheck) + BiometricCheck.biometricPrompt(activity, titlePassResourceIDs[protection.ordinal], ok, cancel, fail, passwordCheck) ProtectionType.MASTER_PASSWORD -> passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() }) ProtectionType.CUSTOM_PASSWORD -> - passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], { ok?.run() }, { cancel?.run() }, { fail?.run() }) + passwordCheck.queryPassword(activity, titlePassResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], { ok?.run() }, { cancel?.run() }, { fail?.run() }) + ProtectionType.CUSTOM_PIN -> + passwordCheck.queryPassword(activity, titlePinResourceIDs[protection.ordinal], pinsResourceIDs[protection.ordinal], { ok?.run() }, { cancel?.run() }, { fail?.run() }, true) } } -} \ No newline at end of file +} diff --git a/core/src/main/res/layout/passwordprompt.xml b/core/src/main/res/layout/passwordprompt.xml index 90b14b14e4..bfbfbde942 100644 --- a/core/src/main/res/layout/passwordprompt.xml +++ b/core/src/main/res/layout/passwordprompt.xml @@ -2,8 +2,10 @@ + android:padding="10dp" + tools:context=".utils.protection.PasswordCheck"> Bolus protection Master password Settings password + Settings PIN Application password + Application PIN Bolus password + Bolus PIN Unlock settings Biometric Custom password + Custom PIN No protection Protection Master password is not set!\n\nPlease set your Master password in Preferences (%1$s → %2$s) @@ -19,15 +23,23 @@ In order to be effective, biometric protection needs a master password set for fallback.\n\nPlease set a master password! Password set! + PIN set! Password not set + PIN not set Password not changed + PIN not changed Password cleared! + PIN cleared! Enter password here + Enter PIN here master_password settings_password + settings_pin application_password + application_pin bolus_password + bolus_pin settings_protection application_protection bolus_protection diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index ea72c80e9d..65ad75af76 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -222,7 +222,9 @@ Wrong password + Wrong PIN Passwords don\'t match + PINs don\'t match Basal values not aligned to hours: %1$s