mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'master' into master
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 -> {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
} ?: ""
|
} ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
5
app/src/main/res/drawable/ic_day_white_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_day_white_24dp.xml
Normal 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>
|
||||||
149
app/src/main/res/layout/pref_dialog_duration.xml
Normal file
149
app/src/main/res/layout/pref_dialog_duration.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -258,17 +258,6 @@
|
|||||||
<string name="html_text_dev_feature_upgrade">N’oubliez pas de garder votre application à jour en installant les nouvelles versions.</string>
|
<string name="html_text_dev_feature_upgrade">N’oubliez 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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
1
fastlane/metadata/android/en-US/changelogs/69.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/69.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* Fix small bugs #948
|
||||||
3
fastlane/metadata/android/en-US/changelogs/71.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/71.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
* Import / Export app properties #839
|
||||||
|
* Force twofish padding compatibility #955
|
||||||
|
* Better timeout preference #579
|
||||||
1
fastlane/metadata/android/fr-FR/changelogs/69.txt
Normal file
1
fastlane/metadata/android/fr-FR/changelogs/69.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* Correction de petits bugs #948
|
||||||
3
fastlane/metadata/android/fr-FR/changelogs/71.txt
Normal file
3
fastlane/metadata/android/fr-FR/changelogs/71.txt
Normal 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
|
||||||
Reference in New Issue
Block a user