Merge branch 'master' into master

This commit is contained in:
Uli
2021-04-18 14:37:43 +02:00
committed by GitHub
81 changed files with 1257 additions and 907 deletions

View File

@@ -1,3 +1,11 @@
KeePassDX(2.9.17)
* Import / Export app properties #839
* Force twofish padding compatibility #955
* Better timeout preference #579
KeePassDX(2.9.16)
* Fix small bugs #948
KeePassDX(2.9.15) KeePassDX(2.9.15)
* Fix themes #935 #926 * Fix themes #935 #926
* Decrease default clipboard time #934 * Decrease default clipboard time #934

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 30 targetSdkVersion 30
versionCode = 68 versionCode = 71
versionName = "2.9.15" versionName = "2.9.17"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -39,6 +39,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
@@ -94,6 +95,8 @@ class EntryActivity : LockingActivity() {
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var mFirstLaunchOfActivity: Boolean = false private var mFirstLaunchOfActivity: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
private var iconColor: Int = 0 private var iconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -141,6 +144,9 @@ class EntryActivity : LockingActivity() {
clipboardHelper = ClipboardHelper(this) clipboardHelper = ClipboardHelper(this)
mFirstLaunchOfActivity = savedInstanceState?.getBoolean(KEY_FIRST_LAUNCH_ACTIVITY) ?: true mFirstLaunchOfActivity = savedInstanceState?.getBoolean(KEY_FIRST_LAUNCH_ACTIVITY) ?: true
// Init SAF manager
mExternalFileHelper = ExternalFileHelper(this)
// Init attachment service binder manager // Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this) mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
@@ -347,7 +353,7 @@ class EntryActivity : LockingActivity() {
// Manage attachments // Manage attachments
entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem -> entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
createDocument(this, attachmentItem.name)?.let { requestCode -> mExternalFileHelper?.createDocument(attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem mAttachmentsToDownload[requestCode] = attachmentItem
} }
} }
@@ -383,7 +389,7 @@ class EntryActivity : LockingActivity() {
} }
} }
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri -> mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) { if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload -> mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager mAttachmentFileBinderManager

View File

@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
import com.kunzisoft.keepass.activities.fragments.EntryEditFragment import com.kunzisoft.keepass.activities.fragments.EntryEditFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
@@ -105,7 +105,7 @@ class EntryEditActivity : LockingActivity(),
private var lockView: View? = null private var lockView: View? = null
// To manage attachments // To manage attachments
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAllowMultipleAttachments: Boolean = false private var mAllowMultipleAttachments: Boolean = false
private var mTempAttachments = ArrayList<EntryAttachmentState>() private var mTempAttachments = ArrayList<EntryAttachmentState>()
@@ -242,7 +242,7 @@ class EntryEditActivity : LockingActivity(),
} }
// To retrieve attachment // To retrieve attachment
mSelectFileHelper = SelectFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mAttachmentFileBinderManager = AttachmentFileBinderManager(this) mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
// Save button // Save button
@@ -488,8 +488,8 @@ class EntryEditActivity : LockingActivity(),
/** /**
* Add a new attachment * Add a new attachment
*/ */
private fun addNewAttachment(item: MenuItem) { private fun addNewAttachment() {
mSelectFileHelper?.selectFileOnClickViewListener?.onMenuItemClick(item) mExternalFileHelper?.openDocument()
} }
override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) { override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) {
@@ -535,7 +535,7 @@ class EntryEditActivity : LockingActivity(),
entryEditFragment?.icon = icon entryEditFragment?.icon = icon
} }
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
uri?.let { attachmentToUploadUri -> uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile -> UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
documentFile.name?.let { fileName -> documentFile.name?.let { fileName ->
@@ -691,7 +691,7 @@ class EntryEditActivity : LockingActivity(),
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation( && entryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView, attachmentView,
{ {
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(attachmentView) mExternalFileHelper?.openDocument()
}, },
{ {
performedNextEducation(entryEditActivityEducation) performedNextEducation(entryEditActivityEducation)
@@ -723,7 +723,7 @@ class EntryEditActivity : LockingActivity(),
return true return true
} }
R.id.menu_add_attachment -> { R.id.menu_add_attachment -> {
addNewAttachment(item) addNewAttachment()
return true return true
} }
R.id.menu_add_otp -> { R.id.menu_add_otp -> {

View File

@@ -42,8 +42,9 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
@@ -82,7 +83,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mDatabaseFileUri: Uri? = null private var mDatabaseFileUri: Uri? = null
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
@@ -103,14 +104,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
createDatabaseButtonView?.setOnClickListener { createNewFile() } createDatabaseButtonView?.setOnClickListener { createNewFile() }
// Open database button // Open database button
mSelectFileHelper = SelectFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_keyfile_button) openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.apply { openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper)
mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
// History list // History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list) val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
@@ -171,8 +167,6 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
databaseFiles.databaseFileToActivate?.let { databaseFileToAdd -> databaseFiles.databaseFileToActivate?.let { databaseFileToAdd ->
mAdapterDatabaseHistory?.addDatabaseFileHistory(databaseFileToAdd) mAdapterDatabaseHistory?.addDatabaseFileHistory(databaseFileToAdd)
} }
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
} }
DatabaseFilesViewModel.DatabaseFileAction.UPDATE -> { DatabaseFilesViewModel.DatabaseFileAction.UPDATE -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToUpdate -> databaseFiles.databaseFileToActivate?.let { databaseFileToUpdate ->
@@ -185,10 +179,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
} }
} }
} }
databaseFilesViewModel.consumeAction()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to observe database action", e) Log.e(TAG, "Unable to observe database action", e)
} }
databaseFilesViewModel.consumeAction()
} }
// Observe default database // Observe default database
@@ -206,6 +200,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential() val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri) databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
} }
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
} }
ACTION_DATABASE_LOAD_TASK -> { ACTION_DATABASE_LOAD_TASK -> {
val database = Database.getInstance() val database = Database.getInstance()
@@ -234,7 +230,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
* Create a new file by calling the content provider * Create a new file by calling the content provider
*/ */
private fun createNewFile() { private fun createNewFile() {
createDocument(this, getString(R.string.database_file_name_default) + mExternalFileHelper?.createDocument( getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass") getString(R.string.database_file_extension_default), "application/x-keepass")
} }
@@ -286,7 +282,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Show open and create button or special mode // Show open and create button or special mode
when (mSpecialMode) { when (mSpecialMode) {
SpecialMode.DEFAULT -> { SpecialMode.DEFAULT -> {
if (allowCreateDocumentByStorageAccessFramework(packageManager)) { if (ExternalFileHelper.allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent. // There is an activity which can handle this intent.
createDatabaseButtonView?.visibility = View.VISIBLE createDatabaseButtonView?.visibility = View.VISIBLE
} else{ } else{
@@ -359,14 +355,14 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
} }
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
if (uri != null) { if (uri != null) {
launchPasswordActivityWithPath(uri) launchPasswordActivityWithPath(uri)
} }
} }
// Retrieve the created URI from the file manager // Retrieve the created URI from the file manager
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri -> mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) { if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true) AssignMasterKeyDialogFragment.getInstance(true)
@@ -412,9 +408,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
openDatabaseButtonView != null openDatabaseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation( && fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
openDatabaseButtonView!!, openDatabaseButtonView!!,
{tapTargetView -> { tapTargetView ->
tapTargetView?.let { tapTargetView?.let {
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it) mExternalFileHelper?.openDocument()
} }
}, },
{} {}

View File

@@ -34,7 +34,8 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.IconPickerFragment import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
@@ -66,7 +67,7 @@ class IconPickerActivity : LockingActivity() {
private var mDatabase: Database? = null private var mDatabase: Database? = null
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -84,15 +85,11 @@ class IconPickerActivity : LockingActivity() {
coordinatorLayout = findViewById(R.id.icon_picker_coordinator) coordinatorLayout = findViewById(R.id.icon_picker_coordinator)
mExternalFileHelper = ExternalFileHelper(this)
uploadButton = findViewById(R.id.icon_picker_upload) uploadButton = findViewById(R.id.icon_picker_upload)
if (mDatabase?.allowCustomIcons == true) { if (mDatabase?.allowCustomIcons == true) {
uploadButton.setOnClickListener { uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
}
uploadButton.setOnLongClickListener {
mSelectFileHelper?.selectFileOnClickViewListener?.onLongClick(it)
true
}
} else { } else {
uploadButton.visibility = View.GONE uploadButton.visibility = View.GONE
} }
@@ -124,8 +121,6 @@ class IconPickerActivity : LockingActivity() {
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this) findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this)
mSelectFileHelper = SelectFileHelper(this)
iconPickerViewModel.standardIconPicked.observe(this) { iconStandard -> iconPickerViewModel.standardIconPicked.observe(this) { iconStandard ->
mIconImage.standard = iconStandard mIconImage.standard = iconStandard
// Remove the custom icon if a standard one is selected // Remove the custom icon if a standard one is selected
@@ -281,7 +276,7 @@ class IconPickerActivity : LockingActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
addCustomIcon(uri) addCustomIcon(uri)
} }
} }

View File

@@ -42,10 +42,7 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.*
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -95,7 +92,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private var mDatabaseKeyFileUri: Uri? = null private var mDatabaseKeyFileUri: Uri? = null
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mPermissionAsked = false private var mPermissionAsked = false
private var readOnly: Boolean = false private var readOnly: Boolean = false
@@ -138,13 +135,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this) mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mSelectFileHelper = SelectFileHelper(this@PasswordActivity) mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
keyFileSelectionView?.apply { keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
passwordView?.setOnEditorActionListener(onEditorActionListener) passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher { passwordView?.addTextChangedListener(object : TextWatcher {
@@ -702,8 +694,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} }
var keyFileResult = false var keyFileResult = false
mSelectFileHelper?.let { mExternalFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
mDatabaseKeyFileUri = uri mDatabaseKeyFileUri = uri

View File

@@ -30,13 +30,13 @@ import android.text.SpannableStringBuilder
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
@@ -60,7 +60,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var mListener: AssignPasswordDialogListener? = null private var mListener: AssignPasswordDialogListener? = null
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
private var mNoKeyConfirmationDialog: AlertDialog? = null private var mNoKeyConfirmationDialog: AlertDialog? = null
@@ -133,11 +133,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox) keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection) keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
mSelectFileHelper = SelectFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
keyFileSelectionView?.apply { keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
setOnClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
setOnLongClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
}
val dialog = builder.create() val dialog = builder.create()
@@ -289,7 +286,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile -> UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null keyFileSelectionView?.error = null

View File

@@ -0,0 +1,254 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class ExternalFileHelper {
private var activity: FragmentActivity? = null
private var fragment: Fragment? = null
constructor(context: FragmentActivity) {
this.activity = context
this.fragment = null
}
constructor(context: Fragment) {
this.activity = context.activity
this.fragment = context
}
fun openDocument(getContent: Boolean = false,
typeString: String = "*/*") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
if (getContent) {
openActivityWithActionGetContent(typeString)
} else {
openActivityWithActionOpenDocument(typeString)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to open document", e)
showFileManagerDialogFragment()
}
} else {
showFileManagerDialogFragment()
}
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionOpenDocument(typeString: String) {
val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
else
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionGetContent(typeString: String) {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
else
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param onFileSelected Callback retrieve from data
* @return true if requestCode was captured, false elsewhere
*/
fun onOpenDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
onFileSelected: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
FILE_BROWSE -> {
if (resultCode == RESULT_OK) {
val filename = data?.dataString
var keyUri: Uri? = null
if (filename != null) {
keyUri = UriUtil.parse(filename)
}
onFileSelected?.invoke(keyUri)
}
return true
}
GET_CONTENT, OPEN_DOC -> {
if (resultCode == RESULT_OK) {
if (data != null) {
val uri = data.data
if (uri != null) {
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
}
} catch (e: Exception) {
// nop
}
onFileSelected?.invoke(uri)
}
}
}
return true
}
}
return false
}
/**
* Show Browser dialog to select file picker app
*/
private fun showFileManagerDialogFragment() {
try {
if (fragment != null) {
fragment?.parentFragmentManager
} else {
activity?.supportFragmentManager
}?.let { fragmentManager ->
FileManagerDialogFragment().show(fragmentManager, "browserDialog")
}
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
fun createDocument(titleString: String,
typeString: String = "application/octet-stream"): Int? {
val idCode = getUnusedCreateFileRequestCode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
putExtra(Intent.EXTRA_TITLE, titleString)
}
if (fragment != null)
fragment?.startActivityForResult(intent, idCode)
else
activity?.startActivityForResult(intent, idCode)
return idCode
} catch (e: Exception) {
Log.e(TAG, "Unable to create document", e)
showFileManagerDialogFragment()
}
} else {
showFileManagerDialogFragment()
}
return null
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param onFileCreated Callback retrieve from data
* @return true if requestCode was captured, false elsewhere
*/
fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
onFileCreated: (fileCreated: Uri?)->Unit) {
// Retrieve the created URI from the file manager
if (fileRequestCodes.contains(requestCode) && resultCode == RESULT_OK) {
onFileCreated.invoke(data?.data)
fileRequestCodes.remove(requestCode)
}
}
companion object {
private const val TAG = "OpenFileHelper"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853
private var fileRequestCodes = ArrayList<Int>()
private fun getUnusedCreateFileRequestCode(): Int {
val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++
fileRequestCodes.add(newCreateFileRequestCode)
return newCreateFileRequestCode
}
@SuppressLint("InlinedApi")
fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager,
typeString: String = "application/octet-stream"): Boolean {
return when {
// To check if a custom file manager can manage the ACTION_CREATE_DOCUMENT
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
packageManager.queryIntentActivities(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
}, PackageManager.MATCH_DEFAULT_ONLY).isNotEmpty()
}
else -> true
}
}
}
}
fun View.setOpenDocumentClickListener(externalFileHelper: ExternalFileHelper?) {
externalFileHelper?.let { fileHelper ->
setOnClickListener {
fileHelper.openDocument()
}
setOnLongClickListener {
fileHelper.openDocument(true)
true
}
} ?: kotlin.run {
setOnClickListener(null)
setOnLongClickListener(null)
}
}

