diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt index 27a546d2e..94b6a6edc 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt @@ -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) { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt new file mode 100644 index 000000000..a2c7d3928 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt @@ -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(getString(R.string.keyfile_key))?.setOnPreferenceChangeListener { _, newValue -> + if (!(newValue as Boolean)) { + FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles() + } + true + } + + findPreference(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(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener { + UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url) + false + } + + findPreference(getString(R.string.magic_keyboard_key))?.setOnPreferenceClickListener { + startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }) + false + } + + findPreference(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener { + startActivity(Intent(context, MagikIMESettings::class.java)) + false + } + + findPreference(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener { + UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url) + false + } + + findPreference(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(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(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(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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt new file mode 100644 index 000000000..16c63b342 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt @@ -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(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(getString(R.string.database_category_history_key)) + ?.isVisible = mDatabase.manageHistory == true + + // Max history items + dbMaxHistoryItemsPref = findPreference(getString(R.string.max_history_items_key))?.apply { + summary = mDatabase.historyMaxItems.toString() + } + + // Max history size + dbMaxHistorySizePref = findPreference(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(getString(R.string.encryption_algorithm_key))?.apply { + summary = mDatabase.getEncryptionAlgorithmName(resources) + } + + // Key derivation function + mKeyDerivationPref = findPreference(getString(R.string.key_derivation_function_key))?.apply { + summary = mDatabase.getKeyDerivationName(resources) + } + + // Round encryption + mRoundPref = findPreference(getString(R.string.transform_rounds_key))?.apply { + summary = mDatabase.numberKeyEncryptionRounds.toString() + } + + // Memory Usage + mMemoryPref = findPreference(getString(R.string.memory_usage_key))?.apply { + summary = mDatabase.memoryUsage.toString() + } + + // Parallelism + mParallelismPref = findPreference(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(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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt index 02b6bc244..2ebd4c605 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt @@ -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(getString(R.string.keyfile_key))?.setOnPreferenceChangeListener { _, newValue -> - if (!(newValue as Boolean)) { - FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles() - } - true - } - - findPreference(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(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener { - UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url) - false - } - - findPreference(getString(R.string.magic_keyboard_key))?.setOnPreferenceClickListener { - startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - }) - false - } - - findPreference(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener { - startActivity(Intent(context, MagikIMESettings::class.java)) - false - } - - findPreference(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener { - UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url) - false - } - - findPreference(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(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(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(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(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(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(getString(R.string.database_category_history_key)) - ?.isVisible = mDatabase.manageHistory == true - - // Max history items - dbMaxHistoryItemsPref = findPreference(getString(R.string.max_history_items_key))?.apply { - summary = mDatabase.historyMaxItems.toString() - } - - // Max history size - dbMaxHistorySizePref = findPreference(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(getString(R.string.encryption_algorithm_key))?.apply { - summary = mDatabase.getEncryptionAlgorithmName(resources) - } - - // Key derivation function - mKeyDerivationPref = findPreference(getString(R.string.key_derivation_function_key))?.apply { - summary = mDatabase.getKeyDerivationName(resources) - } - - // Round encryption - mRoundPref = findPreference(getString(R.string.transform_rounds_key))?.apply { - summary = mDatabase.numberKeyEncryptionRounds.toString() - } - - // Memory Usage - mMemoryPref = findPreference(getString(R.string.memory_usage_key))?.apply { - summary = mDatabase.memoryUsage.toString() - } - - // Parallelism - mParallelismPref = findPreference(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(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) diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt index 36282be95..efd5d24a0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt @@ -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 } }