mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Nested fragment for general and database settings
This commit is contained in:
@@ -108,10 +108,14 @@ abstract class LockingActivity : StylishActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
|
||||
// To refresh when back to normal workflow from selection workflow
|
||||
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
||||
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
|
||||
|
||||
invalidateOptionsMenu()
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
// End activity if database not loaded
|
||||
if (!Database.getInstance().loaded) {
|
||||
@@ -127,10 +131,6 @@ abstract class LockingActivity : StylishActivity() {
|
||||
if (!mExitLock)
|
||||
TimeoutHelper.recordTime(this)
|
||||
}
|
||||
|
||||
mProgressDialogThread?.registerProgressTask()
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.autofill.AutofillManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreference
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.ProFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
|
||||
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
when (screen) {
|
||||
Screen.APPLICATION -> {
|
||||
onCreateApplicationPreferences(rootKey)
|
||||
}
|
||||
Screen.FORM_FILLING -> {
|
||||
onCreateFormFillingPreference(rootKey)
|
||||
}
|
||||
Screen.ADVANCED_UNLOCK -> {
|
||||
onCreateAdvancedUnlockPreferences(rootKey)
|
||||
}
|
||||
Screen.APPEARANCE -> {
|
||||
onCreateAppearancePreferences(rootKey)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateApplicationPreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_application, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
allowCopyPassword()
|
||||
|
||||
findPreference<Preference>(getString(R.string.keyfile_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.recentfile_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateFormFillingPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_form_filling, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key))
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val autofillManager = activity.getSystemService(AutofillManager::class.java)
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices())
|
||||
autoFillEnablePreference?.isChecked = autofillManager.hasEnabledAutofillServices()
|
||||
autoFillEnablePreference?.onPreferenceClickListener = object : Preference.OnPreferenceClickListener {
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
override fun onPreferenceClick(preference: Preference): Boolean {
|
||||
if ((preference as SwitchPreference).isChecked) {
|
||||
try {
|
||||
startEnableService()
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
val error = getString(R.string.error_autofill_enable_service)
|
||||
preference.isChecked = false
|
||||
Log.d(javaClass.name, error, e)
|
||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
} else {
|
||||
disableService()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private fun disableService() {
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) {
|
||||
autofillManager.disableAutofillServices()
|
||||
} else {
|
||||
Log.d(javaClass.name, "Sample service already disabled.")
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Throws(ActivityNotFoundException::class)
|
||||
private fun startEnableService() {
|
||||
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
|
||||
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
|
||||
// TODO Autofill
|
||||
intent.data = Uri.parse("package:com.example.android.autofill.service")
|
||||
Log.d(javaClass.name, "enableService(): intent=$intent")
|
||||
startActivityForResult(intent, REQUEST_CODE_AUTOFILL)
|
||||
} else {
|
||||
Log.d(javaClass.name, "Sample service already enabled.")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
autoFillEnablePreference?.setOnPreferenceClickListener { preference ->
|
||||
(preference as SwitchPreference).isChecked = false
|
||||
val fragmentManager = fragmentManager!!
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.O)
|
||||
.show(fragmentManager, "unavailableFeatureDialog")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url)
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.magic_keyboard_key))?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
})
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(context, MagikIMESettings::class.java))
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url)
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.autofill_explanation_url)
|
||||
false
|
||||
}
|
||||
|
||||
// Present in two places
|
||||
allowCopyPassword()
|
||||
}
|
||||
|
||||
private fun allowCopyPassword() {
|
||||
val copyPasswordPreference: SwitchPreference? = findPreference(getString(R.string.allow_copy_password_key))
|
||||
copyPasswordPreference?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue as Boolean && context != null) {
|
||||
val message = getString(R.string.allow_copy_password_warning) +
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning)
|
||||
AlertDialog
|
||||
.Builder(context!!)
|
||||
.setMessage(message)
|
||||
.create()
|
||||
.apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable))
|
||||
{ dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable))
|
||||
{ dialog, _ ->
|
||||
copyPasswordPreference.isChecked = false
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateAdvancedUnlockPreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key))
|
||||
// < M solve verifyError exception
|
||||
var biometricUnlockSupported = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val biometricCanAuthenticate = BiometricManager.from(activity).canAuthenticate()
|
||||
biometricUnlockSupported = biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
if (!biometricUnlockSupported) {
|
||||
// False if under Marshmallow
|
||||
biometricUnlockEnablePreference?.apply {
|
||||
isChecked = false
|
||||
setOnPreferenceClickListener { preference ->
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
(preference as SwitchPreference).isChecked = false
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||
.show(fragmentManager, "unavailableFeatureDialog")
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
|
||||
if (!biometricUnlockSupported) {
|
||||
deleteKeysFingerprints?.isEnabled = false
|
||||
} else {
|
||||
deleteKeysFingerprints?.setOnPreferenceClickListener {
|
||||
context?.let { context ->
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(resources.getString(R.string.biometric_delete_all_key_warning))
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(resources.getString(android.R.string.yes)
|
||||
) { _, _ ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric(
|
||||
activity,
|
||||
object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback {
|
||||
override fun onInvalidKeyException(e: Exception) {}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
Toast.makeText(context,
|
||||
getString(R.string.biometric_scanning_error, e.localizedMessage),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
CipherDatabaseAction.getInstance(context.applicationContext).deleteAll()
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.no))
|
||||
{ _, _ -> }.show()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.advanced_unlock_explanation_url)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateAppearancePreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_appearance, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
var styleEnabled = true
|
||||
val styleIdString = newValue as String
|
||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!))
|
||||
for (themeIdDisabled in BuildConfig.STYLES_DISABLED) {
|
||||
if (themeIdDisabled == styleIdString) {
|
||||
styleEnabled = false
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (styleEnabled) {
|
||||
Stylish.assignStyle(styleIdString)
|
||||
activity.recreate()
|
||||
}
|
||||
styleEnabled
|
||||
}
|
||||
|
||||
findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
var iconPackEnabled = true
|
||||
val iconPackId = newValue as String
|
||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!))
|
||||
for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) {
|
||||
if (iconPackIdDisabled == iconPackId) {
|
||||
iconPackEnabled = false
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (iconPackEnabled) {
|
||||
IconPackChooser.setSelectedIconPack(iconPackId)
|
||||
}
|
||||
iconPackEnabled
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener {
|
||||
// To allow only one toast
|
||||
if (mCount == 0) {
|
||||
val sharedPreferences = Education.getEducationSharedPreferences(context!!)
|
||||
val editor = sharedPreferences.edit()
|
||||
for (resourceId in Education.educationResourcesKeys) {
|
||||
editor.putBoolean(getString(resourceId), false)
|
||||
}
|
||||
editor.apply()
|
||||
Toast.makeText(context, R.string.reset_education_screens_text, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
mCount++
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
activity?.let { activity ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key))
|
||||
if (autoFillEnablePreference != null) {
|
||||
val autofillManager = activity.getSystemService(AutofillManager::class.java)
|
||||
autoFillEnablePreference.isChecked = autofillManager != null
|
||||
&& autofillManager.hasEnabledAutofillServices()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var mCount = 0
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
activity?.let { activity ->
|
||||
if (mCount == 10) {
|
||||
Education.getEducationSharedPreferences(activity).edit()
|
||||
.putBoolean(getString(R.string.education_screen_reclicked_key), true).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val REQUEST_CODE_AUTOFILL = 5201
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.SwitchPreference
|
||||
import com.kunzisoft.androidclearchroma.ChromaUtil
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.lock
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.settings.preference.*
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
|
||||
class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
|
||||
private var mDatabase: Database = Database.getInstance()
|
||||
private var mDatabaseReadOnly: Boolean = false
|
||||
private var mDatabaseAutoSaveEnabled: Boolean = true
|
||||
|
||||
private var dbNamePref: InputTextPreference? = null
|
||||
private var dbDescriptionPref: InputTextPreference? = null
|
||||
private var dbDefaultUsername: InputTextPreference? = null
|
||||
private var dbCustomColorPref: DialogColorPreference? = null
|
||||
private var dbDataCompressionPref: Preference? = null
|
||||
private var recycleBinGroupPref: Preference? = null
|
||||
private var dbMaxHistoryItemsPref: InputNumberPreference? = null
|
||||
private var dbMaxHistorySizePref: InputNumberPreference? = null
|
||||
private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null
|
||||
private var mKeyDerivationPref: DialogListExplanationPreference? = null
|
||||
private var mRoundPref: InputKdfNumberPreference? = null
|
||||
private var mMemoryPref: InputKdfNumberPreference? = null
|
||||
private var mParallelismPref: InputKdfNumberPreference? = null
|
||||
|
||||
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
mDatabaseReadOnly = mDatabase.isReadOnly
|
||||
|| ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
when (screen) {
|
||||
Screen.DATABASE -> {
|
||||
onCreateDatabasePreference(rootKey)
|
||||
}
|
||||
Screen.DATABASE_SECURITY -> {
|
||||
onCreateDatabaseSecurityPreference(rootKey)
|
||||
}
|
||||
Screen.DATABASE_MASTER_KEY -> {
|
||||
onCreateDatabaseMasterKeyPreference(rootKey)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabasePreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database, rootKey)
|
||||
|
||||
if (mDatabase.loaded) {
|
||||
|
||||
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
|
||||
|
||||
// Database name
|
||||
dbNamePref = findPreference(getString(R.string.database_name_key))
|
||||
if (mDatabase.allowName) {
|
||||
dbNamePref?.summary = mDatabase.name
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbNamePref)
|
||||
}
|
||||
|
||||
// Database description
|
||||
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
|
||||
if (mDatabase.allowDescription) {
|
||||
dbDescriptionPref?.summary = mDatabase.description
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
|
||||
}
|
||||
|
||||
// Database default username
|
||||
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
|
||||
if (mDatabase.allowDefaultUsername) {
|
||||
dbDefaultUsername?.summary = mDatabase.defaultUsername
|
||||
} else {
|
||||
dbDefaultUsername?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
|
||||
}
|
||||
|
||||
// Database custom color
|
||||
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
|
||||
if (mDatabase.allowCustomColor) {
|
||||
dbCustomColorPref?.apply {
|
||||
try {
|
||||
color = Color.parseColor(mDatabase.customColor)
|
||||
summary = mDatabase.customColor
|
||||
} catch (e: Exception) {
|
||||
color = DialogColorPreference.DISABLE_COLOR
|
||||
summary = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dbCustomColorPref?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
|
||||
}
|
||||
|
||||
// Version
|
||||
findPreference<Preference>(getString(R.string.database_version_key))
|
||||
?.summary = mDatabase.version
|
||||
|
||||
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key))
|
||||
|
||||
// Database compression
|
||||
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
||||
if (mDatabase.allowDataCompression) {
|
||||
dbDataCompressionPref?.summary = (mDatabase.compressionAlgorithm
|
||||
?: PwCompressionAlgorithm.None).getName(resources)
|
||||
} else {
|
||||
dbCompressionPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
|
||||
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
|
||||
|
||||
// Recycle bin
|
||||
if (mDatabase.allowRecycleBin) {
|
||||
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
|
||||
recycleBinEnablePref?.apply {
|
||||
isChecked = mDatabase.isRecycleBinEnabled
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val recycleBinEnabled = newValue as Boolean
|
||||
mDatabase.isRecycleBinEnabled = recycleBinEnabled
|
||||
if (recycleBinEnabled) {
|
||||
mDatabase.ensureRecycleBinExists(resources)
|
||||
} else {
|
||||
mDatabase.removeRecycleBin()
|
||||
}
|
||||
refreshRecycleBinGroup()
|
||||
// Save the database if not in readonly mode
|
||||
(context as SettingsActivity?)?.
|
||||
mProgressDialogThread?.startDatabaseSave(mDatabaseAutoSaveEnabled)
|
||||
true
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// Recycle Bin group
|
||||
refreshRecycleBinGroup()
|
||||
} else {
|
||||
dbRecycleBinPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
// History
|
||||
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
|
||||
?.isVisible = mDatabase.manageHistory == true
|
||||
|
||||
// Max history items
|
||||
dbMaxHistoryItemsPref = findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))?.apply {
|
||||
summary = mDatabase.historyMaxItems.toString()
|
||||
}
|
||||
|
||||
// Max history size
|
||||
dbMaxHistorySizePref = findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))?.apply {
|
||||
summary = mDatabase.historyMaxSize.toString()
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshRecycleBinGroup() {
|
||||
recycleBinGroupPref?.apply {
|
||||
if (mDatabase.isRecycleBinEnabled) {
|
||||
summary = mDatabase.recycleBin?.title
|
||||
isEnabled = true
|
||||
} else {
|
||||
summary = null
|
||||
isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseSecurityPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database_security, rootKey)
|
||||
|
||||
if (mDatabase.loaded) {
|
||||
// Encryption Algorithm
|
||||
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
|
||||
summary = mDatabase.getEncryptionAlgorithmName(resources)
|
||||
}
|
||||
|
||||
// Key derivation function
|
||||
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
|
||||
summary = mDatabase.getKeyDerivationName(resources)
|
||||
}
|
||||
|
||||
// Round encryption
|
||||
mRoundPref = findPreference<InputKdfNumberPreference>(getString(R.string.transform_rounds_key))?.apply {
|
||||
summary = mDatabase.numberKeyEncryptionRounds.toString()
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
mMemoryPref = findPreference<InputKdfNumberPreference>(getString(R.string.memory_usage_key))?.apply {
|
||||
summary = mDatabase.memoryUsage.toString()
|
||||
}
|
||||
|
||||
// Parallelism
|
||||
mParallelismPref = findPreference<InputKdfNumberPreference>(getString(R.string.parallelism_key))?.apply {
|
||||
summary = mDatabase.parallelism.toString()
|
||||
}
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseMasterKeyPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey)
|
||||
|
||||
if (mDatabase.loaded) {
|
||||
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey)
|
||||
.show(fragmentManager, "passwordDialog")
|
||||
}
|
||||
false
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
|
||||
private val colorSelectedListener: ((Boolean, Int)-> Unit)? = { enable, color ->
|
||||
dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false)
|
||||
if (enable) {
|
||||
dbCustomColorPref?.color = color
|
||||
} else {
|
||||
dbCustomColorPref?.color = DialogColorPreference.DISABLE_COLOR
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
try {
|
||||
// To reassign color listener after orientation change
|
||||
val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
|
||||
chromaDialog?.onColorSelectedListener = colorSelectedListener
|
||||
} catch (e: Exception) {}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onProgressDialogThreadResult(actionTask: String,
|
||||
result: ActionRunnable.Result) {
|
||||
result.data?.let { data ->
|
||||
if (data.containsKey(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
&& data.containsKey(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)) {
|
||||
when (actionTask) {
|
||||
/*
|
||||
--------
|
||||
Main preferences
|
||||
--------
|
||||
*/
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_NAME_TASK -> {
|
||||
val oldName = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!!
|
||||
val newName = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!!
|
||||
val nameToShow =
|
||||
if (result.isSuccess) {
|
||||
newName
|
||||
} else {
|
||||
mDatabase.name = oldName
|
||||
oldName
|
||||
}
|
||||
dbNamePref?.summary = nameToShow
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK -> {
|
||||
val oldDescription = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!!
|
||||
val newDescription = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!!
|
||||
val descriptionToShow =
|
||||
if (result.isSuccess) {
|
||||
newDescription
|
||||
} else {
|
||||
mDatabase.description = oldDescription
|
||||
oldDescription
|
||||
}
|
||||
dbDescriptionPref?.summary = descriptionToShow
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK -> {
|
||||
val oldDefaultUsername = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!!
|
||||
val newDefaultUsername = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!!
|
||||
val defaultUsernameToShow =
|
||||
if (result.isSuccess) {
|
||||
newDefaultUsername
|
||||
} else {
|
||||
mDatabase.defaultUsername = oldDefaultUsername
|
||||
oldDefaultUsername
|
||||
}
|
||||
dbDefaultUsername?.summary = defaultUsernameToShow
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_COLOR_TASK -> {
|
||||
val oldColor = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!!
|
||||
val newColor = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!!
|
||||
|
||||
val defaultColorToShow =
|
||||
if (result.isSuccess) {
|
||||
newColor
|
||||
} else {
|
||||
mDatabase.customColor = oldColor
|
||||
oldColor
|
||||
}
|
||||
dbCustomColorPref?.summary = defaultColorToShow
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> {
|
||||
val oldCompression = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as PwCompressionAlgorithm
|
||||
val newCompression = data.getSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) as PwCompressionAlgorithm
|
||||
val algorithmToShow =
|
||||
if (result.isSuccess) {
|
||||
newCompression
|
||||
} else {
|
||||
mDatabase.compressionAlgorithm = oldCompression
|
||||
oldCompression
|
||||
}
|
||||
dbDataCompressionPref?.summary = algorithmToShow.getName(resources)
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK -> {
|
||||
val oldMaxHistoryItems = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
val newMaxHistoryItems = data.getInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
||||
val maxHistoryItemsToShow =
|
||||
if (result.isSuccess) {
|
||||
newMaxHistoryItems
|
||||
} else {
|
||||
mDatabase.historyMaxItems = oldMaxHistoryItems
|
||||
oldMaxHistoryItems
|
||||
}
|
||||
dbMaxHistoryItemsPref?.summary = maxHistoryItemsToShow.toString()
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK -> {
|
||||
val oldMaxHistorySize = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
val newMaxHistorySize = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
||||
val maxHistorySizeToShow =
|
||||
if (result.isSuccess) {
|
||||
newMaxHistorySize
|
||||
} else {
|
||||
mDatabase.historyMaxSize = oldMaxHistorySize
|
||||
oldMaxHistorySize
|
||||
}
|
||||
dbMaxHistorySizePref?.summary = maxHistorySizeToShow.toString()
|
||||
}
|
||||
|
||||
/*
|
||||
--------
|
||||
Security
|
||||
--------
|
||||
*/
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK -> {
|
||||
val oldEncryption = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as PwEncryptionAlgorithm
|
||||
val newEncryption = data.getSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) as PwEncryptionAlgorithm
|
||||
val algorithmToShow =
|
||||
if (result.isSuccess) {
|
||||
newEncryption
|
||||
} else {
|
||||
mDatabase.encryptionAlgorithm = oldEncryption
|
||||
oldEncryption
|
||||
}
|
||||
mEncryptionAlgorithmPref?.summary = algorithmToShow.getName(resources)
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK -> {
|
||||
val oldKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as KdfEngine
|
||||
val newKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) as KdfEngine
|
||||
val kdfEngineToShow =
|
||||
if (result.isSuccess) {
|
||||
newKeyDerivationEngine
|
||||
} else {
|
||||
mDatabase.kdfEngine = oldKeyDerivationEngine
|
||||
oldKeyDerivationEngine
|
||||
}
|
||||
mKeyDerivationPref?.summary = kdfEngineToShow.getName(resources)
|
||||
|
||||
mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString()
|
||||
// Disable memory and parallelism if not available
|
||||
mMemoryPref?.summary = kdfEngineToShow.defaultMemoryUsage.toString()
|
||||
mParallelismPref?.summary = kdfEngineToShow.defaultParallelism.toString()
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> {
|
||||
val oldIterations = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
val newIterations = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
||||
val roundsToShow =
|
||||
if (result.isSuccess) {
|
||||
newIterations
|
||||
} else {
|
||||
mDatabase.numberKeyEncryptionRounds = oldIterations
|
||||
oldIterations
|
||||
}
|
||||
mRoundPref?.summary = roundsToShow.toString()
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK -> {
|
||||
val oldMemoryUsage = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
val newMemoryUsage = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
||||
val memoryToShow =
|
||||
if (result.isSuccess) {
|
||||
newMemoryUsage
|
||||
} else {
|
||||
mDatabase.memoryUsage = oldMemoryUsage
|
||||
oldMemoryUsage
|
||||
}
|
||||
mMemoryPref?.summary = memoryToShow.toString()
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> {
|
||||
val oldParallelism = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
val newParallelism = data.getInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
||||
val parallelismToShow =
|
||||
if (result.isSuccess) {
|
||||
newParallelism
|
||||
} else {
|
||||
mDatabase.parallelism = oldParallelism
|
||||
oldParallelism
|
||||
}
|
||||
mParallelismPref?.summary = parallelismToShow.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference?) {
|
||||
|
||||
var otherDialogFragment = false
|
||||
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
preference?.let { preference ->
|
||||
var dialogFragment: DialogFragment? = null
|
||||
when {
|
||||
// Main Preferences
|
||||
preference.key == getString(R.string.database_name_key) -> {
|
||||
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.database_description_key) -> {
|
||||
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.database_default_username_key) -> {
|
||||
dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.database_custom_color_key) -> {
|
||||
dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
|
||||
onColorSelectedListener = colorSelectedListener
|
||||
}
|
||||
}
|
||||
preference.key == getString(R.string.database_data_compression_key) -> {
|
||||
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.max_history_items_key) -> {
|
||||
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.max_history_size_key) -> {
|
||||
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
|
||||
// Security
|
||||
preference.key == getString(R.string.encryption_algorithm_key) -> {
|
||||
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.key_derivation_function_key) -> {
|
||||
val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
// Add other prefs to manage
|
||||
keyDerivationDialogFragment.setRoundPreference(mRoundPref)
|
||||
keyDerivationDialogFragment.setMemoryPreference(mMemoryPref)
|
||||
keyDerivationDialogFragment.setParallelismPreference(mParallelismPref)
|
||||
dialogFragment = keyDerivationDialogFragment
|
||||
}
|
||||
preference.key == getString(R.string.transform_rounds_key) -> {
|
||||
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.memory_usage_key) -> {
|
||||
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.parallelism_key) -> {
|
||||
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
else -> otherDialogFragment = true
|
||||
}
|
||||
|
||||
if (dialogFragment != null && !mDatabaseReadOnly) {
|
||||
dialogFragment.setTargetFragment(this, 0)
|
||||
dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT)
|
||||
}
|
||||
// Could not be handled here. Try with the super method.
|
||||
else if (otherDialogFragment) {
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
context?.let { context ->
|
||||
mDatabaseAutoSaveEnabled = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
|
||||
inflater.inflate(R.menu.database, menu)
|
||||
if (mDatabaseReadOnly) {
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
|
||||
val settingActivity = activity as SettingsActivity?
|
||||
|
||||
when (item.itemId) {
|
||||
R.id.menu_lock -> {
|
||||
settingActivity?.lock()
|
||||
return true
|
||||
}
|
||||
R.id.menu_save_database -> {
|
||||
settingActivity?.mProgressDialogThread?.startDatabaseSave(!mDatabaseReadOnly)
|
||||
return true
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Check the time lock before launching settings
|
||||
settingActivity?.let {
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, mDatabaseReadOnly)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
|
||||
}
|
||||
}
|
||||
@@ -19,591 +19,37 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.autofill.AutofillManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.*
|
||||
import com.kunzisoft.androidclearchroma.ChromaUtil
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.*
|
||||
import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_ELEMENT_KEY
|
||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.OLD_ELEMENT_KEY
|
||||
import com.kunzisoft.keepass.settings.preference.*
|
||||
import com.kunzisoft.keepass.settings.preference.DialogColorPreference.Companion.DISABLE_COLOR
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class NestedSettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private var mDatabase: Database = Database.getInstance()
|
||||
private var mDatabaseReadOnly: Boolean = false
|
||||
private var mDatabaseAutoSaveEnabled: Boolean = true
|
||||
|
||||
private var mCount = 0
|
||||
|
||||
private var dbNamePref: InputTextPreference? = null
|
||||
private var dbDescriptionPref: InputTextPreference? = null
|
||||
private var dbDefaultUsername: InputTextPreference? = null
|
||||
private var dbCustomColorPref: DialogColorPreference? = null
|
||||
private var dbDataCompressionPref: Preference? = null
|
||||
private var recycleBinGroupPref: Preference? = null
|
||||
private var dbMaxHistoryItemsPref: InputNumberPreference? = null
|
||||
private var dbMaxHistorySizePref: InputNumberPreference? = null
|
||||
private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null
|
||||
private var mKeyDerivationPref: DialogListExplanationPreference? = null
|
||||
private var mRoundPref: InputKdfNumberPreference? = null
|
||||
private var mMemoryPref: InputKdfNumberPreference? = null
|
||||
private var mParallelismPref: InputKdfNumberPreference? = null
|
||||
abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
enum class Screen {
|
||||
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
activity?.let { activity ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key))
|
||||
if (autoFillEnablePreference != null) {
|
||||
val autofillManager = activity.getSystemService(AutofillManager::class.java)
|
||||
autoFillEnablePreference.isChecked = autofillManager != null
|
||||
&& autofillManager.hasEnabledAutofillServices()
|
||||
}
|
||||
}
|
||||
mDatabaseAutoSaveEnabled = PreferencesUtil.isAutoSaveDatabaseEnabled(activity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
||||
var key = 0
|
||||
if (arguments != null)
|
||||
key = arguments!!.getInt(TAG_KEY)
|
||||
|
||||
mDatabaseReadOnly = mDatabase.isReadOnly
|
||||
|| ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
when (Screen.values()[key]) {
|
||||
Screen.APPLICATION -> {
|
||||
onCreateApplicationPreferences(rootKey)
|
||||
}
|
||||
Screen.FORM_FILLING -> {
|
||||
onCreateFormFillingPreference(rootKey)
|
||||
}
|
||||
Screen.ADVANCED_UNLOCK -> {
|
||||
onCreateAdvancedUnlockPreferences(rootKey)
|
||||
}
|
||||
Screen.APPEARANCE -> {
|
||||
onCreateAppearancePreferences(rootKey)
|
||||
}
|
||||
Screen.DATABASE -> {
|
||||
onCreateDatabasePreference(rootKey)
|
||||
}
|
||||
Screen.DATABASE_SECURITY -> {
|
||||
onCreateDatabaseSecurityPreference(rootKey)
|
||||
}
|
||||
Screen.DATABASE_MASTER_KEY -> {
|
||||
onCreateDatabaseMasterKeyPreference(rootKey)
|
||||
}
|
||||
}
|
||||
onCreateScreenPreference(Screen.values()[key], savedInstanceState, rootKey)
|
||||
}
|
||||
|
||||
private fun onCreateApplicationPreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_application, rootKey)
|
||||
abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?)
|
||||
|
||||
activity?.let { activity ->
|
||||
allowCopyPassword()
|
||||
open fun onProgressDialogThreadResult(actionTask: String,
|
||||
result: ActionRunnable.Result) {}
|
||||
|
||||
findPreference<Preference>(getString(R.string.keyfile_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.recentfile_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateFormFillingPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_form_filling, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key))
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val autofillManager = activity.getSystemService(AutofillManager::class.java)
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices())
|
||||
autoFillEnablePreference?.isChecked = autofillManager.hasEnabledAutofillServices()
|
||||
autoFillEnablePreference?.onPreferenceClickListener = object : Preference.OnPreferenceClickListener {
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
override fun onPreferenceClick(preference: Preference): Boolean {
|
||||
if ((preference as SwitchPreference).isChecked) {
|
||||
try {
|
||||
startEnableService()
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
val error = getString(R.string.error_autofill_enable_service)
|
||||
preference.isChecked = false
|
||||
Log.d(javaClass.name, error, e)
|
||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
} else {
|
||||
disableService()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private fun disableService() {
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) {
|
||||
autofillManager.disableAutofillServices()
|
||||
} else {
|
||||
Log.d(javaClass.name, "Sample service already disabled.")
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Throws(ActivityNotFoundException::class)
|
||||
private fun startEnableService() {
|
||||
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
|
||||
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
|
||||
// TODO Autofill
|
||||
intent.data = Uri.parse("package:com.example.android.autofill.service")
|
||||
Log.d(javaClass.name, "enableService(): intent=$intent")
|
||||
startActivityForResult(intent, REQUEST_CODE_AUTOFILL)
|
||||
} else {
|
||||
Log.d(javaClass.name, "Sample service already enabled.")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
autoFillEnablePreference?.setOnPreferenceClickListener { preference ->
|
||||
(preference as SwitchPreference).isChecked = false
|
||||
val fragmentManager = fragmentManager!!
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.O)
|
||||
.show(fragmentManager, "unavailableFeatureDialog")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url)
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.magic_keyboard_key))?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
})
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener {
|
||||
startActivity(Intent(context, MagikIMESettings::class.java))
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url)
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.autofill_explanation_url)
|
||||
false
|
||||
}
|
||||
|
||||
// Present in two places
|
||||
allowCopyPassword()
|
||||
}
|
||||
|
||||
private fun onCreateAdvancedUnlockPreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key))
|
||||
// < M solve verifyError exception
|
||||
var biometricUnlockSupported = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val biometricCanAuthenticate = BiometricManager.from(activity).canAuthenticate()
|
||||
biometricUnlockSupported = biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
if (!biometricUnlockSupported) {
|
||||
// False if under Marshmallow
|
||||
biometricUnlockEnablePreference?.apply {
|
||||
isChecked = false
|
||||
setOnPreferenceClickListener { preference ->
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
(preference as SwitchPreference).isChecked = false
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||
.show(fragmentManager, "unavailableFeatureDialog")
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
|
||||
if (!biometricUnlockSupported) {
|
||||
deleteKeysFingerprints?.isEnabled = false
|
||||
} else {
|
||||
deleteKeysFingerprints?.setOnPreferenceClickListener {
|
||||
context?.let { context ->
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(resources.getString(R.string.biometric_delete_all_key_warning))
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(resources.getString(android.R.string.yes)
|
||||
) { _, _ ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric(
|
||||
activity,
|
||||
object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback {
|
||||
override fun onInvalidKeyException(e: Exception) {}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
Toast.makeText(context,
|
||||
getString(R.string.biometric_scanning_error, e.localizedMessage),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
CipherDatabaseAction.getInstance(context.applicationContext).deleteAll()
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.no))
|
||||
{ _, _ -> }.show()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(context!!, R.string.advanced_unlock_explanation_url)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateAppearancePreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_appearance, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
var styleEnabled = true
|
||||
val styleIdString = newValue as String
|
||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!))
|
||||
for (themeIdDisabled in BuildConfig.STYLES_DISABLED) {
|
||||
if (themeIdDisabled == styleIdString) {
|
||||
styleEnabled = false
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (styleEnabled) {
|
||||
Stylish.assignStyle(styleIdString)
|
||||
activity.recreate()
|
||||
}
|
||||
styleEnabled
|
||||
}
|
||||
|
||||
findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
var iconPackEnabled = true
|
||||
val iconPackId = newValue as String
|
||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!))
|
||||
for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) {
|
||||
if (iconPackIdDisabled == iconPackId) {
|
||||
iconPackEnabled = false
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (iconPackEnabled) {
|
||||
IconPackChooser.setSelectedIconPack(iconPackId)
|
||||
}
|
||||
iconPackEnabled
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener {
|
||||
// To allow only one toast
|
||||
if (mCount == 0) {
|
||||
val sharedPreferences = Education.getEducationSharedPreferences(context!!)
|
||||
val editor = sharedPreferences.edit()
|
||||
for (resourceId in Education.educationResourcesKeys) {
|
||||
editor.putBoolean(getString(resourceId), false)
|
||||
}
|
||||
editor.apply()
|
||||
Toast.makeText(context, R.string.reset_education_screens_text, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
mCount++
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabasePreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database, rootKey)
|
||||
|
||||
if (mDatabase.loaded) {
|
||||
|
||||
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
|
||||
|
||||
// Database name
|
||||
dbNamePref = findPreference(getString(R.string.database_name_key))
|
||||
if (mDatabase.allowName) {
|
||||
dbNamePref?.summary = mDatabase.name
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbNamePref)
|
||||
}
|
||||
|
||||
// Database description
|
||||
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
|
||||
if (mDatabase.allowDescription) {
|
||||
dbDescriptionPref?.summary = mDatabase.description
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
|
||||
}
|
||||
|
||||
// Database default username
|
||||
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
|
||||
if (mDatabase.allowDefaultUsername) {
|
||||
dbDefaultUsername?.summary = mDatabase.defaultUsername
|
||||
} else {
|
||||
dbDefaultUsername?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
|
||||
}
|
||||
|
||||
// Database custom color
|
||||
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
|
||||
if (mDatabase.allowCustomColor) {
|
||||
dbCustomColorPref?.apply {
|
||||
try {
|
||||
color = Color.parseColor(mDatabase.customColor)
|
||||
summary = mDatabase.customColor
|
||||
} catch (e: Exception) {
|
||||
color = DISABLE_COLOR
|
||||
summary = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dbCustomColorPref?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
|
||||
}
|
||||
|
||||
// Version
|
||||
findPreference<Preference>(getString(R.string.database_version_key))
|
||||
?.summary = mDatabase.version
|
||||
|
||||
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key))
|
||||
|
||||
// Database compression
|
||||
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
||||
if (mDatabase.allowDataCompression) {
|
||||
dbDataCompressionPref?.summary = (mDatabase.compressionAlgorithm
|
||||
?: PwCompressionAlgorithm.None).getName(resources)
|
||||
} else {
|
||||
dbCompressionPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
|
||||
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
|
||||
|
||||
// Recycle bin
|
||||
if (mDatabase.allowRecycleBin) {
|
||||
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
|
||||
recycleBinEnablePref?.apply {
|
||||
isChecked = mDatabase.isRecycleBinEnabled
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val recycleBinEnabled = newValue as Boolean
|
||||
mDatabase.isRecycleBinEnabled = recycleBinEnabled
|
||||
if (recycleBinEnabled) {
|
||||
mDatabase.ensureRecycleBinExists(resources)
|
||||
} else {
|
||||
mDatabase.removeRecycleBin()
|
||||
}
|
||||
refreshRecycleBinGroup()
|
||||
// Save the database if not in readonly mode
|
||||
(context as SettingsActivity?)?.
|
||||
mProgressDialogThread?.startDatabaseSave(mDatabaseAutoSaveEnabled)
|
||||
true
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// Recycle Bin group
|
||||
refreshRecycleBinGroup()
|
||||
} else {
|
||||
dbRecycleBinPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
// History
|
||||
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
|
||||
?.isVisible = mDatabase.manageHistory == true
|
||||
|
||||
// Max history items
|
||||
dbMaxHistoryItemsPref = findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))?.apply {
|
||||
summary = mDatabase.historyMaxItems.toString()
|
||||
}
|
||||
|
||||
// Max history size
|
||||
dbMaxHistorySizePref = findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))?.apply {
|
||||
summary = mDatabase.historyMaxSize.toString()
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshRecycleBinGroup() {
|
||||
recycleBinGroupPref?.apply {
|
||||
if (mDatabase.isRecycleBinEnabled) {
|
||||
summary = mDatabase.recycleBin?.title
|
||||
isEnabled = true
|
||||
} else {
|
||||
summary = null
|
||||
isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseSecurityPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database_security, rootKey)
|
||||
|
||||
if (mDatabase.loaded) {
|
||||
// Encryption Algorithm
|
||||
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
|
||||
summary = mDatabase.getEncryptionAlgorithmName(resources)
|
||||
}
|
||||
|
||||
// Key derivation function
|
||||
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
|
||||
summary = mDatabase.getKeyDerivationName(resources)
|
||||
}
|
||||
|
||||
// Round encryption
|
||||
mRoundPref = findPreference<InputKdfNumberPreference>(getString(R.string.transform_rounds_key))?.apply {
|
||||
summary = mDatabase.numberKeyEncryptionRounds.toString()
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
mMemoryPref = findPreference<InputKdfNumberPreference>(getString(R.string.memory_usage_key))?.apply {
|
||||
summary = mDatabase.memoryUsage.toString()
|
||||
}
|
||||
|
||||
// Parallelism
|
||||
mParallelismPref = findPreference<InputKdfNumberPreference>(getString(R.string.parallelism_key))?.apply {
|
||||
summary = mDatabase.parallelism.toString()
|
||||
}
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseMasterKeyPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey)
|
||||
|
||||
if (mDatabase.loaded) {
|
||||
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey)
|
||||
.show(fragmentManager, "passwordDialog")
|
||||
}
|
||||
false
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
|
||||
private fun allowCopyPassword() {
|
||||
val copyPasswordPreference: SwitchPreference? = findPreference(getString(R.string.allow_copy_password_key))
|
||||
copyPasswordPreference?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue as Boolean && context != null) {
|
||||
val message = getString(R.string.allow_copy_password_warning) +
|
||||
"\n\n" +
|
||||
getString(R.string.clipboard_warning)
|
||||
AlertDialog
|
||||
.Builder(context!!)
|
||||
.setMessage(message)
|
||||
.create()
|
||||
.apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable))
|
||||
{ dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable))
|
||||
{ dialog, _ ->
|
||||
copyPasswordPreference.isChecked = false
|
||||
dialog.dismiss()
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun preferenceInDevelopment(preferenceInDev: Preference) {
|
||||
protected fun preferenceInDevelopment(preferenceInDev: Preference) {
|
||||
preferenceInDev.setOnPreferenceClickListener { preference ->
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
try { // don't check if we can
|
||||
@@ -616,295 +62,22 @@ class NestedSettingsFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
activity?.let { activity ->
|
||||
if (mCount == 10) {
|
||||
Education.getEducationSharedPreferences(activity).edit()
|
||||
.putBoolean(getString(R.string.education_screen_reclicked_key), true).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val colorSelectedListener: ((Boolean, Int)-> Unit)? = { enable, color ->
|
||||
dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false)
|
||||
if (enable) {
|
||||
dbCustomColorPref?.color = color
|
||||
} else {
|
||||
dbCustomColorPref?.color = DISABLE_COLOR
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
try {
|
||||
// To reassign color listener after orientation change
|
||||
val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
|
||||
chromaDialog?.onColorSelectedListener = colorSelectedListener
|
||||
} catch (e: Exception) {}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
fun onProgressDialogThreadResult(actionTask: String,
|
||||
result: ActionRunnable.Result) {
|
||||
result.data?.let { data ->
|
||||
if (data.containsKey(OLD_ELEMENT_KEY)
|
||||
&& data.containsKey(NEW_ELEMENT_KEY)) {
|
||||
when (actionTask) {
|
||||
/*
|
||||
--------
|
||||
Main preferences
|
||||
--------
|
||||
*/
|
||||
ACTION_DATABASE_UPDATE_NAME_TASK -> {
|
||||
val oldName = data.getString(OLD_ELEMENT_KEY)!!
|
||||
val newName = data.getString(NEW_ELEMENT_KEY)!!
|
||||
val nameToShow =
|
||||
if (result.isSuccess) {
|
||||
newName
|
||||
} else {
|
||||
mDatabase.name = oldName
|
||||
oldName
|
||||
}
|
||||
dbNamePref?.summary = nameToShow
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK -> {
|
||||
val oldDescription = data.getString(OLD_ELEMENT_KEY)!!
|
||||
val newDescription = data.getString(NEW_ELEMENT_KEY)!!
|
||||
val descriptionToShow =
|
||||
if (result.isSuccess) {
|
||||
newDescription
|
||||
} else {
|
||||
mDatabase.description = oldDescription
|
||||
oldDescription
|
||||
}
|
||||
dbDescriptionPref?.summary = descriptionToShow
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK -> {
|
||||
val oldDefaultUsername = data.getString(OLD_ELEMENT_KEY)!!
|
||||
val newDefaultUsername = data.getString(NEW_ELEMENT_KEY)!!
|
||||
val defaultUsernameToShow =
|
||||
if (result.isSuccess) {
|
||||
newDefaultUsername
|
||||
} else {
|
||||
mDatabase.defaultUsername = oldDefaultUsername
|
||||
oldDefaultUsername
|
||||
}
|
||||
dbDefaultUsername?.summary = defaultUsernameToShow
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_COLOR_TASK -> {
|
||||
val oldColor = data.getString(OLD_ELEMENT_KEY)!!
|
||||
val newColor = data.getString(NEW_ELEMENT_KEY)!!
|
||||
|
||||
val defaultColorToShow =
|
||||
if (result.isSuccess) {
|
||||
newColor
|
||||
} else {
|
||||
mDatabase.customColor = oldColor
|
||||
oldColor
|
||||
}
|
||||
dbCustomColorPref?.summary = defaultColorToShow
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> {
|
||||
val oldCompression = data.getSerializable(OLD_ELEMENT_KEY) as PwCompressionAlgorithm
|
||||
val newCompression = data.getSerializable(NEW_ELEMENT_KEY) as PwCompressionAlgorithm
|
||||
val algorithmToShow =
|
||||
if (result.isSuccess) {
|
||||
newCompression
|
||||
} else {
|
||||
mDatabase.compressionAlgorithm = oldCompression
|
||||
oldCompression
|
||||
}
|
||||
dbDataCompressionPref?.summary = algorithmToShow.getName(resources)
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK -> {
|
||||
val oldMaxHistoryItems = data.getInt(OLD_ELEMENT_KEY)
|
||||
val newMaxHistoryItems = data.getInt(NEW_ELEMENT_KEY)
|
||||
val maxHistoryItemsToShow =
|
||||
if (result.isSuccess) {
|
||||
newMaxHistoryItems
|
||||
} else {
|
||||
mDatabase.historyMaxItems = oldMaxHistoryItems
|
||||
oldMaxHistoryItems
|
||||
}
|
||||
dbMaxHistoryItemsPref?.summary = maxHistoryItemsToShow.toString()
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK -> {
|
||||
val oldMaxHistorySize = data.getLong(OLD_ELEMENT_KEY)
|
||||
val newMaxHistorySize = data.getLong(NEW_ELEMENT_KEY)
|
||||
val maxHistorySizeToShow =
|
||||
if (result.isSuccess) {
|
||||
newMaxHistorySize
|
||||
} else {
|
||||
mDatabase.historyMaxSize = oldMaxHistorySize
|
||||
oldMaxHistorySize
|
||||
}
|
||||
dbMaxHistorySizePref?.summary = maxHistorySizeToShow.toString()
|
||||
}
|
||||
|
||||
/*
|
||||
--------
|
||||
Security
|
||||
--------
|
||||
*/
|
||||
ACTION_DATABASE_UPDATE_ENCRYPTION_TASK -> {
|
||||
val oldEncryption = data.getSerializable(OLD_ELEMENT_KEY) as PwEncryptionAlgorithm
|
||||
val newEncryption = data.getSerializable(NEW_ELEMENT_KEY) as PwEncryptionAlgorithm
|
||||
val algorithmToShow =
|
||||
if (result.isSuccess) {
|
||||
newEncryption
|
||||
} else {
|
||||
mDatabase.encryptionAlgorithm = oldEncryption
|
||||
oldEncryption
|
||||
}
|
||||
mEncryptionAlgorithmPref?.summary = algorithmToShow.getName(resources)
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK -> {
|
||||
val oldKeyDerivationEngine = data.getSerializable(OLD_ELEMENT_KEY) as KdfEngine
|
||||
val newKeyDerivationEngine = data.getSerializable(NEW_ELEMENT_KEY) as KdfEngine
|
||||
val kdfEngineToShow =
|
||||
if (result.isSuccess) {
|
||||
newKeyDerivationEngine
|
||||
} else {
|
||||
mDatabase.kdfEngine = oldKeyDerivationEngine
|
||||
oldKeyDerivationEngine
|
||||
}
|
||||
mKeyDerivationPref?.summary = kdfEngineToShow.getName(resources)
|
||||
|
||||
mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString()
|
||||
// Disable memory and parallelism if not available
|
||||
mMemoryPref?.summary = kdfEngineToShow.defaultMemoryUsage.toString()
|
||||
mParallelismPref?.summary = kdfEngineToShow.defaultParallelism.toString()
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> {
|
||||
val oldIterations = data.getLong(OLD_ELEMENT_KEY)
|
||||
val newIterations = data.getLong(NEW_ELEMENT_KEY)
|
||||
val roundsToShow =
|
||||
if (result.isSuccess) {
|
||||
newIterations
|
||||
} else {
|
||||
mDatabase.numberKeyEncryptionRounds = oldIterations
|
||||
oldIterations
|
||||
}
|
||||
mRoundPref?.summary = roundsToShow.toString()
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK -> {
|
||||
val oldMemoryUsage = data.getLong(OLD_ELEMENT_KEY)
|
||||
val newMemoryUsage = data.getLong(NEW_ELEMENT_KEY)
|
||||
val memoryToShow =
|
||||
if (result.isSuccess) {
|
||||
newMemoryUsage
|
||||
} else {
|
||||
mDatabase.memoryUsage = oldMemoryUsage
|
||||
oldMemoryUsage
|
||||
}
|
||||
mMemoryPref?.summary = memoryToShow.toString()
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> {
|
||||
val oldParallelism = data.getInt(OLD_ELEMENT_KEY)
|
||||
val newParallelism = data.getInt(NEW_ELEMENT_KEY)
|
||||
val parallelismToShow =
|
||||
if (result.isSuccess) {
|
||||
newParallelism
|
||||
} else {
|
||||
mDatabase.parallelism = oldParallelism
|
||||
oldParallelism
|
||||
}
|
||||
mParallelismPref?.summary = parallelismToShow.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference?) {
|
||||
|
||||
var otherDialogFragment = false
|
||||
|
||||
fragmentManager?.let { fragmentManager ->
|
||||
preference?.let { preference ->
|
||||
var dialogFragment: DialogFragment? = null
|
||||
when {
|
||||
// Main Preferences
|
||||
preference.key == getString(R.string.database_name_key) -> {
|
||||
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.database_description_key) -> {
|
||||
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.database_default_username_key) -> {
|
||||
dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.database_custom_color_key) -> {
|
||||
dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
|
||||
onColorSelectedListener = colorSelectedListener
|
||||
}
|
||||
}
|
||||
preference.key == getString(R.string.database_data_compression_key) -> {
|
||||
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.max_history_items_key) -> {
|
||||
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.max_history_size_key) -> {
|
||||
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
|
||||
// Security
|
||||
preference.key == getString(R.string.encryption_algorithm_key) -> {
|
||||
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.key_derivation_function_key) -> {
|
||||
val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
// Add other prefs to manage
|
||||
keyDerivationDialogFragment.setRoundPreference(mRoundPref)
|
||||
keyDerivationDialogFragment.setMemoryPreference(mMemoryPref)
|
||||
keyDerivationDialogFragment.setParallelismPreference(mParallelismPref)
|
||||
dialogFragment = keyDerivationDialogFragment
|
||||
}
|
||||
preference.key == getString(R.string.transform_rounds_key) -> {
|
||||
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.memory_usage_key) -> {
|
||||
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
preference.key == getString(R.string.parallelism_key) -> {
|
||||
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
else -> otherDialogFragment = true
|
||||
}
|
||||
|
||||
if (dialogFragment != null && !mDatabaseReadOnly) {
|
||||
dialogFragment.setTargetFragment(this, 0)
|
||||
dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT)
|
||||
}
|
||||
// Could not be handled here. Try with the super method.
|
||||
else if (otherDialogFragment) {
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, mDatabaseReadOnly)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG_KEY = "NESTED_KEY"
|
||||
|
||||
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
|
||||
|
||||
private const val REQUEST_CODE_AUTOFILL = 5201
|
||||
|
||||
@JvmOverloads
|
||||
fun newInstance(key: Screen, databaseReadOnly: Boolean = ReadOnlyHelper.READ_ONLY_DEFAULT)
|
||||
: NestedSettingsFragment {
|
||||
val fragment = NestedSettingsFragment()
|
||||
val fragment: NestedSettingsFragment = when (key) {
|
||||
Screen.APPLICATION,
|
||||
Screen.FORM_FILLING,
|
||||
Screen.ADVANCED_UNLOCK,
|
||||
Screen.APPEARANCE -> NestedAppSettingsFragment()
|
||||
Screen.DATABASE,
|
||||
Screen.DATABASE_SECURITY,
|
||||
Screen.DATABASE_MASTER_KEY -> NestedDatabaseSettingsFragment()
|
||||
}
|
||||
// supply arguments to bundle.
|
||||
val args = Bundle()
|
||||
args.putInt(TAG_KEY, key.ordinal)
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
@@ -27,10 +29,8 @@ import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.AboutActivity
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper.READ_ONLY_DEFAULT
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.settings.SettingsActivity
|
||||
|
||||
|
||||
object MenuUtil {
|
||||
|
||||
fun contributionMenuInflater(inflater: MenuInflater, menu: Menu) {
|
||||
@@ -43,33 +43,33 @@ object MenuUtil {
|
||||
inflater.inflate(R.menu.default_menu, menu)
|
||||
}
|
||||
|
||||
fun onContributionItemSelected(activity: StylishActivity) {
|
||||
UriUtil.gotoUrl(activity, R.string.contribution_url)
|
||||
fun onContributionItemSelected(context: Context) {
|
||||
UriUtil.gotoUrl(context, R.string.contribution_url)
|
||||
}
|
||||
|
||||
/*
|
||||
* @param checkLock Check the time lock before launch settings in LockingActivity
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun onDefaultMenuOptionsItemSelected(activity: StylishActivity, item: MenuItem, readOnly: Boolean = READ_ONLY_DEFAULT, timeoutEnable: Boolean = false): Boolean {
|
||||
fun onDefaultMenuOptionsItemSelected(activity: Activity,
|
||||
item: MenuItem,
|
||||
readOnly: Boolean = READ_ONLY_DEFAULT,
|
||||
timeoutEnable: Boolean = false): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_contribute -> {
|
||||
onContributionItemSelected(activity)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_app_settings -> {
|
||||
// To avoid flickering when launch settings in a LockingActivity
|
||||
SettingsActivity.launch(activity, readOnly, timeoutEnable)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_about -> {
|
||||
val intent = Intent(activity, AboutActivity::class.java)
|
||||
activity.startActivity(intent)
|
||||
return true
|
||||
}
|
||||
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user