View File

@@ -1,244 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class SelectFileHelper {
private var activity: Activity? = null
private var fragment: Fragment? = null
val selectFileOnClickViewListener: SelectFileOnClickViewListener
get() = SelectFileOnClickViewListener()
constructor(context: Activity) {
this.activity = context
this.fragment = null
}
constructor(context: Fragment) {
this.activity = context.activity
this.fragment = context
}
inner class SelectFileOnClickViewListener :
View.OnClickListener,
View.OnLongClickListener,
MenuItem.OnMenuItemClickListener {
private fun onAbstractClick(longClick: Boolean = false) {
try {
if (longClick) {
try {
openActivityWithActionGetContent()
} catch (e: Exception) {
openActivityWithActionOpenDocument()
}
} else {
try {
openActivityWithActionOpenDocument()
} catch (e: Exception) {
openActivityWithActionGetContent()
}
}
} catch (e: Exception) {
Log.e(TAG, "Enable to start the file picker activity", e)
// Open browser dialog
if (lookForOpenIntentsFilePicker())
showBrowserDialog()
}
}
override fun onClick(v: View) {
onAbstractClick()
}
override fun onLongClick(v: View?): Boolean {
onAbstractClick(true)
return true
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
onAbstractClick()
return true
}
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionOpenDocument() {
val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
else
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
else
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
private fun lookForOpenIntentsFilePicker(): Boolean {
var showBrowser = false
try {
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
if (fragment != null)
fragment?.startActivityForResult(intent, FILE_BROWSE)
else
activity?.startActivityForResult(intent, FILE_BROWSE)
} else {
showBrowser = true
}
} catch (e: Exception) {
Log.w(TAG, "Enable to start OPEN_INTENTS_FILE_BROWSE", e)
showBrowser = true
}
return showBrowser
}
/**
* Indicates whether the specified action can be used as an intent. This
* method queries the package manager for installed packages that can
* respond to an intent with the specified action. If no suitable package is
* found, this method returns false.
*
* @param context The application's environment.
* @param action The Intent action to check for availability.
*
* @return True if an Intent with the specified action can be sent and
* responded to, false otherwise.
*/
private fun isIntentAvailable(context: Context, action: String): Boolean {
val packageManager = context.packageManager
val intent = Intent(action)
val list = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY)
return list.size > 0
}
/**
* Show Browser dialog to select file picker app
*/
private fun showBrowserDialog() {
try {
val fileManagerDialogFragment = FileManagerDialogFragment()
fragment?.let {
fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
} ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param keyFileCallback Callback retrieve from data
* @return true if requestCode was captured, false elsechere
*/
fun onActivityResultCallback(
requestCode: Int,
resultCode: Int,
data: Intent?,
keyFileCallback: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
FILE_BROWSE -> {
if (resultCode == RESULT_OK) {
val filename = data?.dataString
var keyUri: Uri? = null
if (filename != null) {
keyUri = UriUtil.parse(filename)
}
keyFileCallback?.invoke(keyUri)
}
return true
}
GET_CONTENT, OPEN_DOC -> {
if (resultCode == RESULT_OK) {
if (data != null) {
val uri = data.data
if (uri != null) {
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
}
} catch (e: Exception) {
// nop
}
keyFileCallback?.invoke(uri)
}
}
}
return true
}
}
return false
}
companion object {
private const val TAG = "OpenFileHelper"
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
}
}

View File

@@ -37,12 +37,12 @@ object Stylish {
* Initialize the class with a theme preference * Initialize the class with a theme preference
* @param context Context to retrieve the theme preference * @param context Context to retrieve the theme preference
*/ */
fun init(context: Context) { fun load(context: Context) {
Log.d(Stylish::class.java.name, "Attatching to " + context.packageName) Log.d(Stylish::class.java.name, "Attatching to " + context.packageName)
themeString = PreferencesUtil.getStyle(context) themeString = PreferencesUtil.getStyle(context)
} }
private fun retrieveEquivalentSystemStyle(context: Context, styleString: String): String { fun retrieveEquivalentSystemStyle(context: Context, styleString: String): String {
val systemNightMode = when (PreferencesUtil.getStyleBrightness(context)) { val systemNightMode = when (PreferencesUtil.getStyleBrightness(context)) {
context.getString(R.string.list_style_brightness_light) -> false context.getString(R.string.list_style_brightness_light) -> false
context.getString(R.string.list_style_brightness_night) -> true context.getString(R.string.list_style_brightness_night) -> true
@@ -84,12 +84,16 @@ object Stylish {
} }
} }
fun defaultStyle(context: Context): String {
return context.getString(R.string.list_style_name_light)
}
/** /**
* Assign the style to the class attribute * Assign the style to the class attribute
* @param styleString Style id String * @param styleString Style id String
*/ */
fun assignStyle(context: Context, styleString: String) { fun assignStyle(context: Context, styleString: String) {
themeString = retrieveEquivalentSystemStyle(context, styleString) PreferencesUtil.setStyle(context, styleString)
} }
/** /**

View File

@@ -29,7 +29,7 @@ class App : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Stylish.init(this) Stylish.load(this)
PRNGFixes.apply() PRNGFixes.apply()
} }

View File

@@ -36,6 +36,9 @@ abstract class CipherEngine {
return 16 return 16
} }
// Used only with padding workaround
var forcePaddingCompatibility = false
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher

View File

@@ -30,7 +30,7 @@ class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher { override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getTwofish(opmode, key, IV) return CipherFactory.getTwofish(opmode, key, IV, forcePaddingCompatibility)
} }
override fun getEncryptionAlgorithm(): EncryptionAlgorithm { override fun getEncryptionAlgorithm(): EncryptionAlgorithm {

View File

@@ -105,10 +105,6 @@ class Database {
return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache() return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache()
} }
fun setCacheDirectory(cacheDirectory: File) {
binaryCache.cacheDirectory = cacheDirectory
}
private val iconsManager: IconsManager private val iconsManager: IconsManager
get() { get() {
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache) return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache)

View File

@@ -466,16 +466,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
return result return result
} }
companion object {
companion object CREATOR : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
const val PMS_TAN_ENTRY = "<TAN>" const val PMS_TAN_ENTRY = "<TAN>"
/** /**
@@ -484,5 +475,16 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun newExtraFieldNameAllowed(field: Field): Boolean { fun newExtraFieldNameAllowed(field: Field): Boolean {
return EntryKDBX.newCustomNameAllowed(field.name) return EntryKDBX.newCustomNameAllowed(field.name)
} }
@JvmField
val CREATOR: Parcelable.Creator<Entry> = object : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
}
} }
} }

View File

@@ -11,7 +11,7 @@ class BinaryCache {
*/ */
var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey() var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey()
lateinit var cacheDirectory: File var cacheDirectory: File? = null
private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0)) private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0))
@@ -19,11 +19,12 @@ class BinaryCache {
smallSize: Boolean = false, smallSize: Boolean = false,
compression: Boolean = false, compression: Boolean = false,
protection: Boolean = false): BinaryData { protection: Boolean = false): BinaryData {
return if (smallSize) { val cacheDir = cacheDirectory
return if (smallSize || cacheDir == null) {
BinaryByte(binaryId, compression, protection) BinaryByte(binaryId, compression, protection)
} else { } else {
val fileInCache = File(cacheDirectory, binaryId) val fileInCache = File(cacheDir, binaryId)
return BinaryFile(fileInCache, compression, protection) BinaryFile(fileInCache, compression, protection)
} }
} }

View File

