Add xkpasswd-style password generator (#633)

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
glowinthedark
2020-02-29 21:46:31 +01:00
committed by GitHub
parent bea3cd5457
commit 947e41105b
13 changed files with 9719 additions and 10 deletions

View File

@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
### Added
- Copy implicit username (password filename) by long pressing
- Create xkpasswd style passwords
### Fixed
- Can't delete folders containing a password

View File

@@ -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)
}
}
}

View File

@@ -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
/**

View File

@@ -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
}

View File

@@ -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 = "!@\$%^&*-_+=:|~?/.;#"
}
}

View File

@@ -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"
}
}

View File

@@ -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]

View File

@@ -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"
}
}

View File

@@ -0,0 +1,142 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingTop="20dp"
android:paddingRight="24dp"
android:paddingBottom="20dp"
tools:context=".MainActivityFragment">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/xkPasswordText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal"
android:weightSum="2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight=".6"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/pwgen_include"
android:textAppearance="?android:attr/textAppearanceSmall" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/xknumerals"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:text="@string/xkpwgen_numbers"
android:layout_height="wrap_content" />
<Spinner
android:id="@+id/xk_numbers_count"
android:layout_width="fill_parent"
android:minWidth="40dp"
android:dropDownWidth="40dp"
android:gravity="center_vertical"
android:layout_height="wrap_content"
android:entries="@array/xk_range_1_10"
android:entryValues="@array/xk_range_1_10"
android:spinnerMode="dropdown" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/xksymbols"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:text="@string/xkpwgen_symbols"
android:layout_height="wrap_content"/>
<Spinner
android:id="@+id/xk_symbols_count"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minWidth="40dp"
android:dropDownWidth="40dp"
android:entries="@array/xk_range_1_10"
android:entryValues="@array/xk_range_1_10"
android:spinnerMode="dropdown" />
</LinearLayout>
<Spinner
android:id="@+id/xkCapType"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:entries="@array/capitalization_type_values"
android:entryValues="@array/capitalization_type_values"
android:spinnerMode="dropdown" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.4"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/xkpwgen_length"
android:textAppearance="?android:attr/textAppearanceSmall" />
<EditText
android:id="@+id/xk_num_words"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:maxLength="2"
android:ems="10"
android:inputType="number" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/xkpwgen_separator"
android:textAppearance="?android:attr/textAppearanceSmall" />
<EditText
android:id="@+id/xk_separator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:ems="10"
android:autofillHints=""
android:hint="@string/xkpwgen_separator_character"
android:inputType="text" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

File diff suppressed because it is too large Load Diff

View File

@@ -19,4 +19,33 @@
<item>FILE_FIRST</item>
<item>INDEPENDENT</item>
</string-array>
<string-array name="capitalization_type_entries">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="capitalization_type_values">
<item>lowercase</item>
<item>UPPERCASE</item>
<item>TitleCase</item>
<item>Sentencecase</item>
</string-array>
<string-array name="xk_range_1_10">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</string-array>
<string-array name="pwgen_provider">
<item>classic</item>
<item>xkpasswd</item>
</string-array>
</resources>

View File

@@ -190,6 +190,25 @@
<string name="pwgen_no_chars_error">No characters included</string>
<string name="pwgen_length_too_short_error">Length too short for selected criteria</string>
<!-- XKPWD password generator -->
<string name="xkpwgen_title">Xkpasswd Generator</string>
<string name="xkpwgen_length">Total words</string>
<string name="xkpwgen_separator">Separator</string>
<string name="xkpwgen_custom_dict_imported">Custom wordlist: %1$s</string>
<string name="xkpwgen_separator_character">separator character</string>
<string name="xkpwgen_numbers">numbers:</string>
<string name="xkpwgen_symbols">symbols:</string>
<string name="xkpwgen_builder_error">Selected dictionary does not contain enough words of given length %1$d..%2$d</string>
<!-- XKPWD prefs -->
<string name="xkpwgen_pref_gentype_title">Password generator type</string>
<string name="xkpwgen_pref_custom_dict_title">Custom wordlist</string>
<string name="xkpwgen_pref_custom_dict_summary_on">Using custom wordlist file</string>
<string name="xkpwgen_pref_custom_dict_summary_off">Using built-in wordlist</string>
<string name="xkpwgen_pref_custom_dict_picker_title">Custom worldlist file</string>
<string name="xkpwgen_pref_custom_dict_picker_summary">Tap to pick a custom wordlist file containing one word per line</string>
<!-- ssh keygen fragment -->
<string name="ssh_keygen_length">Length</string>
<string name="ssh_keygen_passphrase">Passphrase</string>
@@ -285,4 +304,6 @@
<string name="pref_search_on_start_hint">Open search bar when app is launched</string>
<string name="pref_search_from_root">Always search from root</string>
<string name="pref_search_from_root_hint">Search from root of store regardless of currently open directory</string>
<string name="password_generator_category_title">Password Generator</string>
</resources>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.PreferenceCategory android:title="@string/pref_git_title">
<androidx.preference.Preference
android:key="git_server_info"
@@ -48,6 +49,27 @@
android:title="@string/pref_key_title" />
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory android:title="@string/password_generator_category_title">
<androidx.preference.ListPreference
android:key="pref_key_pwgen_type"
android:title="@string/xkpwgen_pref_gentype_title"
android:defaultValue="classic"
android:entries="@array/pwgen_provider"
android:entryValues="@array/pwgen_provider"
app:useSimpleSummaryProvider="true"
android:persistent="true" />
<androidx.preference.CheckBoxPreference
android:key="pref_key_is_custom_dict"
android:title="@string/xkpwgen_pref_custom_dict_title"
android:summaryOn="@string/xkpwgen_pref_custom_dict_summary_on"
android:summaryOff="@string/xkpwgen_pref_custom_dict_summary_off"/>
<androidx.preference.Preference
android:key="pref_key_custom_dict"
android:title="@string/xkpwgen_pref_custom_dict_picker_title"
android:summary="@string/xkpwgen_pref_custom_dict_picker_summary"
android:dependency="pref_key_is_custom_dict"/>
</androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory android:title="@string/pref_general_title">
<androidx.preference.EditTextPreference
android:defaultValue="45"