diff options
author | glowinthedark <48893368+glowinthedark@users.noreply.github.com> | 2020-02-29 21:46:31 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-01 02:16:31 +0530 |
commit | 947e41105b3fdfb7ce9734fb25119c46695f7bba (patch) | |
tree | 74c63b130b262a06ea068e10ffc20c1dd69d4897 /app/src/main/java | |
parent | bea3cd5457e331f04a9aeb5c99267f14908f5eaa (diff) |
Add xkpasswd-style password generator (#633)
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java')
7 files changed, 503 insertions, 9 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 50d5bcad..24ed62df 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -15,6 +15,7 @@ import android.os.Bundle import android.os.Environment import android.provider.DocumentsContract import android.provider.Settings +import android.text.TextUtils import android.view.MenuItem import android.view.accessibility.AccessibilityManager import android.widget.Toast @@ -23,6 +24,7 @@ import androidx.biometric.BiometricManager import androidx.core.content.getSystemService import androidx.documentfile.provider.DocumentFile import androidx.preference.CheckBoxPreference +import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager @@ -32,6 +34,7 @@ import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity import com.zeapo.pwdstore.crypto.PgpActivity import com.zeapo.pwdstore.git.GitActivity +import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity import com.zeapo.pwdstore.utils.PasswordRepository @@ -312,6 +315,51 @@ class UserPreference : AppCompatActivity() { } } } + + val prefCustomXkpwdDictionary = findPreference<Preference>("pref_key_custom_dict") + prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener { + callingActivity.storeCustomDictionaryPath() + true + } + val dictUri = sharedPreferences.getString("pref_key_custom_dict", "") + + if (!TextUtils.isEmpty(dictUri)) { + setCustomDictSummary(prefCustomXkpwdDictionary, Uri.parse(dictUri)) + } + + val prefIsCustomDict = findPreference<CheckBoxPreference>("pref_key_is_custom_dict") + val prefCustomDictPicker = findPreference<Preference>("pref_key_custom_dict") + val prefPwgenType = findPreference<ListPreference>("pref_key_pwgen_type") + showHideDependentPrefs(prefPwgenType?.value, prefIsCustomDict, prefCustomDictPicker) + + prefPwgenType?.onPreferenceChangeListener = ChangeListener() { _, newValue -> + showHideDependentPrefs(newValue, prefIsCustomDict, prefCustomDictPicker) + true + } + + prefIsCustomDict?.onPreferenceChangeListener = ChangeListener() { _, newValue -> + if (!(newValue as Boolean)) { + val customDictFile = File(context.filesDir, XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE) + if (customDictFile.exists()) { + FileUtils.deleteQuietly(customDictFile) + } + prefCustomDictPicker?.setSummary(R.string.xkpwgen_pref_custom_dict_picker_summary) + } + true + } + } + + private fun showHideDependentPrefs(newValue: Any?, prefIsCustomDict: CheckBoxPreference?, prefCustomDictPicker: Preference?) { + when (newValue as String) { + PgpActivity.KEY_PWGEN_TYPE_CLASSIC -> { + prefIsCustomDict?.isVisible = false + prefCustomDictPicker?.isVisible = false + } + PgpActivity.KEY_PWGEN_TYPE_XKPASSWD -> { + prefIsCustomDict?.isVisible = true + prefCustomDictPicker?.isVisible = true + } + } } override fun onResume() { @@ -396,6 +444,17 @@ class UserPreference : AppCompatActivity() { } } + /** + * Pick custom xkpwd dictionary from sdcard + */ + private fun storeCustomDictionaryPath() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/*" + } + startActivityForResult(intent, SET_CUSTOM_XKPWD_DICT) + } + @Throws(IOException::class) private fun copySshKey(uri: Uri) { // TODO: Check if valid SSH Key before import @@ -516,6 +575,27 @@ class UserPreference : AppCompatActivity() { } } } + SET_CUSTOM_XKPWD_DICT -> { + val uri: Uri = data.data ?: throw IOException("Unable to open file") + + Toast.makeText( + this, + this.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path), + Toast.LENGTH_SHORT + ).show() + val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext) + + prefs.edit().putString("pref_key_custom_dict", uri.toString()).apply() + + val customDictPref = prefsFragment.findPreference<Preference>("pref_key_custom_dict") + setCustomDictSummary(customDictPref, uri) + // copy user selected file to internal storage + val inputStream = this.contentResolver.openInputStream(uri) + val customDictFile = File(this.filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE) + FileUtils.copyInputStreamToFile(inputStream, customDictFile) + + setResult(Activity.RESULT_OK) + } } } super.onActivityResult(requestCode, resultCode, data) @@ -599,6 +679,16 @@ class UserPreference : AppCompatActivity() { private const val SELECT_GIT_DIRECTORY = 4 private const val EXPORT_PASSWORDS = 5 private const val EDIT_GIT_CONFIG = 6 + private const val SET_CUSTOM_XKPWD_DICT = 7 private const val TAG = "UserPreference" + + /** + * Set custom dictionary summary + */ + @JvmStatic + private fun setCustomDictSummary(customDictPref: Preference?, uri: Uri) { + val fileName = uri.path?.substring(uri.path?.lastIndexOf(":")!! + 1) + customDictPref?.setSummary("Selected dictionary: " + fileName) + } } } diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt index 4e49dfe3..42878171 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt @@ -38,6 +38,7 @@ import com.zeapo.pwdstore.PasswordEntry import com.zeapo.pwdstore.R import com.zeapo.pwdstore.UserPreference import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment +import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment import com.zeapo.pwdstore.utils.Otp import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -142,8 +143,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { setContentView(R.layout.encrypt_layout) generate_password?.setOnClickListener { - PasswordGeneratorDialogFragment() - .show(supportFragmentManager, "generator") + when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) { + KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment() + .show(supportFragmentManager, "generator") + KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment() + .show(supportFragmentManager, "xkpwgenerator") + } } title = getString(R.string.new_password_title) @@ -505,8 +510,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { private fun editPassword() { setContentView(R.layout.encrypt_layout) generate_password?.setOnClickListener { - PasswordGeneratorDialogFragment() - .show(supportFragmentManager, "generator") + when (settings.getString("pref_key_pwgen_type", KEY_PWGEN_TYPE_CLASSIC)) { + KEY_PWGEN_TYPE_CLASSIC -> PasswordGeneratorDialogFragment() + .show(supportFragmentManager, "generator") + KEY_PWGEN_TYPE_XKPASSWD -> XkPasswordGeneratorDialogFragment() + .show(supportFragmentManager, "xkpwgenerator") + } } title = getString(R.string.edit_password_title) @@ -853,6 +862,9 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { const val TAG = "PgpActivity" + const val KEY_PWGEN_TYPE_CLASSIC = "classic" + const val KEY_PWGEN_TYPE_XKPASSWD = "xkpasswd" + private var delayTask: DelayShow? = null /** diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/CapsType.kt b/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/CapsType.kt new file mode 100644 index 00000000..a0311b8d --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/CapsType.kt @@ -0,0 +1,9 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.pwgenxkpwd + +enum class CapsType { + lowercase, UPPERCASE, TitleCase, Sentencecase, As_iS +} diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/PasswordBuilder.kt b/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/PasswordBuilder.kt new file mode 100644 index 00000000..a526490f --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/PasswordBuilder.kt @@ -0,0 +1,166 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.pwgenxkpwd + +import android.content.Context +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.pwgen.PasswordGenerator +import com.zeapo.pwdstore.pwgen.PasswordGenerator.PasswordGeneratorExeption +import java.io.IOException +import java.security.SecureRandom +import java.util.ArrayList +import java.util.Locale + +class PasswordBuilder(ctx: Context) { + + private var numSymbols = 0 + private var isAppendSymbolsSeparator = false + private var context = ctx + private var numWords = 3 + private var maxWordLength = 9 + private var minWordLength = 5 + private var separator = "." + private var capsType = CapsType.Sentencecase + private var prependDigits = 0 + private var numDigits = 0 + private var isPrependWithSeparator = false + private var isAppendNumberSeparator = false + + fun setNumberOfWords(amount: Int) = apply { + numWords = amount + } + + fun setMinimumWordLength(min: Int) = apply { + minWordLength = min + } + + fun setMaximumWordLength(max: Int) = apply { + maxWordLength = max + } + + fun setSeparator(separator: String) = apply { + this.separator = separator + } + + fun setCapitalization(capitalizationScheme: CapsType) = apply { + capsType = capitalizationScheme + } + + @JvmOverloads + fun prependNumbers(numDigits: Int, addSeparator: Boolean = true) = apply { + prependDigits = numDigits + isPrependWithSeparator = addSeparator + } + + @JvmOverloads + fun appendNumbers(numDigits: Int, addSeparator: Boolean = false) = apply { + this.numDigits = numDigits + isAppendNumberSeparator = addSeparator + } + + @JvmOverloads + fun appendSymbols(numSymbols: Int, addSeparator: Boolean = false) = apply { + this.numSymbols = numSymbols + isAppendSymbolsSeparator = addSeparator + } + + private fun generateRandomNumberSequence(totalNumbers: Int): String { + val secureRandom = SecureRandom() + val numbers = StringBuilder(totalNumbers) + + for (i in 0 until totalNumbers) { + numbers.append(secureRandom.nextInt(10)) + } + return numbers.toString() + } + + private fun generateRandomSymbolSequence(numSymbols: Int): String { + val secureRandom = SecureRandom() + val numbers = StringBuilder(numSymbols) + + for (i in 0 until numSymbols) { + numbers.append(SYMBOLS[secureRandom.nextInt(SYMBOLS.length)]) + } + return numbers.toString() + } + + @Throws(PasswordGenerator.PasswordGeneratorExeption::class) + fun create(): String { + val wordBank = ArrayList<String>() + val secureRandom = SecureRandom() + val password = StringBuilder() + + if (prependDigits != 0) { + password.append(generateRandomNumberSequence(prependDigits)) + if (isPrependWithSeparator) { + password.append(separator) + } + } + try { + val dictionary = XkpwdDictionary(context) + val words = dictionary.words + for (wordLength in words.keys) { + if (wordLength in minWordLength..maxWordLength) { + wordBank.addAll(words[wordLength]!!) + } + } + + if (wordBank.size == 0) { + throw PasswordGeneratorExeption(context.getString(R.string.xkpwgen_builder_error, minWordLength, maxWordLength)) + } + + for (i in 0 until numWords) { + val randomIndex = secureRandom.nextInt(wordBank.size) + var s = wordBank[randomIndex] + + if (capsType != CapsType.As_iS) { + s = s.toLowerCase(Locale.getDefault()) + when (capsType) { + CapsType.UPPERCASE -> s = s.toUpperCase(Locale.getDefault()) + CapsType.Sentencecase -> { + if (i == 0) { + s = capitalize(s) + } + } + CapsType.TitleCase -> { + s = capitalize(s) + } + } + } + password.append(s) + wordBank.removeAt(randomIndex) + if (i + 1 < numWords) { + password.append(separator) + } + } + } catch (e: IOException) { + throw PasswordGeneratorExeption("Failed generating password!") + } + if (numDigits != 0) { + if (isAppendNumberSeparator) { + password.append(separator) + } + password.append(generateRandomNumberSequence(numDigits)) + } + if (numSymbols != 0) { + if (isAppendSymbolsSeparator) { + password.append(separator) + } + password.append(generateRandomSymbolSequence(numSymbols)) + } + return password.toString() + } + + private fun capitalize(s: String): String { + var result = s + val lower = result.toLowerCase(Locale.getDefault()) + result = lower.substring(0, 1).toUpperCase(Locale.getDefault()) + result.substring(1) + return result + } + + companion object { + private const val SYMBOLS = "!@\$%^&*-_+=:|~?/.;#" + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/XkpwdDictionary.kt b/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/XkpwdDictionary.kt new file mode 100644 index 00000000..27f8fbd0 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/pwgenxkpwd/XkpwdDictionary.kt @@ -0,0 +1,58 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.pwgenxkpwd + +import android.content.Context +import android.text.TextUtils +import androidx.preference.PreferenceManager +import com.zeapo.pwdstore.R +import java.io.File +import java.util.ArrayList +import java.util.HashMap + +class XkpwdDictionary(context: Context) { + val words: HashMap<Int, ArrayList<String>> = HashMap() + + init { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + var lines: List<String> = listOf() + + if (prefs.getBoolean("pref_key_is_custom_dict", false)) { + + val uri = prefs.getString("pref_key_custom_dict", "") + + if (!TextUtils.isEmpty(uri)) { + val customDictFile = File(context.filesDir, XKPWD_CUSTOM_DICT_FILE) + + if (customDictFile.exists() && customDictFile.canRead()) { + lines = customDictFile.inputStream().bufferedReader().readLines() + } + } + } + + if (lines.isEmpty()) { + lines = context.getResources().openRawResource(R.raw.xkpwdict).bufferedReader().readLines() + } + + for (word in lines) { + if (!word.trim { it <= ' ' }.contains(" ")) { + val length = word.trim { it <= ' ' }.length + + if (length > 0) { + if (!words.containsKey(length)) { + words[length] = ArrayList() + } + val strings = words[length]!! + strings.add(word.trim { it <= ' ' }) + } + } + } + } + + companion object { + const val XKPWD_CUSTOM_DICT_FILE = "custom_dict.txt" + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt index af344bd4..26b6190f 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/PasswordGeneratorDialogFragment.kt @@ -4,7 +4,6 @@ */ package com.zeapo.pwdstore.ui.dialogs -import android.app.Activity import android.app.Dialog import android.content.Context import android.graphics.Typeface @@ -26,7 +25,7 @@ import com.zeapo.pwdstore.pwgen.PasswordGenerator.setPrefs class PasswordGeneratorDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = MaterialAlertDialogBuilder(requireContext()) - val callingActivity: Activity = requireActivity() + val callingActivity = requireActivity() val inflater = callingActivity.layoutInflater val view = inflater.inflate(R.layout.fragment_pwgen, null) val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf") @@ -49,8 +48,8 @@ class PasswordGeneratorDialogFragment : DialogFragment() { val edit = callingActivity.findViewById<EditText>(R.id.crypto_password_edit) edit.setText(passwordText.text) } - builder.setNegativeButton(resources.getString(R.string.dialog_cancel)) { _, _ -> } - builder.setNeutralButton(resources.getString(R.string.pwgen_generate), null) + builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> } + builder.setNegativeButton(resources.getString(R.string.pwgen_generate), null) val dialog = builder.setTitle(this.resources.getString(R.string.pwgen_title)).create() dialog.setOnShowListener { setPreferences() @@ -60,7 +59,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() { Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show() passwordText.text = "" } - dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener { + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { setPreferences() try { passwordText.text = generate(callingActivity.applicationContext)[0] diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt new file mode 100644 index 00000000..f42b6536 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/XkPasswordGeneratorDialogFragment.kt @@ -0,0 +1,160 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.ui.dialogs + +import android.app.Dialog +import android.content.Context +import android.content.SharedPreferences +import android.graphics.Typeface +import android.os.Bundle +import android.widget.CheckBox +import android.widget.EditText +import android.widget.Spinner +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatEditText +import androidx.appcompat.widget.AppCompatTextView +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.pwgen.PasswordGenerator +import com.zeapo.pwdstore.pwgenxkpwd.CapsType +import com.zeapo.pwdstore.pwgenxkpwd.PasswordBuilder +import timber.log.Timber + +/** A placeholder fragment containing a simple view. */ +class XkPasswordGeneratorDialogFragment : DialogFragment() { + + private lateinit var editSeparator: AppCompatEditText + private lateinit var editNumWords: AppCompatEditText + private lateinit var cbSymbols: CheckBox + private lateinit var spinnerCapsType: Spinner + private lateinit var cbNumbers: CheckBox + private lateinit var prefs: SharedPreferences + private lateinit var spinnerNumbersCount: Spinner + private lateinit var spinnerSymbolsCount: Spinner + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val builder = MaterialAlertDialogBuilder(requireContext()) + val callingActivity = requireActivity() + val inflater = callingActivity.layoutInflater + val view = inflater.inflate(R.layout.fragment_xkpwgen, null) + + val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf") + + builder.setView(view) + + prefs = callingActivity.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE) + + cbNumbers = view.findViewById<CheckBox>(R.id.xknumerals) + cbNumbers.isChecked = prefs.getBoolean(PREF_KEY_USE_NUMERALS, false) + + spinnerNumbersCount = view.findViewById<Spinner>(R.id.xk_numbers_count) + + val storedNumbersCount = prefs.getInt(PREF_KEY_NUMBERS_COUNT, 0) + spinnerNumbersCount.setSelection(storedNumbersCount) + + cbSymbols = view.findViewById<CheckBox>(R.id.xksymbols) + cbSymbols.isChecked = prefs.getBoolean(PREF_KEY_USE_SYMBOLS, false) != false + spinnerSymbolsCount = view.findViewById<Spinner>(R.id.xk_symbols_count) + val symbolsCount = prefs.getInt(PREF_KEY_SYMBOLS_COUNT, 0) + spinnerSymbolsCount.setSelection(symbolsCount) + + val previousStoredCapStyle: String = try { + prefs.getString(PREF_KEY_CAPITALS_STYLE, DEFAULT_CAPS_STYLE)!! + } catch (e: Exception) { + Timber.tag("xkpw").e(e) + DEFAULT_CAPS_STYLE + } + spinnerCapsType = view.findViewById<Spinner>(R.id.xkCapType) + + val lastCapitalsStyleIndex: Int + + lastCapitalsStyleIndex = try { + CapsType.valueOf(previousStoredCapStyle).ordinal + } catch (e: Exception) { + Timber.tag("xkpw").e(e) + DEFAULT_CAPS_INDEX + } + spinnerCapsType.setSelection(lastCapitalsStyleIndex) + + editNumWords = view.findViewById<AppCompatEditText>(R.id.xk_num_words) + editNumWords.setText(prefs.getString(PREF_KEY_NUM_WORDS, DEFAULT_NUMBER_OF_WORDS)) + + editSeparator = view.findViewById<AppCompatEditText>(R.id.xk_separator) + editSeparator.setText(prefs.getString(PREF_KEY_SEPARATOR, DEFAULT_WORD_SEPARATOR)) + + val passwordText: AppCompatTextView = view.findViewById(R.id.xkPasswordText) + passwordText.typeface = monoTypeface + + builder.setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> + setPreferences() + val edit = callingActivity.findViewById<EditText>(R.id.crypto_password_edit) + edit.setText(passwordText.text) + } + + // flip neutral and negative buttons + builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> } + builder.setNegativeButton(resources.getString(R.string.pwgen_generate), null) + + val dialog = builder.setTitle(this.resources.getString(R.string.xkpwgen_title)).create() + + dialog.setOnShowListener { + setPreferences() + makeAndSetPassword(passwordText) + + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { + setPreferences() + makeAndSetPassword(passwordText) + } + } + return dialog + } + + private fun makeAndSetPassword(passwordText: AppCompatTextView) { + try { + passwordText.text = PasswordBuilder(requireContext()) + .setNumberOfWords(Integer.valueOf(editNumWords.text.toString())) + .setMinimumWordLength(DEFAULT_MIN_WORD_LENGTH) + .setMaximumWordLength(DEFAULT_MAX_WORD_LENGTH) + .setSeparator(editSeparator.text.toString()) + .appendNumbers(if (cbNumbers.isChecked) Integer.parseInt(spinnerNumbersCount.selectedItem as String) else 0) + .appendSymbols(if (cbSymbols.isChecked) Integer.parseInt(spinnerSymbolsCount.selectedItem as String) else 0) + .setCapitalization(CapsType.valueOf(spinnerCapsType.selectedItem.toString())).create() + } catch (e: PasswordGenerator.PasswordGeneratorExeption) { + Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show() + Timber.tag("xkpw").e(e, "failure generating xkpasswd") + passwordText.text = FALLBACK_ERROR_PASS + } + } + + private fun setPreferences() { + prefs.edit().putBoolean(PREF_KEY_USE_NUMERALS, cbNumbers.isChecked) + .putBoolean(PREF_KEY_USE_SYMBOLS, cbSymbols.isChecked) + .putString(PREF_KEY_CAPITALS_STYLE, spinnerCapsType.selectedItem.toString()) + .putString(PREF_KEY_NUM_WORDS, editNumWords.text.toString()) + .putString(PREF_KEY_SEPARATOR, editSeparator.text.toString()) + .putInt(PREF_KEY_NUMBERS_COUNT, Integer.parseInt(spinnerNumbersCount.selectedItem as String) - 1) + .putInt(PREF_KEY_SYMBOLS_COUNT, Integer.parseInt(spinnerSymbolsCount.selectedItem as String) - 1) + .apply() + } + + companion object { + const val PREF_KEY_USE_NUMERALS = "pref_key_use_numerals" + const val PREF_KEY_USE_SYMBOLS = "pref_key_use_symbols" + const val PREF_KEY_CAPITALS_STYLE = "pref_key_capitals_style" + const val PREF_KEY_NUM_WORDS = "pref_key_num_words" + const val PREF_KEY_SEPARATOR = "pref_key_separator" + const val PREF_KEY_NUMBERS_COUNT = "pref_key_xkpwgen_numbers_count" + const val PREF_KEY_SYMBOLS_COUNT = "pref_key_symbols_count" + val DEFAULT_CAPS_STYLE = CapsType.Sentencecase.name + val DEFAULT_CAPS_INDEX = CapsType.Sentencecase.ordinal + const val DEFAULT_NUMBER_OF_WORDS = "3" + const val DEFAULT_WORD_SEPARATOR = "." + const val DEFAULT_MIN_WORD_LENGTH = 3 + const val DEFAULT_MAX_WORD_LENGTH = 9 + const val FALLBACK_ERROR_PASS = "42" + } +} |