@@ -90,7 +90,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = parcel.readString() ?: url url = parcel.readString() ?: url
notes = parcel.readString() ?: notes notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription binaryDescription = parcel.readString() ?: binaryDescription
binaryDataId = parcel.readInt() val rawBinaryDataId = parcel.readInt()
binaryDataId = if (rawBinaryDataId == -1) null else rawBinaryDataId
} }
override fun readParentParcelable(parcel: Parcel): GroupKDB? { override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -109,9 +110,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeString(url) dest.writeString(url)
dest.writeString(notes) dest.writeString(notes)
dest.writeString(binaryDescription) dest.writeString(binaryDescription)
binaryDataId?.let { dest.writeInt(binaryDataId ?: -1)
dest.writeInt(it)
}
} }
fun updateWith(source: EntryKDB) { fun updateWith(source: EntryKDB) {

View File

@@ -56,32 +56,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
var additional = "" var additional = ""
var tags = "" var tags = ""
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
override var expires: Boolean = false override var expires: Boolean = false
constructor() : super() constructor() : super()
@@ -102,6 +76,14 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
tags = parcel.readString() ?: tags tags = parcel.readString() ?: tags
} }
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong()) dest.writeLong(usageCount.toKotlinLong())
@@ -164,14 +146,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return NodeIdUUID(nodeId.id) return NodeIdUUID(nodeId.id)
} }
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
/** /**
* Decode a reference key with the FieldReferencesEngine * Decode a reference key with the FieldReferencesEngine
* @param decodeRef * @param decodeRef
@@ -228,6 +202,32 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override var locationChanged = DateInstant() override var locationChanged = DateInstant()
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
fun afterChangeParent() { fun afterChangeParent() {
locationChanged = DateInstant() locationChanged = DateInstant()
} }
@@ -349,6 +349,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
const val STR_URL = "URL" const val STR_URL = "URL"
const val STR_NOTES = "Notes" const val STR_NOTES = "Notes"
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
fun newCustomNameAllowed(name: String): Boolean { fun newCustomNameAllowed(name: String): Boolean {
return !(name.equals(STR_TITLE, true) return !(name.equals(STR_TITLE, true)
|| name.equals(STR_USERNAME, true) || name.equals(STR_USERNAME, true)
@@ -367,7 +369,5 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return arrayOfNulls(size) return arrayOfNulls(size)
} }
} }
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
} }
} }

View File

@@ -36,6 +36,10 @@ abstract class EntryVersioned
constructor(parcel: Parcel) : super(parcel) constructor(parcel: Parcel) : super(parcel)
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
}
override fun nodeIndexInParentForNaturalOrder(): Int { override fun nodeIndexInParentForNaturalOrder(): Int {
if (nodeIndexInParentForNaturalOrder == -1) { if (nodeIndexInParentForNaturalOrder == -1) {
val numberOfGroups = parent?.getChildGroups()?.size val numberOfGroups = parent?.getChildGroups()?.size

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
class IconImage() : IconImageDraw(), Parcelable { class IconImage() : IconImageDraw() {
var standard: IconImageStandard = IconImageStandard() var standard: IconImageStandard = IconImageStandard()
var custom: IconImageCustom = IconImageCustom() var custom: IconImageCustom = IconImageCustom()

View File

@@ -25,7 +25,7 @@ import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.* import java.util.*
class IconImageCustom : Parcelable, IconImageDraw { class IconImageCustom : IconImageDraw {
var uuid: UUID var uuid: UUID

View File

@@ -19,7 +19,9 @@
*/ */
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
abstract class IconImageDraw { import android.os.Parcelable
abstract class IconImageDraw : Parcelable {
var selected = false var selected = false
/** /**

View File

@@ -23,7 +23,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
class IconImageStandard : Parcelable, IconImageDraw { class IconImageStandard : IconImageDraw {
val id: Int val id: Int

View File

@@ -151,9 +151,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
val cipher: Cipher val cipher: Cipher
try { try {
engine = EncryptionAlgorithm.getFrom(mDatabase.cipherUuid).cipherEngine engine = EncryptionAlgorithm.getFrom(mDatabase.cipherUuid).cipherEngine
engine.forcePaddingCompatibility = true
mDatabase.setDataEngine(engine) mDatabase.setDataEngine(engine)
mDatabase.encryptionAlgorithm = engine.getEncryptionAlgorithm() mDatabase.encryptionAlgorithm = engine.getEncryptionAlgorithm()
cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV)
engine.forcePaddingCompatibility = false
} catch (e: Exception) { } catch (e: Exception) {
throw InvalidAlgorithmDatabaseException(e) throw InvalidAlgorithmDatabaseException(e)
} }

View File

@@ -10,7 +10,6 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import kotlinx.coroutines.*
class AdvancedUnlockNotificationService : NotificationService() { class AdvancedUnlockNotificationService : NotificationService() {
@@ -18,9 +17,6 @@ class AdvancedUnlockNotificationService : NotificationService() {
private var mActionTaskBinder = AdvancedUnlockBinder() private var mActionTaskBinder = AdvancedUnlockBinder()
private var notificationTimeoutMilliSecs: Long = 0
private var mTimerJob: Job? = null
inner class AdvancedUnlockBinder: Binder() { inner class AdvancedUnlockBinder: Binder() {
fun getCipherDatabase(databaseUri: Uri): CipherDatabaseEntity? { fun getCipherDatabase(databaseUri: Uri): CipherDatabaseEntity? {
return mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString()} return mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString()}
@@ -80,23 +76,11 @@ class AdvancedUnlockNotificationService : NotificationService() {
when (intent?.action) { when (intent?.action) {
ACTION_TIMEOUT -> { ACTION_TIMEOUT -> {
notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this) val notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
// Not necessarily a foreground service // Not necessarily a foreground service
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) { if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
mTimerJob = CoroutineScope(Dispatchers.Main).launch { defineTimerJob(notificationBuilder, notificationTimeoutMilliSecs) {
val maxPos = 100 stopSelf()
val posDurationMills = notificationTimeoutMilliSecs / maxPos
for (pos in maxPos downTo 0) {
notificationBuilder.setProgress(maxPos, pos, false)
startForeground(notificationId, notificationBuilder.build())
delay(posDurationMills)
if (pos <= 0) {
stopSelf()
}
}
notificationManager?.cancel(notificationId)
mTimerJob = null
cancel()
} }
} else { } else {
startForeground(notificationId, notificationBuilder.build()) startForeground(notificationId, notificationBuilder.build())
@@ -118,7 +102,6 @@ class AdvancedUnlockNotificationService : NotificationService() {
override fun onDestroy() { override fun onDestroy() {
mTempCipherDao.clear() mTempCipherDao.clear()
mTimerJob?.cancel()
super.onDestroy() super.onDestroy()
} }

View File

@@ -26,7 +26,6 @@ import android.net.Uri
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
@@ -34,6 +33,7 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
@@ -173,7 +173,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
putExtra(FILE_URI_KEY, attachmentNotification.uri) putExtra(FILE_URI_KEY, attachmentNotification.uri)
}, PendingIntent.FLAG_CANCEL_CURRENT) }, PendingIntent.FLAG_CANCEL_CURRENT)
val fileName = DocumentFile.fromSingleUri(this, attachmentNotification.uri)?.name ?: "" val fileName = UriUtil.getFileData(this, attachmentNotification.uri)?.name
?: attachmentNotification.uri.path
val builder = buildNewNotification().apply { val builder = buildNewNotification().apply {
when (attachmentNotification.entryAttachmentState.streamDirection) { when (attachmentNotification.entryAttachmentState.streamDirection) {

View File

@@ -37,8 +37,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
override val notificationId = 485 override val notificationId = 485
private var mEntryInfo: EntryInfo? = null private var mEntryInfo: EntryInfo? = null
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var notificationTimeoutMilliSecs: Long = 0 private var mNotificationTimeoutMilliSecs: Long = 0
private var cleanCopyNotificationTimerTask: Thread? = null
override fun retrieveChannelId(): String { override fun retrieveChannelId(): String {
return CHANNEL_CLIPBOARD_ID return CHANNEL_CLIPBOARD_ID
@@ -70,7 +69,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO) mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO)
//Get settings //Get settings
notificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this) mNotificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this)
when { when {
intent == null -> Log.w(TAG, "null intent") intent == null -> Log.w(TAG, "null intent")
@@ -78,7 +77,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
newNotification(mEntryInfo?.title, constructListOfField(intent)) newNotification(mEntryInfo?.title, constructListOfField(intent))
} }
ACTION_CLEAN_CLIPBOARD == intent.action -> { ACTION_CLEAN_CLIPBOARD == intent.action -> {
stopTask(cleanCopyNotificationTimerTask) mTimerJob?.cancel()
cleanClipboard() cleanClipboard()
stopNotificationAndSendLockIfNeeded() stopNotificationAndSendLockIfNeeded()
} }
@@ -121,7 +120,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
} }
private fun newNotification(title: String?, fieldsToAdd: ArrayList<ClipboardEntryNotificationField>) { private fun newNotification(title: String?, fieldsToAdd: ArrayList<ClipboardEntryNotificationField>) {
stopTask(cleanCopyNotificationTimerTask) mTimerJob?.cancel()
val builder = buildNewNotification() val builder = buildNewNotification()
.setSmallIcon(R.drawable.notification_ic_clipboard_key_24dp) .setSmallIcon(R.drawable.notification_ic_clipboard_key_24dp)
@@ -147,7 +146,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
} }
private fun copyField(fieldToCopy: ClipboardEntryNotificationField, nextFields: ArrayList<ClipboardEntryNotificationField>) { private fun copyField(fieldToCopy: ClipboardEntryNotificationField, nextFields: ArrayList<ClipboardEntryNotificationField>) {
stopTask(cleanCopyNotificationTimerTask) mTimerJob?.cancel()
try { try {
var generatedValue = fieldToCopy.getGeneratedValue(mEntryInfo) var generatedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
@@ -170,40 +169,23 @@ class ClipboardEntryNotificationService : LockNotificationService() {
this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT) this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setDeleteIntent(cleanPendingIntent) builder.setDeleteIntent(cleanPendingIntent)
val myNotificationId = notificationId if (mNotificationTimeoutMilliSecs != NEVER) {
defineTimerJob(builder, mNotificationTimeoutMilliSecs, {
if (notificationTimeoutMilliSecs != NEVER) { val newGeneratedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
cleanCopyNotificationTimerTask = Thread { // New auto generated value
val maxPos = 100 if (generatedValue != newGeneratedValue) {
val posDurationMills = notificationTimeoutMilliSecs / maxPos generatedValue = newGeneratedValue
for (pos in maxPos downTo 0) { clipboardHelper?.copyToClipboard(fieldToCopy.label, generatedValue)
val newGeneratedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
// New auto generated value
if (generatedValue != newGeneratedValue) {
generatedValue = newGeneratedValue
clipboardHelper?.copyToClipboard(fieldToCopy.label, generatedValue)
}
builder.setProgress(maxPos, pos, false)
notificationManager?.notify(myNotificationId, builder.build())
try {
Thread.sleep(posDurationMills)
} catch (e: InterruptedException) {
break
}
if (pos <= 0) {
stopNotificationAndSendLockIfNeeded()
}
} }
stopTask(cleanCopyNotificationTimerTask) }) {
notificationManager?.cancel(myNotificationId) stopNotificationAndSendLockIfNeeded()
// Clean password only if no next field // Clean password only if no next field
if (nextFields.size <= 0) if (nextFields.size <= 0)
cleanClipboard() cleanClipboard()
} }
cleanCopyNotificationTimerTask?.start()
} else { } else {
// No timer // No timer
notificationManager?.notify(myNotificationId, builder.build()) notificationManager?.notify(notificationId, builder.build())
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -228,10 +210,6 @@ class ClipboardEntryNotificationService : LockNotificationService() {
override fun onDestroy() { override fun onDestroy() {
cleanClipboard() cleanClipboard()
stopTask(cleanCopyNotificationTimerTask)
cleanCopyNotificationTimerTask = null
super.onDestroy() super.onDestroy()
} }

View File

@@ -35,8 +35,7 @@ import com.kunzisoft.keepass.utils.LOCK_ACTION
class KeyboardEntryNotificationService : LockNotificationService() { class KeyboardEntryNotificationService : LockNotificationService() {
override val notificationId = 486 override val notificationId = 486
private var cleanNotificationTimerTask: Thread? = null private var mNotificationTimeoutMilliSecs: Long = 0
private var notificationTimeoutMilliSecs: Long = 0
private var pendingDeleteIntent: PendingIntent? = null private var pendingDeleteIntent: PendingIntent? = null
@@ -61,7 +60,7 @@ class KeyboardEntryNotificationService : LockNotificationService() {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
//Get settings //Get settings
notificationTimeoutMilliSecs = PreferenceManager.getDefaultSharedPreferences(this) mNotificationTimeoutMilliSecs = PreferenceManager.getDefaultSharedPreferences(this)
.getString(getString(R.string.keyboard_entry_timeout_key), .getString(getString(R.string.keyboard_entry_timeout_key),
getString(R.string.timeout_default))?.toLong() ?: TimeoutHelper.DEFAULT_TIMEOUT getString(R.string.timeout_default))?.toLong() ?: TimeoutHelper.DEFAULT_TIMEOUT
@@ -107,27 +106,12 @@ class KeyboardEntryNotificationService : LockNotificationService() {
notificationManager?.cancel(notificationId) notificationManager?.cancel(notificationId)
notificationManager?.notify(notificationId, builder.build()) notificationManager?.notify(notificationId, builder.build())
stopTask(cleanNotificationTimerTask)
// Timeout only if notification clear is available // Timeout only if notification clear is available
if (PreferencesUtil.isClearKeyboardNotificationEnable(this)) { if (PreferencesUtil.isClearKeyboardNotificationEnable(this)) {
if (notificationTimeoutMilliSecs != TimeoutHelper.NEVER) { if (mNotificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
cleanNotificationTimerTask = Thread { defineTimerJob(builder, mNotificationTimeoutMilliSecs) {
val maxPos = 100 stopNotificationAndSendLockIfNeeded()
val posDurationMills = notificationTimeoutMilliSecs / maxPos
for (pos in maxPos downTo 0) {
builder.setProgress(maxPos, pos, false)
notificationManager?.notify(notificationId, builder.build())
try {
Thread.sleep(posDurationMills)
} catch (e: InterruptedException) {
break
}
if (pos <= 0) {
stopNotificationAndSendLockIfNeeded()
}
}
} }
cleanNotificationTimerTask?.start()
} }
} }
} }
@@ -142,8 +126,6 @@ class KeyboardEntryNotificationService : LockNotificationService() {
// Remove the entry from the keyboard // Remove the entry from the keyboard
MagikIME.removeEntry(this) MagikIME.removeEntry(this)
stopTask(cleanNotificationTimerTask)
cleanNotificationTimerTask = null
pendingDeleteIntent?.cancel() pendingDeleteIntent?.cancel()
super.onDestroy() super.onDestroy()

View File

@@ -50,11 +50,6 @@ abstract class LockNotificationService : NotificationService() {
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
protected fun stopTask(task: Thread?) {
if (task != null && task.isAlive)
task.interrupt()
}
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
notificationManager?.cancel(notificationId) notificationManager?.cancel(notificationId)

View File

@@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish import com.kunzisoft.keepass.activities.stylish.Stylish
import kotlinx.coroutines.*
abstract class NotificationService : Service() { abstract class NotificationService : Service() {
@@ -18,6 +19,8 @@ abstract class NotificationService : Service() {
protected var notificationManager: NotificationManagerCompat? = null protected var notificationManager: NotificationManagerCompat? = null
private var colorNotificationAccent: Int = 0 private var colorNotificationAccent: Int = 0
protected var mTimerJob: Job? = null
protected abstract val notificationId: Int protected abstract val notificationId: Int
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
@@ -71,7 +74,33 @@ abstract class NotificationService : Service() {
} }
} }
protected fun defineTimerJob(builder: NotificationCompat.Builder,
timeoutMilliseconds: Long,
actionAfterASecond: (() -> Unit)? = null,
actionEnd: () -> Unit) {
mTimerJob?.cancel()
mTimerJob = CoroutineScope(Dispatchers.Main).launch {
val timeoutInSeconds = timeoutMilliseconds / 1000L
for (currentTime in timeoutInSeconds downTo 0) {
actionAfterASecond?.invoke()
builder.setProgress(100,
(currentTime * 100 / timeoutInSeconds).toInt(),
false)
startForeground(notificationId, builder.build())
delay(1000)
if (currentTime <= 0) {
actionEnd()
}
}
notificationManager?.cancel(notificationId)
mTimerJob = null
cancel()
}
}
override fun onDestroy() { override fun onDestroy() {
mTimerJob?.cancel()
mTimerJob = null
notificationManager?.cancel(notificationId) notificationManager?.cancel(notificationId)
super.onDestroy() super.onDestroy()

View File

@@ -20,9 +20,12 @@
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
class MagikeyboardSettingsFragment : PreferenceFragmentCompat() { class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
@@ -30,4 +33,31 @@ class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
// Load the preferences from an XML resource // Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences_keyboard, rootKey) setPreferencesFromResource(R.xml.preferences_keyboard, rootKey)
} }
override fun onDisplayPreferenceDialog(preference: Preference?) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
when (preference?.key) {
getString(R.string.keyboard_entry_timeout_key) -> {
dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference)
}
}
companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
}
} }

View File

@@ -30,6 +30,7 @@ import android.view.autofill.AutofillManager
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
@@ -46,6 +47,7 @@ import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.preference.IconPackListPreference import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
@@ -90,6 +92,20 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
true true
} }
findPreference<Preference>(getString(R.string.import_app_properties_key))?.setOnPreferenceClickListener { _ ->
(activity as? SettingsActivity?)?.apply {
importAppProperties()
}
true
}
findPreference<Preference>(getString(R.string.export_app_properties_key))?.setOnPreferenceClickListener { _ ->
(activity as? SettingsActivity?)?.apply {
exportAppProperties()
}
true
}
} }
} }
@@ -388,10 +404,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
Stylish.assignStyle(activity, styleIdString) Stylish.assignStyle(activity, styleIdString)
// Relaunch the current activity to redraw theme // Relaunch the current activity to redraw theme
(activity as? SettingsActivity?)?.apply { (activity as? SettingsActivity?)?.apply {
keepCurrentScreen() relaunchCurrentScreen()
startActivity(intent)
finish()
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} }
} }
styleEnabled styleEnabled
@@ -399,10 +412,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<ListPreference>(getString(R.string.setting_style_brightness_key))?.setOnPreferenceChangeListener { _, _ -> findPreference<ListPreference>(getString(R.string.setting_style_brightness_key))?.setOnPreferenceChangeListener { _, _ ->
(activity as? SettingsActivity?)?.apply { (activity as? SettingsActivity?)?.apply {
keepCurrentScreen() relaunchCurrentScreen()
startActivity(intent)
finish()
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} }
true true
} }
@@ -440,6 +450,31 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
} }
override fun onDisplayPreferenceDialog(preference: Preference?) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
when (preference?.key) {
getString(R.string.app_timeout_key),
getString(R.string.clipboard_timeout_key),
getString(R.string.temp_advanced_unlock_timeout_key) -> {
dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference)
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
activity?.let { activity -> activity?.let { activity ->
@@ -470,7 +505,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
companion object { companion object {
private const val REQUEST_CODE_AUTOFILL = 5201 private const val REQUEST_CODE_AUTOFILL = 5201
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
} }
} }

View File

@@ -576,7 +576,6 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
} }
companion object { companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT" private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
} }
} }

View File

@@ -21,14 +21,17 @@ package com.kunzisoft.keepass.settings
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import java.util.* import java.util.*
@@ -134,28 +137,46 @@ object PreferencesUtil {
} }
fun getStyle(context: Context): String { fun getStyle(context: Context): String {
val stylishPrefKey = context.getString(R.string.setting_style_key) val defaultStyleString = Stylish.defaultStyle(context)
val defaultStyleString = context.getString(R.string.list_style_name_light)
val styleString = PreferenceManager.getDefaultSharedPreferences(context) val styleString = PreferenceManager.getDefaultSharedPreferences(context)
.getString(stylishPrefKey, defaultStyleString) .getString(context.getString(R.string.setting_style_key), defaultStyleString)
?: defaultStyleString ?: defaultStyleString
return Stylish.retrieveEquivalentLightStyle(context, styleString) // Return the system style
return Stylish.retrieveEquivalentSystemStyle(context, styleString)
}
fun setStyle(context: Context, styleString: String) {
var tempThemeString = styleString
if (tempThemeString in BuildConfig.STYLES_DISABLED) {
tempThemeString = Stylish.defaultStyle(context)
}
// Store light style to show selection in array list
tempThemeString = Stylish.retrieveEquivalentLightStyle(context, tempThemeString)
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putString(context.getString(R.string.setting_style_key), tempThemeString)
.apply()
Stylish.load(context)
} }
fun getStyleBrightness(context: Context): String? { fun getStyleBrightness(context: Context): String? {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString(context.getString(R.string.setting_style_brightness_key), return prefs.getString(context.getString(R.string.setting_style_brightness_key),
context.resources.getString(R.string.list_style_brightness_follow_system)) context.getString(R.string.list_style_brightness_follow_system))
} }
/** /**
* Retrieve the text size in % (1 for 100%) * Retrieve the text size in % (1 for 100%)
*/ */
fun getListTextSize(context: Context): Float { fun getListTextSize(context: Context): Float {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val index = try {
val listSizeString = prefs.getString(context.getString(R.string.list_size_key), val prefs = PreferenceManager.getDefaultSharedPreferences(context)
context.getString(R.string.list_size_string_medium)) val listSizeString = prefs.getString(context.getString(R.string.list_size_key),
val index = context.resources.getStringArray(R.array.list_size_string_values).indexOf(listSizeString) context.getString(R.string.list_size_string_medium))
context.resources.getStringArray(R.array.list_size_string_values).indexOf(listSizeString)
} catch (e: Exception) {
1
}
val typedArray = context.resources.obtainTypedArray(R.array.list_size_values) val typedArray = context.resources.obtainTypedArray(R.array.list_size_values)
val listSize = typedArray.getFloat(index, 1.0F) val listSize = typedArray.getFloat(index, 1.0F)
typedArray.recycle() typedArray.recycle()
@@ -289,11 +310,13 @@ object PreferencesUtil {
} }
fun getListSort(context: Context): SortNodeEnum { fun getListSort(context: Context): SortNodeEnum {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) try {
prefs.getString(context.getString(R.string.sort_node_key), val prefs = PreferenceManager.getDefaultSharedPreferences(context)
SortNodeEnum.DB.name)?.let { prefs.getString(context.getString(R.string.sort_node_key),
return SortNodeEnum.valueOf(it) SortNodeEnum.DB.name)?.let {
} return SortNodeEnum.valueOf(it)
}
} catch (e: Exception) {}
return SortNodeEnum.DB return SortNodeEnum.DB
} }
@@ -517,4 +540,133 @@ object PreferencesUtil {
.putStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), setItems) .putStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), setItems)
.apply() .apply()
} }
fun getAppProperties(context: Context): Properties {
val properties = Properties()
for ((name, value) in PreferenceManager.getDefaultSharedPreferences(context).all) {
properties[name] = value.toString()
}
for ((name, value) in Education.getEducationSharedPreferences(context).all) {
properties[name] = value.toString()
}
return properties
}
private fun getStringSetFromProperties(value: String): Set<String> {
return value.removePrefix("[")
.removeSuffix("]")
.split(", ")
.toSet()
}
private fun putPropertiesInPreferences(properties: Properties,
preferences: SharedPreferences,
putProperty: (editor: SharedPreferences.Editor,
name: String,
value: String) -> Unit) {
preferences.edit().apply {
for ((name, value) in properties) {
try {
putProperty(this, name as String, value as String)
} catch (e:Exception) {
Log.e("PreferencesUtil", "Error when trying to parse app property $name=$value", e)
}
}
}.apply()
}
fun setAppProperties(context: Context, properties: Properties) {
putPropertiesInPreferences(properties,
PreferenceManager.getDefaultSharedPreferences(context)) { editor, name, value ->
when (name) {
context.getString(R.string.allow_no_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.delete_entered_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_read_only_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_auto_save_database_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.omit_backup_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.auto_focus_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.subdomain_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.app_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.lock_database_screen_off_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.lock_database_back_root_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.lock_database_show_button_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.password_length_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.list_password_generator_options_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.hide_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.allow_copy_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.remember_database_locations_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_recent_files_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_broken_locations_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.remember_keyfile_locations_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.biometric_unlock_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.device_credential_unlock_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.biometric_auto_open_prompt_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.temp_advanced_unlock_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.temp_advanced_unlock_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.magic_keyboard_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.clipboard_notifications_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.clear_clipboard_notification_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.clipboard_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.settings_autofill_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_notification_entry_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_notification_entry_clear_close_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_entry_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.keyboard_selection_entry_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_search_share_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_auto_go_action_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_key_vibrate_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_key_sound_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_database_credentials_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_fill_in_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_lock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_close_database_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_auto_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_inline_suggestions_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_ask_to_save_data_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_application_id_blocklist_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.autofill_web_domain_blocklist_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.setting_style_key) -> setStyle(context, value)
context.getString(R.string.setting_style_brightness_key) -> editor.putString(name, value)
context.getString(R.string.setting_icon_pack_choose_key) -> editor.putString(name, value)
context.getString(R.string.list_entries_show_username_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.list_groups_show_number_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.list_size_key) -> editor.putString(name, value)
context.getString(R.string.monospace_font_fields_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_uuid_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.sort_node_key) -> editor.putString(name, value)
context.getString(R.string.sort_group_before_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.sort_ascending_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.sort_recycle_bin_bottom_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.allow_copy_password_first_time_key) -> editor.putBoolean(name, value.toBoolean())
}
}
putPropertiesInPreferences(properties,
Education.getEducationSharedPreferences(context)) { editor, name, value ->
when (name) {
context.getString(R.string.education_create_db_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_select_db_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_unlock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_read_only_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_biometric_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_new_node_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_sort_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_lock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_copy_username_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_entry_edit_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_password_generator_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_entry_new_field_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_add_attachment_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_setup_OTP_key) -> editor.putBoolean(name, value.toBoolean())
}
}
}
} }

View File

@@ -24,22 +24,27 @@ import android.app.backup.BackupManager
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import java.util.*
open class SettingsActivity open class SettingsActivity
: LockingActivity(), : LockingActivity(),
@@ -48,6 +53,8 @@ open class SettingsActivity
PasswordEncodingDialogFragment.Listener { PasswordEncodingDialogFragment.Listener {
private var backupManager: BackupManager? = null private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var appPropertiesFileCreationRequestCode: Int? = null
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
@@ -70,6 +77,8 @@ open class SettingsActivity
coordinatorLayout = findViewById(R.id.toolbar_coordinator) coordinatorLayout = findViewById(R.id.toolbar_coordinator)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
mExternalFileHelper = ExternalFileHelper(this)
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty()) if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
toolbar?.setTitle(R.string.settings) toolbar?.setTitle(R.string.settings)
else else
@@ -216,6 +225,13 @@ open class SettingsActivity
hideOrShowLockButton(key) hideOrShowLockButton(key)
} }
fun relaunchCurrentScreen() {
keepCurrentScreen()
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
/** /**
* To keep the current screen when activity is reloaded * To keep the current screen when activity is reloaded
*/ */
@@ -235,6 +251,58 @@ open class SettingsActivity
replaceFragment(key, reload) replaceFragment(key, reload)
} }
fun importAppProperties() {
mExternalFileHelper?.openDocument()
}
fun exportAppProperties() {
appPropertiesFileCreationRequestCode = mExternalFileHelper?.createDocument(getString(R.string.app_properties_file_name))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Import app properties result
try {
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedfileUri ->
selectedfileUri?.let { uri ->
val appProperties = Properties()
contentResolver?.openInputStream(uri)?.use { inputStream ->
appProperties.load(inputStream)
}
PreferencesUtil.setAppProperties(this, appProperties)
// Restart the current activity
relaunchCurrentScreen()
Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show()
}
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_import_app_properties, Toast.LENGTH_LONG).show()
Log.e(TAG, "Unable to import app properties", e)
}
// Export app properties result
try {
if (requestCode == appPropertiesFileCreationRequestCode) {
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
createdFileUri?.let { uri ->
contentResolver?.openOutputStream(uri)?.use { outputStream ->
PreferencesUtil
.getAppProperties(this)
.store(outputStream, getString(R.string.description_app_properties))
}
Toast.makeText(this, R.string.success_export_app_properties, Toast.LENGTH_LONG).show()
}
}
appPropertiesFileCreationRequestCode = null
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show()
Log.e(LockingActivity.TAG, "Unable to export app properties", e)
}
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@@ -244,6 +312,8 @@ open class SettingsActivity
companion object { companion object {
private val TAG = SettingsActivity::class.java.name
private const val SHOW_LOCK = "SHOW_LOCK" private const val SHOW_LOCK = "SHOW_LOCK"
private const val TITLE_KEY = "TITLE_KEY" private const val TITLE_KEY = "TITLE_KEY"
private const val TAG_NESTED = "TAG_NESTED" private const val TAG_NESTED = "TAG_NESTED"

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import androidx.preference.DialogPreference
import com.kunzisoft.keepass.R
class DurationDialogPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
private var mDuration: Long = 0L
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_duration
}
/**
* Get current duration of preference
*/
fun getDuration(): Long {
return if (mDuration >= 0) mDuration else -1
}
/**
* Assign [duration] of preference
*/
fun setDuration(duration: Long) {
persistString(duration.toString())
notifyChanged()
}
override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any?) {
if (restorePersistedValue) {
mDuration = getPersistedString(mDuration.toString()).toLongOrNull() ?: mDuration
} else {
mDuration = defaultValue?.toString()?.toLongOrNull() ?: mDuration
persistString(mDuration.toString())
}
}
override fun onGetDefaultValue(a: TypedArray?, index: Int): Any {
return try {
a?.getString(index)?.toLongOrNull() ?: mDuration
} catch (e: Exception) {
mDuration
}
}
// Was previously a string
override fun persistString(value: String?): Boolean {
mDuration = value?.toLongOrNull() ?: mDuration
return super.persistString(value)
}
}

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import android.widget.NumberPicker
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preference.DurationDialogPreference
class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
private var mEnabled = true
private var mDays = 0
private var mHours = 0
private var mMinutes = 0
private var mSeconds = 0
private var daysNumberPicker: NumberPicker? = null
private var hoursNumberPicker: NumberPicker? = null
private var minutesNumberPicker: NumberPicker? = null
private var secondsNumberPicker: NumberPicker? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// To get items from saved instance state
if (savedInstanceState != null
&& savedInstanceState.containsKey(ENABLE_KEY)
&& savedInstanceState.containsKey(DAYS_KEY)
&& savedInstanceState.containsKey(HOURS_KEY)
&& savedInstanceState.containsKey(MINUTES_KEY)
&& savedInstanceState.containsKey(SECONDS_KEY)) {
mEnabled = savedInstanceState.getBoolean(ENABLE_KEY)
mDays = savedInstanceState.getInt(DAYS_KEY)
mHours = savedInstanceState.getInt(HOURS_KEY)
mMinutes = savedInstanceState.getInt(MINUTES_KEY)
mSeconds = savedInstanceState.getInt(SECONDS_KEY)
} else {
val currentPreference = preference
if (currentPreference is DurationDialogPreference) {
durationToDaysHoursMinutesSeconds(currentPreference.getDuration())
}
}
}
private fun durationToDaysHoursMinutesSeconds(duration: Long) {
if (duration < 0) {
mDays = 0
mHours = 0
mMinutes = 0
mSeconds = 0
} else {
mDays = (duration / (24L * 60L * 60L * 1000L)).toInt()
val daysMilliseconds = mDays * 24L * 60L * 60L * 1000L
mHours = ((duration - daysMilliseconds) / (60L * 60L * 1000L)).toInt()
val hoursMilliseconds = mHours * 60L * 60L * 1000L
mMinutes = ((duration - daysMilliseconds - hoursMilliseconds) / (60L * 1000L)).toInt()
val minutesMilliseconds = mMinutes * 60L * 1000L
mSeconds = ((duration - daysMilliseconds - hoursMilliseconds - minutesMilliseconds) / (1000L)).toInt()
}
}
private fun assignValuesInViews() {
daysNumberPicker?.value = mDays
hoursNumberPicker?.value = mHours
minutesNumberPicker?.value = mMinutes
secondsNumberPicker?.value = mSeconds
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
daysNumberPicker = view.findViewById<NumberPicker>(R.id.days_picker).apply {
minValue = 0
maxValue = 364
setOnValueChangedListener { _, _, newVal ->
mDays = newVal
activateSwitch()
}
}
hoursNumberPicker = view.findViewById<NumberPicker>(R.id.hours_picker).apply {
minValue = 0
maxValue = 23
setOnValueChangedListener { _, _, newVal ->
mHours = newVal
activateSwitch()
}
}
minutesNumberPicker = view.findViewById<NumberPicker>(R.id.minutes_picker).apply {
minValue = 0
maxValue = 59
setOnValueChangedListener { _, _, newVal ->
mMinutes = newVal
activateSwitch()
}
}
secondsNumberPicker = view.findViewById<NumberPicker>(R.id.seconds_picker).apply {
minValue = 0
maxValue = 59
setOnValueChangedListener { _, _, newVal ->
mSeconds = newVal
activateSwitch()
}
}
setSwitchAction({ isChecked ->
mEnabled = isChecked
}, mDays + mHours + mMinutes + mSeconds > 0)
assignValuesInViews()
}
private fun buildDuration(): Long {
return if (mEnabled) {
mDays * 24L * 60L * 60L * 1000L +
mHours * 60L * 60L * 1000L +
mMinutes * 60L * 1000L +
mSeconds * 1000L
} else {
-1
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(ENABLE_KEY, mEnabled)
outState.putInt(DAYS_KEY, mDays)
outState.putInt(HOURS_KEY, mHours)
outState.putInt(MINUTES_KEY, mMinutes)
outState.putInt(SECONDS_KEY, mSeconds)
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
val currentPreference = preference
if (currentPreference is DurationDialogPreference) {
currentPreference.setDuration(buildDuration())
}
}
}
companion object {
private const val ENABLE_KEY = "ENABLE_KEY"
private const val DAYS_KEY = "DAYS_KEY"
private const val HOURS_KEY = "HOURS_KEY"
private const val MINUTES_KEY = "MINUTES_KEY"
private const val SECONDS_KEY = "SECONDS_KEY"
fun newInstance(key: String): DurationDialogFragmentCompat {
val fragment = DurationDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -154,4 +154,14 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
onCheckedChange?.invoke(isChecked) onCheckedChange?.invoke(isChecked)
} }
} }
fun activateSwitch() {
if (switchElementView?.isChecked != true)
switchElementView?.isChecked = true
}
fun deactivateSwitch() {
if (switchElementView?.isChecked == true)
switchElementView?.isChecked = false
}
} }

View File

@@ -1,86 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853
private var fileRequestCodes = ArrayList<Int>()
fun getUnusedCreateFileRequestCode(): Int {
val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++
fileRequestCodes.add(newCreateFileRequestCode)
return newCreateFileRequestCode
}
@SuppressLint("InlinedApi")
fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager): Boolean {
return when {
// To check if a custom file manager can manage the ACTION_CREATE_DOCUMENT
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
packageManager.queryIntentActivities(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}, PackageManager.MATCH_DEFAULT_ONLY).isNotEmpty()
}
else -> true
}
}
fun createDocument(activity: FragmentActivity,
titleString: String,
typeString: String = "application/octet-stream"): Int? {
val idCode = getUnusedCreateFileRequestCode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
activity.startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
putExtra(Intent.EXTRA_TITLE, titleString)
}, idCode)
return idCode
} catch (e: Exception) {
FileManagerDialogFragment().show(activity.supportFragmentManager, "browserDialog")
}
} else {
FileManagerDialogFragment().show(activity.supportFragmentManager, "browserDialog")
}
return null
}
fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
action: (fileCreated: Uri?)->Unit) {
// Retrieve the created URI from the file manager
if (fileRequestCodes.contains(requestCode) && resultCode == Activity.RESULT_OK) {
action.invoke(data?.data)
fileRequestCodes.remove(requestCode)
}
}

View File

@@ -38,16 +38,26 @@ object UriUtil {
fun getFileData(context: Context, fileUri: Uri?): DocumentFile? { fun getFileData(context: Context, fileUri: Uri?): DocumentFile? {
if (fileUri == null) if (fileUri == null)
return null return null
return when { return try {
isFileScheme(fileUri) -> { when {
fileUri.path?.let { isFileScheme(fileUri) -> {
File(it).let { file -> fileUri.path?.let {
return DocumentFile.fromFile(file) File(it).let { file ->
return DocumentFile.fromFile(file)
}
} }
} }
isContentScheme(fileUri) -> {
DocumentFile.fromSingleUri(context, fileUri)
}
else -> {
Log.e("FileData", "Content scheme not known")
null
}
} }
isContentScheme(fileUri) -> DocumentFile.fromSingleUri(context, fileUri) } catch (e: Exception) {
else -> null Log.e("FileData", "Unable to get document file", e)
null
} }
} }

View File

@@ -6,9 +6,9 @@ import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.documentfile.provider.DocumentFile
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class KeyFileSelectionView @JvmOverloads constructor(context: Context, class KeyFileSelectionView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@@ -51,7 +51,7 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
set(value) { set(value) {
mUri = value mUri = value
keyFileNameView.text = value?.let { keyFileNameView.text = value?.let {
DocumentFile.fromSingleUri(context, value)?.name ?: value.path UriUtil.getFileData(context, value)?.name ?: value.path
} ?: "" } ?: ""
} }
} }

View File

@@ -50,14 +50,18 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
).execute() ).execute()
} }
private fun getDatabaseFilesLoadedValue(): DatabaseFileData {
var newValue = databaseFilesLoaded.value
if (newValue == null) {
newValue = DatabaseFileData()
}
return newValue
}
fun loadListOfDatabases() { fun loadListOfDatabases() {
checkDefaultDatabase() checkDefaultDatabase()
mFileDatabaseHistoryAction?.getDatabaseFileList { databaseFileListRetrieved -> mFileDatabaseHistoryAction?.getDatabaseFileList { databaseFileListRetrieved ->
var newValue = databaseFilesLoaded.value databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
if (newValue == null) {
newValue = DatabaseFileData()
}
newValue.apply {
databaseFileAction = DatabaseFileAction.NONE databaseFileAction = DatabaseFileAction.NONE
databaseFileToActivate = null databaseFileToActivate = null
databaseFileList.apply { databaseFileList.apply {
@@ -65,14 +69,13 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
addAll(databaseFileListRetrieved) addAll(databaseFileListRetrieved)
} }
} }
databaseFilesLoaded.value = newValue
} }
} }
fun addDatabaseFile(databaseUri: Uri, keyFileUri: Uri?) { fun addDatabaseFile(databaseUri: Uri, keyFileUri: Uri?) {
mFileDatabaseHistoryAction?.addOrUpdateDatabaseUri(databaseUri, keyFileUri) { databaseFileAdded -> mFileDatabaseHistoryAction?.addOrUpdateDatabaseUri(databaseUri, keyFileUri) { databaseFileAdded ->
databaseFileAdded?.let { _ -> databaseFileAdded?.let { _ ->
databaseFilesLoaded.value = databaseFilesLoaded.value?.apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
this.databaseFileAction = DatabaseFileAction.ADD this.databaseFileAction = DatabaseFileAction.ADD
this.databaseFileList.add(databaseFileAdded) this.databaseFileList.add(databaseFileAdded)
this.databaseFileToActivate = databaseFileAdded this.databaseFileToActivate = databaseFileAdded
@@ -84,7 +87,7 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
fun updateDatabaseFile(databaseFileToUpdate: DatabaseFile) { fun updateDatabaseFile(databaseFileToUpdate: DatabaseFile) {
mFileDatabaseHistoryAction?.addOrUpdateDatabaseFile(databaseFileToUpdate) { databaseFileUpdated -> mFileDatabaseHistoryAction?.addOrUpdateDatabaseFile(databaseFileToUpdate) { databaseFileUpdated ->
databaseFileUpdated?.let { _ -> databaseFileUpdated?.let { _ ->
databaseFilesLoaded.value = databaseFilesLoaded.value?.apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
this.databaseFileAction = DatabaseFileAction.UPDATE this.databaseFileAction = DatabaseFileAction.UPDATE
this.databaseFileList this.databaseFileList
.find { it.databaseUri == databaseFileUpdated.databaseUri } .find { it.databaseUri == databaseFileUpdated.databaseUri }
@@ -104,7 +107,7 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile) { fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile) {
mFileDatabaseHistoryAction?.deleteDatabaseFile(databaseFileToDelete) { databaseFileDeleted -> mFileDatabaseHistoryAction?.deleteDatabaseFile(databaseFileToDelete) { databaseFileDeleted ->
databaseFileDeleted?.let { _ -> databaseFileDeleted?.let { _ ->
databaseFilesLoaded.value = databaseFilesLoaded.value?.apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
databaseFileAction = DatabaseFileAction.DELETE databaseFileAction = DatabaseFileAction.DELETE
databaseFileToActivate = databaseFileDeleted databaseFileToActivate = databaseFileDeleted
databaseFileList.remove(databaseFileDeleted) databaseFileList.remove(databaseFileDeleted)

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM7,10h5v5L7,15z"/>
</vector>

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePassDX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/edit"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/explanation_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:gravity="center"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/explanation_text" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/switch_element">
<LinearLayout
android:id="@+id/duration_days_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration_hours_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_hours_picker">
<NumberPicker
android:id="@+id/days_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:src="@drawable/ic_day_white_24dp"
app:tint="?android:attr/textColor"
android:contentDescription="@string/digits" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_hours_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_days_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_days_picker"
app:layout_constraintEnd_toStartOf="@+id/duration_time_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_time_picker">
<NumberPicker
android:id="@+id/hours_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="bold"
android:text=":" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_time_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_hours_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_hours_picker"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent">
<NumberPicker
android:id="@+id/minutes_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="'"/>
<NumberPicker
android:id="@+id/seconds_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="''"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -122,17 +122,6 @@
<string name="uppercase">Majúscules</string> <string name="uppercase">Majúscules</string>
<string name="version_label">Versió %1$s</string> <string name="version_label">Versió %1$s</string>
<string name="education_unlock_summary">Introdueix una contrasenya i/o un arxiu clau per desbloquejar la base de dades.</string> <string name="education_unlock_summary">Introdueix una contrasenya i/o un arxiu clau per desbloquejar la base de dades.</string>
<string-array name="timeout_options">
<item>5 segons</item>
<item>10 segons</item>
<item>20 segons</item>
<item>30 segons</item>
<item>1 minut</item>
<item>5 minuts</item>
<item>15 minuts</item>
<item>30 minuts</item>
<item>Mai</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Petita</item> <item>Petita</item>
<item>Mitjana</item> <item>Mitjana</item>

View File

@@ -133,17 +133,6 @@
<string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem. <string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem.
\n \n
\nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.</string> \nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.</string>
<string-array name="timeout_options">
<item>5 sekund</item>
<item>10 sekund</item>
<item>20 sekund</item>
<item>30 sekund</item>
<item>1 minuta</item>
<item>5 minut</item>
<item>15 minut</item>
<item>30 minut</item>
<item>Nikdy</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Malý</item> <item>Malý</item>
<item>Střední</item> <item>Střední</item>

View File

@@ -132,17 +132,6 @@
<string name="education_unlock_summary">Angiv en adgangskode og/eller en nøglefil til at låse databasen op. <string name="education_unlock_summary">Angiv en adgangskode og/eller en nøglefil til at låse databasen op.
\n \n
\nHusk at gemme en kopi af .kdbx filen i et sikkert sted efter hver ændring.</string> \nHusk at gemme en kopi af .kdbx filen i et sikkert sted efter hver ændring.</string>
<string-array name="timeout_options">
<item>5 sekunder</item>
<item>10 sekunder</item>
<item>20 sekunder</item>
<item>30 sekunder</item>
<item>1 minut</item>
<item>5 minutter</item>
<item>15 minutter</item>
<item>30 minutter</item>
<item>Aldrig</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Lille</item> <item>Lille</item>
<item>Mellem</item> <item>Mellem</item>

View File

@@ -147,17 +147,6 @@
<string name="education_unlock_summary">Geben Sie das Passwort und/oder die Schlüsseldatei ein, um Ihre Datenbank zu entsperren. <string name="education_unlock_summary">Geben Sie das Passwort und/oder die Schlüsseldatei ein, um Ihre Datenbank zu entsperren.
\n \n
\nSichern Sie Ihre Datenbankdatei nach jeder Änderung an einem sicheren Ort.</string> \nSichern Sie Ihre Datenbankdatei nach jeder Änderung an einem sicheren Ort.</string>
<string-array name="timeout_options">
<item>5 Sekunden</item>
<item>10 Sekunden</item>
<item>20 Sekunden</item>
<item>30 Sekunden</item>
<item>1 Minute</item>
<item>5 Minuten</item>
<item>15 Minuten</item>
<item>30 Minuten</item>
<item>Nie</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Klein</item> <item>Klein</item>
<item>Mittel</item> <item>Mittel</item>

View File

@@ -135,17 +135,6 @@
<string name="education_unlock_summary">Καταχωρίστε τον κωδικό πρόσβασης και /ή το αρχείο-κλειδί για να ξεκλειδώσετε τη βάση δεδομένων σας. <string name="education_unlock_summary">Καταχωρίστε τον κωδικό πρόσβασης και /ή το αρχείο-κλειδί για να ξεκλειδώσετε τη βάση δεδομένων σας.
\n \n
\nΔημιουργήστε αντίγραφα ασφαλείας του αρχείου βάσης δεδομένων σας, σε ασφαλές μέρος μετά από κάθε αλλαγή.</string> \nΔημιουργήστε αντίγραφα ασφαλείας του αρχείου βάσης δεδομένων σας, σε ασφαλές μέρος μετά από κάθε αλλαγή.</string>
<string-array name="timeout_options">
<item>5 δευτερόλεπτα</item>
<item>10 δευτερόλεπτα</item>
<item>20 δευτερόλεπτα</item>
<item>30 δευτερόλεπτα</item>
<item>1 λεπτό</item>
<item>5 λεπτά</item>
<item>15 λεπτά</item>
<item>30 λεπτά</item>
<item>Ποτέ</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Μικρά</item> <item>Μικρά</item>
<item>Μεσαία</item> <item>Μεσαία</item>

View File

@@ -125,17 +125,6 @@
<string name="education_unlock_summary">Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos. <string name="education_unlock_summary">Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos.
\n \n
\nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.</string> \nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.</string>
<string-array name="timeout_options">
<item>5 segundos</item>
<item>10 segundos</item>
<item>20 segundos</item>
<item>30 segundos</item>
<item>1 minuto</item>
<item>5 minutos</item>
<item>15 minutos</item>
<item>30 minutos</item>
<item>Nunca</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Pequeño</item> <item>Pequeño</item>
<item>Mediano</item> <item>Mediano</item>

View File

@@ -132,17 +132,6 @@
<string name="uppercase">Maiuskulak</string> <string name="uppercase">Maiuskulak</string>
<string name="version_label">Bertsioa %1$s</string> <string name="version_label">Bertsioa %1$s</string>
<string name="education_unlock_summary">Sartu pasahitz eta / edo gako fitxategi bat zure datubasea desblokeatzeko.</string> <string name="education_unlock_summary">Sartu pasahitz eta / edo gako fitxategi bat zure datubasea desblokeatzeko.</string>
<string-array name="timeout_options">
<item>5 segundu</item>
<item>10 segundu</item>
<item>20 segundu</item>
<item>30 segundu</item>
<item>minutu 1</item>
<item>5 minutu</item>
<item>15 minutu</item>
<item>30 minutu</item>
<item>Inoiz ez</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Txikia</item> <item>Txikia</item>
<item>Ertaina</item> <item>Ertaina</item>

View File

@@ -132,17 +132,6 @@
<string name="uppercase">Isot kirjaimet</string> <string name="uppercase">Isot kirjaimet</string>
<string name="version_label">Versio %1$s</string> <string name="version_label">Versio %1$s</string>
<string name="education_unlock_summary">Syötä salasana ja/tai avaintiedosto avataksesi tietokantasi.</string> <string name="education_unlock_summary">Syötä salasana ja/tai avaintiedosto avataksesi tietokantasi.</string>
<string-array name="timeout_options">
<item>5 sekuntia</item>
<item>10 sekuntia</item>
<item>20 sekuntia</item>
<item>30 sekuntia</item>
<item>1 minuutti</item>
<item>5 minuttia</item>
<item>15 minuttia</item>
<item>30 minuttia</item>
<item>Ei koskaan</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Pieni</item> <item>Pieni</item>
<item>Keskikokoinen</item> <item>Keskikokoinen</item>

View File

@@ -258,17 +258,6 @@
<string name="html_text_dev_feature_upgrade">Noubliez pas de garder votre application à jour en installant les nouvelles versions.</string> <string name="html_text_dev_feature_upgrade">Noubliez pas de garder votre application à jour en installant les nouvelles versions.</string>
<string name="download">Télécharger</string> <string name="download">Télécharger</string>
<string name="contribute">Contribuer</string> <string name="contribute">Contribuer</string>
<string-array name="timeout_options">
<item>5 secondes</item>
<item>10 secondes</item>
<item>20 secondes</item>
<item>30 secondes</item>
<item>1 minute</item>
<item>5 minutes</item>
<item>15 minutes</item>
<item>30 minutes</item>
<item>Jamais</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Petit</item> <item>Petit</item>
<item>Moyen</item> <item>Moyen</item>

View File

@@ -140,17 +140,6 @@
<string name="education_unlock_summary">Adja meg a jelszót és/vagy a kulcsfájlt, hogy kinyithassa az adatbázist. <string name="education_unlock_summary">Adja meg a jelszót és/vagy a kulcsfájlt, hogy kinyithassa az adatbázist.
\n \n
\nKészítsen biztonsági mentést az adatbázisról minden egyes módosítás után.</string> \nKészítsen biztonsági mentést az adatbázisról minden egyes módosítás után.</string>
<string-array name="timeout_options">
<item>5 másodperc</item>
<item>10 másodperc</item>
<item>20 másodperc</item>
<item>30 másodperc</item>
<item>1 perc</item>
<item>5 perc</item>
<item>15 perc</item>
<item>30 perc</item>
<item>Soha</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Kicsi</item> <item>Kicsi</item>
<item>Közepes</item> <item>Közepes</item>

View File

@@ -142,17 +142,6 @@
<string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati. <string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati.
\n \n
\nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.</string> \nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.</string>
<string-array name="timeout_options">
<item>5 secondi</item>
<item>10 secondi</item>
<item>20 secondi</item>
<item>30 secondi</item>
<item>1 minuto</item>
<item>5 minuti</item>
<item>15 minuti</item>
<item>30 minuti</item>
<item>Mai</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Piccolo</item> <item>Piccolo</item>
<item>Medio</item> <item>Medio</item>

View File

@@ -129,17 +129,6 @@
<string name="uppercase">רישית</string> <string name="uppercase">רישית</string>
<string name="version_label">גרסה %1$s</string> <string name="version_label">גרסה %1$s</string>
<string name="education_unlock_summary">הזן סיסמה ו/או קובץ מפתח כדי לפתוח את מסד הנתונים.</string> <string name="education_unlock_summary">הזן סיסמה ו/או קובץ מפתח כדי לפתוח את מסד הנתונים.</string>
<string-array name="timeout_options">
<item>5 שניות</item>
<item>10 שניות</item>
<item>20 שניות</item>
<item>30 שניות</item>
<item>דקה אחת</item>
<item>5 דקות</item>
<item>15 דקות</item>
<item>30 דקות</item>
<item>אף פעם</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>קטן</item> <item>קטן</item>
<item>בינוני</item> <item>בינוני</item>

View File

@@ -470,17 +470,6 @@
<string name="download_progression">進行中:%1$d%%</string> <string name="download_progression">進行中:%1$d%%</string>
<string name="download_finalization">終了しています…</string> <string name="download_finalization">終了しています…</string>
<string name="download_complete">完了しました!</string> <string name="download_complete">完了しました!</string>
<string-array name="timeout_options">
<item>5秒</item>
<item>10秒</item>
<item>20秒</item>
<item>30秒</item>
<item>1分</item>
<item>5分</item>
<item>15分</item>
<item>30分</item>
<item>なし</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item></item> <item></item>
<item></item> <item></item>

View File

@@ -129,17 +129,6 @@
<string name="uppercase">Lielie burti</string> <string name="uppercase">Lielie burti</string>
<string name="version_label">Versija %1$s</string> <string name="version_label">Versija %1$s</string>
<string name="education_unlock_summary">Ievadiet paroli/atslēgas failu, lai atbloķētu savu datu bāzi.</string> <string name="education_unlock_summary">Ievadiet paroli/atslēgas failu, lai atbloķētu savu datu bāzi.</string>
<string-array name="timeout_options">
<item>5 sekundes</item>
<item>10 sekundes</item>
<item>20 sekundes</item>
<item>30 sekundes</item>
<item>1 minūte</item>
<item>5 minūtes</item>
<item>15 minūtes</item>
<item>30 minūtes</item>
<item>Nekad</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Mazs</item> <item>Mazs</item>
<item>Vidējs</item> <item>Vidējs</item>

View File

@@ -125,17 +125,6 @@
<string name="education_unlock_summary">Voer het wachtwoord en/of sleutelbestand in om je database te ontgrendelen. <string name="education_unlock_summary">Voer het wachtwoord en/of sleutelbestand in om je database te ontgrendelen.
\n \n
\nMaak na elke aanpassing een kopie van je .kdbx-bestand op een veilige locatie.</string> \nMaak na elke aanpassing een kopie van je .kdbx-bestand op een veilige locatie.</string>
<string-array name="timeout_options">
<item>5 seconden</item>
<item>10 seconden</item>
<item>20 seconden</item>
<item>30 seconden</item>
<item>1 minuut</item>
<item>5 minuten</item>
<item>15 minuten</item>
<item>30 minuten</item>
<item>Nooit</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Klein</item> <item>Klein</item>
<item>Medium</item> <item>Medium</item>

View File

@@ -120,17 +120,6 @@
<string name="uppercase">Store bokstavar</string> <string name="uppercase">Store bokstavar</string>
<string name="version_label">Utgåve %1$s</string> <string name="version_label">Utgåve %1$s</string>
<string name="education_unlock_summary">Skriv inn passordet og/eller nøkkelfil for å låsa opp databasen.</string> <string name="education_unlock_summary">Skriv inn passordet og/eller nøkkelfil for å låsa opp databasen.</string>
<string-array name="timeout_options">
<item>5 sekund</item>
<item>10 sekund</item>
<item>20 sekund</item>
<item>30 sekund</item>
<item>1 minutt</item>
<item>5 minutt</item>
<item>15 minutt</item>
<item>30 minutt</item>
<item>Aldri</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Liten</item> <item>Liten</item>
<item>Middels</item> <item>Middels</item>

View File

@@ -120,17 +120,6 @@
<string name="education_unlock_summary">prowadź hasło i/lub plik klucza, aby odblokować bazę danych. <string name="education_unlock_summary">prowadź hasło i/lub plik klucza, aby odblokować bazę danych.
\n \n
\nUtwórz kopię zapasową pliku bazy danych w bezpiecznym miejscu po każdej zmianie.</string> \nUtwórz kopię zapasową pliku bazy danych w bezpiecznym miejscu po każdej zmianie.</string>
<string-array name="timeout_options">
<item>5 sekund</item>
<item>10 sekund</item>
<item>20 sekund</item>
<item>30 sekund</item>
<item>1 minuta</item>
<item>5 minut</item>
<item>15 minut</item>
<item>30 minut</item>
<item>Nigdy</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Mała</item> <item>Mała</item>
<item>Średnia</item> <item>Średnia</item>

View File

@@ -123,17 +123,6 @@
<string name="education_unlock_summary">Entre com a senha e/ou com o caminho para o arquivo-chave do banco de dados. <string name="education_unlock_summary">Entre com a senha e/ou com o caminho para o arquivo-chave do banco de dados.
\n \n
\nGuarde uma cópia do seu arquivo do banco em um lugar mais seguro depois de cada alteração.</string> \nGuarde uma cópia do seu arquivo do banco em um lugar mais seguro depois de cada alteração.</string>
<string-array name="timeout_options">
<item>5 segundos</item>
<item>10 segundos</item>
<item>20 segundos</item>
<item>30 segundos</item>
<item>1 minuto</item>
<item>5 minutos</item>
<item>15 minutos</item>
<item>30 minutos</item>
<item>Nunca</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Pequeno</item> <item>Pequeno</item>
<item>Médio</item> <item>Médio</item>

View File

@@ -139,17 +139,6 @@
<string name="education_unlock_summary">Entre com a palavra-passe e/ou com o caminho para o ficheiro-chave da base de dados. <string name="education_unlock_summary">Entre com a palavra-passe e/ou com o caminho para o ficheiro-chave da base de dados.
\n \n
\nGuarde uma cópia do seu ficheiro do banco num lugar mais seguro depois de cada alteração.</string> \nGuarde uma cópia do seu ficheiro do banco num lugar mais seguro depois de cada alteração.</string>
<string-array name="timeout_options">
<item>5 segundos</item>
<item>10 segundos</item>
<item>20 segundos</item>
<item>30 segundos</item>
<item>1 minuto</item>
<item>5 minutos</item>
<item>15 minutos</item>
<item>30 minutos</item>
<item>Nunca</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Pequena</item> <item>Pequena</item>
<item>Média</item> <item>Média</item>

View File

@@ -141,17 +141,6 @@
<string name="education_unlock_summary">Введите пароль и/или файл ключа, чтобы разблокировать базу. <string name="education_unlock_summary">Введите пароль и/или файл ключа, чтобы разблокировать базу.
\n \n
\nНе забывайте сохранять копию файла базы в безопасном месте после каждого изменения.</string> \nНе забывайте сохранять копию файла базы в безопасном месте после каждого изменения.</string>
<string-array name="timeout_options">
<item>5 секунд</item>
<item>10 секунд</item>
<item>20 секунд</item>
<item>30 секунд</item>
<item>1 минута</item>
<item>5 минут</item>
<item>15 минут</item>
<item>30 минут</item>
<item>Никогда</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Мелкий</item> <item>Мелкий</item>
<item>Обычный</item> <item>Обычный</item>

View File

@@ -120,17 +120,6 @@
<string name="uppercase">Veľké písmená</string> <string name="uppercase">Veľké písmená</string>
<string name="version_label">Version %1$s</string> <string name="version_label">Version %1$s</string>
<string name="education_unlock_summary">Vložte heslo a / alebo keyfile pre odomknutie databázy.</string> <string name="education_unlock_summary">Vložte heslo a / alebo keyfile pre odomknutie databázy.</string>
<string-array name="timeout_options">
<item>5 sekúnd</item>
<item>10 sekúnd</item>
<item>20 sekúnd</item>
<item>30 sekúnd</item>
<item>1 minúta</item>
<item>5 minút</item>
<item>15 minút</item>
<item>30 minút</item>
<item>Nikdy</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Malé</item> <item>Malé</item>
<item>Stredné</item> <item>Stredné</item>

View File

@@ -133,17 +133,6 @@
<string name="education_unlock_summary">Ange lösenord och/eller nyckelfil för att öppna databasen. <string name="education_unlock_summary">Ange lösenord och/eller nyckelfil för att öppna databasen.
\n \n
\nBacka upp databasfilen på ett säkert ställe efter varje ändring.</string> \nBacka upp databasfilen på ett säkert ställe efter varje ändring.</string>
<string-array name="timeout_options">
<item>5 sekunder</item>
<item>10 sekunder</item>
<item>20 sekunder</item>
<item>30 sekunder</item>
<item>1 minut</item>
<item>5 minuter</item>
<item>15 minuter</item>
<item>30 minuter</item>
<item>Aldrig</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Liten</item> <item>Liten</item>
<item>Medium</item> <item>Medium</item>

View File

@@ -123,17 +123,6 @@
<string name="education_unlock_summary">Введіть пароль та/або файл ключа, щоб відкрити базу даних. <string name="education_unlock_summary">Введіть пароль та/або файл ключа, щоб відкрити базу даних.
\n \n
\nСтворюйте резервну копію файлу бази даних після кожної внесеної зміни та зберігайте її у безпечному місці.</string> \nСтворюйте резервну копію файлу бази даних після кожної внесеної зміни та зберігайте її у безпечному місці.</string>
<string-array name="timeout_options">
<item>5 секунд</item>
<item>10 секунд</item>
<item>20 секунд</item>
<item>30 секунд</item>
<item>1 хвилина</item>
<item>5 хвилин</item>
<item>15 хвилин</item>
<item>30 хвилин</item>
<item>Ніколи</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Малий</item> <item>Малий</item>
<item>Середній</item> <item>Середній</item>

View File

@@ -119,17 +119,6 @@
<string name="education_unlock_summary">输入密码和/或密钥文件来解锁你的数据库。 <string name="education_unlock_summary">输入密码和/或密钥文件来解锁你的数据库。
\n \n
\n记得在每次做出更改后将数据库文件备份至安全的地方。</string> \n记得在每次做出更改后将数据库文件备份至安全的地方。</string>
<string-array name="timeout_options">
<item>5秒</item>
<item>10秒</item>
<item>20秒</item>
<item>30秒</item>
<item>1分钟</item>
<item>5分钟</item>
<item>15分钟</item>
<item>30分钟</item>
<item>从不</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item></item> <item></item>
<item></item> <item></item>

View File

@@ -117,17 +117,6 @@
<string name="unsupported_db_version">不支援的資料庫版本。</string> <string name="unsupported_db_version">不支援的資料庫版本。</string>
<string name="uppercase">大寫</string> <string name="uppercase">大寫</string>
<string name="education_unlock_summary">輸入密碼和/或一個密鑰檔來解鎖你的資料庫.</string> <string name="education_unlock_summary">輸入密碼和/或一個密鑰檔來解鎖你的資料庫.</string>
<string-array name="timeout_options">
<item>5秒</item>
<item>10秒</item>
<item>20秒</item>
<item>30秒</item>
<item>1分鐘</item>
<item>5分鐘</item>
<item>15分鐘</item>
<item>30分鐘</item>
<item>從不</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item></item> <item></item>
<item></item> <item></item>

View File

@@ -47,6 +47,7 @@
<string name="database_file_name_default" translatable="false">keepass</string> <string name="database_file_name_default" translatable="false">keepass</string>
<string name="database_file_extension_default" translatable="false">.kdbx</string> <string name="database_file_extension_default" translatable="false">.kdbx</string>
<string name="database_default_name" translatable="false">KeePassDX Database</string> <string name="database_default_name" translatable="false">KeePassDX Database</string>
<string name="app_properties_file_name" translatable="false">keepassdx.properties</string>
<!-- <!--
******************* *******************
@@ -105,6 +106,9 @@
<string name="temp_advanced_unlock_timeout_default" translatable="false">36000000</string> <string name="temp_advanced_unlock_timeout_default" translatable="false">36000000</string>
<string name="biometric_delete_all_key_key" translatable="false">biometric_delete_all_key_key</string> <string name="biometric_delete_all_key_key" translatable="false">biometric_delete_all_key_key</string>
<string name="import_app_properties_key" translatable="false">import_app_properties_key</string>
<string name="export_app_properties_key" translatable="false">export_app_properties_key</string>
<!-- Form Filling Settings --> <!-- Form Filling Settings -->
<string name="settings_form_filling_key" translatable="false">settings_form_filling_key</string> <string name="settings_form_filling_key" translatable="false">settings_form_filling_key</string>
@@ -284,31 +288,6 @@
<string name="timeout_backup_key" translatable="false">timeout_backup_key</string> <string name="timeout_backup_key" translatable="false">timeout_backup_key</string>
<string name="timeout_default" translatable="false">300000</string> <string name="timeout_default" translatable="false">300000</string>
<string name="timeout_screen_off" translatable="false">1500</string> <string name="timeout_screen_off" translatable="false">1500</string>
<string-array name="timeout_values">
<item translatable="false">5000</item>
<item translatable="false">10000</item>
<item translatable="false">20000</item>
<item translatable="false">30000</item>
<item translatable="false">60000</item>
<item translatable="false">300000</item>
<item translatable="false">900000</item>
<item translatable="false">1800000</item>
<item translatable="false">-1</item>
</string-array>
<string-array name="large_timeout_values">
<item translatable="false">300000</item>
<item translatable="false">900000</item>
<item translatable="false">1800000</item>
<item translatable="false">3600000</item>
<item translatable="false">7200000</item>
<item translatable="false">18000000</item>
<item translatable="false">36000000</item>
<item translatable="false">86400000</item>
<item translatable="false">172800000</item>
<item translatable="false">604800000</item>
<item translatable="false">2592000000</item>
<item translatable="false">-1</item>
</string-array>
<!-- Text Size --> <!-- Text Size -->
<dimen name="list_icon_size_default" translatable="false">32dp</dimen> <dimen name="list_icon_size_default" translatable="false">32dp</dimen>

View File

@@ -31,7 +31,7 @@
<string name="encryption">Encryption</string> <string name="encryption">Encryption</string>
<string name="encryption_algorithm">Encryption algorithm</string> <string name="encryption_algorithm">Encryption algorithm</string>
<string name="key_derivation_function">Key derivation function</string> <string name="key_derivation_function">Key derivation function</string>
<string name="app_timeout">App timeout</string> <string name="app_timeout">Timeout</string>
<string name="app_timeout_summary">Idle time before locking the database</string> <string name="app_timeout_summary">Idle time before locking the database</string>
<string name="application">App</string> <string name="application">App</string>
<string name="brackets">Brackets</string> <string name="brackets">Brackets</string>
@@ -239,6 +239,15 @@
<string name="show_recent_files_summary">Show locations of recent databases</string> <string name="show_recent_files_summary">Show locations of recent databases</string>
<string name="hide_broken_locations_title">Hide broken database links</string> <string name="hide_broken_locations_title">Hide broken database links</string>
<string name="hide_broken_locations_summary">Hide broken links in the list of recent databases</string> <string name="hide_broken_locations_summary">Hide broken links in the list of recent databases</string>
<string name="import_app_properties_title">Import app properties</string>
<string name="import_app_properties_summary">Select a file to import app properties</string>
<string name="export_app_properties_title">Export app properties</string>
<string name="export_app_properties_summary">Create a file to export app properties</string>
<string name="description_app_properties">KeePassDX properties to manage app settings</string>
<string name="success_import_app_properties">App properties imported</string>
<string name="error_import_app_properties">Error during app properties importation</string>
<string name="success_export_app_properties">App properties exported</string>
<string name="error_export_app_properties">Error during app properties exportation</string>
<string name="root">Root</string> <string name="root">Root</string>
<string name="encryption_explanation">Database encryption algorithm used for all data.</string> <string name="encryption_explanation">Database encryption algorithm used for all data.</string>
<string name="kdf_explanation">To generate the key for the encryption algorithm, the master key is transformed using a randomly salted key derivation function.</string> <string name="kdf_explanation">To generate the key for the encryption algorithm, the master key is transformed using a randomly salted key derivation function.</string>
@@ -307,6 +316,7 @@
<string name="advanced_unlock_prompt_not_initialized">Unable to initialize advanced unlock prompt.</string> <string name="advanced_unlock_prompt_not_initialized">Unable to initialize advanced unlock prompt.</string>
<string name="credential_before_click_advanced_unlock_button">Type in the password, and then click this button.</string> <string name="credential_before_click_advanced_unlock_button">Type in the password, and then click this button.</string>
<string name="database_history">History</string> <string name="database_history">History</string>
<string name="properties">Properties</string>
<string name="menu_appearance_settings">Appearance</string> <string name="menu_appearance_settings">Appearance</string>
<string name="biometric">Biometric</string> <string name="biometric">Biometric</string>
<string name="device_credential">Device credential</string> <string name="device_credential">Device credential</string>
@@ -530,7 +540,6 @@
<string name="unit_kibibyte">KiB</string> <string name="unit_kibibyte">KiB</string>
<string name="unit_mebibyte">MiB</string> <string name="unit_mebibyte">MiB</string>
<string name="unit_gibibyte">GiB</string> <string name="unit_gibibyte">GiB</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5 seconds</item> <item>5 seconds</item>
<item>10 seconds</item> <item>10 seconds</item>

View File

@@ -47,13 +47,11 @@
android:title="@string/temp_advanced_unlock_enable_title" android:title="@string/temp_advanced_unlock_enable_title"
android:summary="@string/temp_advanced_unlock_enable_summary" android:summary="@string/temp_advanced_unlock_enable_summary"
android:defaultValue="@bool/temp_advanced_unlock_enable_default"/> android:defaultValue="@bool/temp_advanced_unlock_enable_default"/>
<ListPreference <com.kunzisoft.keepass.settings.preference.DurationDialogPreference
android:key="@string/temp_advanced_unlock_timeout_key" android:key="@string/temp_advanced_unlock_timeout_key"
android:title="@string/temp_advanced_unlock_timeout_title" android:title="@string/temp_advanced_unlock_timeout_title"
android:summary="@string/temp_advanced_unlock_timeout_summary" android:summary="@string/temp_advanced_unlock_timeout_summary"
android:dependency="@string/temp_advanced_unlock_enable_key" android:dependency="@string/temp_advanced_unlock_enable_key"
android:entries="@array/large_timeout_options"
android:entryValues="@array/large_timeout_values"
android:dialogTitle="@string/advanced_unlock_timeout" android:dialogTitle="@string/advanced_unlock_timeout"
android:defaultValue="@string/temp_advanced_unlock_timeout_default"/> android:defaultValue="@string/temp_advanced_unlock_timeout_default"/>
<Preference <Preference

View File

@@ -69,12 +69,10 @@
<PreferenceCategory <PreferenceCategory
android:title="@string/lock"> android:title="@string/lock">
<ListPreference <com.kunzisoft.keepass.settings.preference.DurationDialogPreference
android:key="@string/app_timeout_key" android:key="@string/app_timeout_key"
android:title="@string/app_timeout" android:title="@string/app_timeout"
android:summary="@string/app_timeout_summary" android:summary="@string/app_timeout_summary"
android:entries="@array/timeout_options"
android:entryValues="@array/timeout_values"
android:dialogTitle="@string/app_timeout" android:dialogTitle="@string/app_timeout"
android:defaultValue="@string/timeout_default"/> android:defaultValue="@string/timeout_default"/>
<SwitchPreference <SwitchPreference
@@ -150,4 +148,18 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
android:title="@string/properties">
<Preference
android:key="@string/import_app_properties_key"
android:title="@string/import_app_properties_title"
android:summary="@string/import_app_properties_summary"/>
<Preference
android:key="@string/export_app_properties_key"
android:title="@string/export_app_properties_title"
android:summary="@string/export_app_properties_summary"/>
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -66,12 +66,10 @@
android:summary="@string/clear_clipboard_notification_summary" android:summary="@string/clear_clipboard_notification_summary"
android:dependency="@string/clipboard_notifications_key" android:dependency="@string/clipboard_notifications_key"
android:defaultValue="@bool/clear_clipboard_notification_default"/> android:defaultValue="@bool/clear_clipboard_notification_default"/>
<ListPreference <com.kunzisoft.keepass.settings.preference.DurationDialogPreference
android:key="@string/clipboard_timeout_key" android:key="@string/clipboard_timeout_key"
android:title="@string/clipboard_timeout" android:title="@string/clipboard_timeout"
android:summary="@string/clipboard_timeout_summary" android:summary="@string/clipboard_timeout_summary"
android:entries="@array/timeout_options"
android:entryValues="@array/timeout_values"
android:dialogTitle="@string/clipboard_timeout" android:dialogTitle="@string/clipboard_timeout"
android:defaultValue="@string/clipboard_timeout_default"/> android:defaultValue="@string/clipboard_timeout_default"/>
<SwitchPreference <SwitchPreference

View File

@@ -31,12 +31,10 @@
android:summary="@string/keyboard_notification_entry_clear_close_summary" android:summary="@string/keyboard_notification_entry_clear_close_summary"
android:dependency="@string/keyboard_notification_entry_key" android:dependency="@string/keyboard_notification_entry_key"
android:defaultValue="@bool/keyboard_notification_entry_clear_close_default"/> android:defaultValue="@bool/keyboard_notification_entry_clear_close_default"/>
<ListPreference <com.kunzisoft.keepass.settings.preference.DurationDialogPreference
android:key="@string/keyboard_entry_timeout_key" android:key="@string/keyboard_entry_timeout_key"
android:title="@string/keyboard_entry_timeout_title" android:title="@string/keyboard_entry_timeout_title"
android:summary="@string/keyboard_entry_timeout_summary" android:summary="@string/keyboard_entry_timeout_summary"
android:entries="@array/timeout_options"
android:entryValues="@array/timeout_values"
android:dialogTitle="@string/keyboard_entry_timeout_title" android:dialogTitle="@string/keyboard_entry_timeout_title"
android:defaultValue="@string/keyboard_entry_timeout_default" android:defaultValue="@string/keyboard_entry_timeout_default"
android:dependency="@string/keyboard_notification_entry_clear_close_key"/> android:dependency="@string/keyboard_notification_entry_clear_close_key"/>

View File

@@ -38,8 +38,12 @@ object CipherFactory {
} }
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
fun getTwofish(opmode: Int, key: ByteArray, IV: ByteArray): Cipher { fun getTwofish(opmode: Int, key: ByteArray, IV: ByteArray, forceCompatibility: Boolean = false): Cipher {
val cipher: Cipher = Cipher.getInstance("Twofish/CBC/PKCS7PADDING") val cipher: Cipher = if (forceCompatibility) {
Cipher.getInstance("Twofish/CBC/NoPadding")
} else {
Cipher.getInstance("Twofish/CBC/PKCS7PADDING")
}
cipher.init(opmode, SecretKeySpec(key, "AES"), IvParameterSpec(IV)) cipher.init(opmode, SecretKeySpec(key, "AES"), IvParameterSpec(IV))
return cipher return cipher
} }

View File

@@ -0,0 +1 @@
* Fix small bugs #948

View File

@@ -0,0 +1,3 @@
* Import / Export app properties #839
* Force twofish padding compatibility #955
* Better timeout preference #579

View File

@@ -0,0 +1 @@
* Correction de petits bugs #948

View File

@@ -0,0 +1,3 @@
* Import / Export des propriétés de l'application #839
* Force la compatibilité du bourrage twofish #955
* Meilleure préférence d'expiration #579