Merge branch 'feature/DatabaseProvider' into develop

This commit is contained in:
J-Jamet
2021-08-20 19:49:20 +02:00
111 changed files with 4523 additions and 3478 deletions

View File

@@ -2,6 +2,7 @@ KeePassDX(3.0.0)
* Add / Manage dynamic templates #191
* Manually select RecycleBin group and Templates group #191
* Small changes #1035
* Fix timeout in dialogs #716
KeePassDX(2.10.5)
* Increase the saving speed of database #1028

View File

@@ -112,8 +112,7 @@
<activity
android:name="com.kunzisoft.keepass.activities.GroupActivity"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustPan"
android:launchMode="singleTask">
android:windowSoftInputMode="adjustPan">
<meta-data
android:name="android.app.default_searchable"
android:value="com.kunzisoft.keepass.search.SearchResults"
@@ -209,7 +208,7 @@
</intent-filter>
</service>
<service
android:name="com.kunzisoft.keepass.magikeyboard.MagikIME"
android:name="com.kunzisoft.keepass.magikeyboard.MagikeyboardService"
android:label="@string/keyboard_label"
android:permission="android.permission.BIND_INPUT_METHOD" >
<meta-data android:name="android.view.im"

View File

@@ -25,14 +25,13 @@ import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST
import com.kunzisoft.keepass.autofill.KeeAutofillService
@@ -44,13 +43,18 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION
@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() {
class AutofillLauncherActivity : DatabaseModeActivity() {
private var mDatabase: Database? = null
override fun applyCustomStyle(): Boolean {
return false
}
override fun onCreate(savedInstanceState: Bundle?) {
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
mDatabase = Database.getInstance()
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
// Retrieve selection mode
EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
@@ -64,9 +68,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database ->
launchSelection(database, searchInfo)
}
launchSelection(database, searchInfo)
}
}
SpecialMode.REGISTRATION -> {
@@ -75,9 +77,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
val searchInfo = SearchInfo(registerInfo?.searchInfo)
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database ->
launchRegistration(database, searchInfo, registerInfo)
}
launchRegistration(database, searchInfo, registerInfo)
}
}
else -> {
@@ -87,11 +87,9 @@ class AutofillLauncherActivity : AppCompatActivity() {
}
}
}
super.onCreate(savedInstanceState)
}
private fun launchSelection(database: Database,
private fun launchSelection(database: Database?,
searchInfo: SearchInfo) {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val autofillComponent = AutofillHelper.retrieveAutofillComponent(intent)
@@ -111,18 +109,18 @@ class AutofillLauncherActivity : AppCompatActivity() {
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ items ->
{ openedDatabase, items ->
// Items found
AutofillHelper.buildResponseAndSetResult(this, database, items)
AutofillHelper.buildResponseAndSetResult(this, openedDatabase, items)
finish()
},
{
{ openedDatabase ->
// Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this,
database.isReadOnly,
autofillComponent,
searchInfo,
false)
openedDatabase,
autofillComponent,
searchInfo,
false)
},
{
// If database not open
@@ -134,7 +132,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
}
}
private fun launchRegistration(database: Database,
private fun launchRegistration(database: Database?,
searchInfo: SearchInfo,
registerInfo: RegisterInfo?) {
if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,
@@ -144,24 +142,26 @@ class AutofillLauncherActivity : AppCompatActivity() {
showBlockRestartMessage()
setResult(Activity.RESULT_CANCELED)
} else {
val readOnly = database.isReadOnly
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ _ ->
{ openedDatabase, _ ->
if (!readOnly) {
// Show the database UI to select the entry
GroupActivity.launchForRegistration(this,
registerInfo)
openedDatabase,
registerInfo)
} else {
showReadOnlySaveMessage()
}
},
{
{ openedDatabase ->
if (!readOnly) {
// Show the database UI to select the entry
GroupActivity.launchForRegistration(this,
registerInfo)
openedDatabase,
registerInfo)
} else {
showReadOnlySaveMessage()
}

View File

@@ -35,28 +35,26 @@ import android.widget.ProgressBar
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.EntryFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
@@ -66,7 +64,7 @@ import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.*
import kotlin.collections.HashMap
class EntryActivity : LockingActivity() {
class EntryActivity : DatabaseLockActivity() {
private var coordinatorLayout: CoordinatorLayout? = null
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
@@ -79,11 +77,17 @@ class EntryActivity : LockingActivity() {
private val mEntryViewModel: EntryViewModel by viewModels()
private var mMainEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1
private var mEntryIsHistory: Boolean = false
private var mUrl: String? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
private var mExternalFileHelper: ExternalFileHelper? = null
private var iconColor: Int = 0
private var mIcon: IconImage? = null
private var mIconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -108,23 +112,18 @@ class EntryActivity : LockingActivity() {
collapsingToolbarLayout?.title = " "
toolbar?.title = " "
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
// Retrieve the textColor to tint the icon
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
iconColor = taIconColor.getColor(0, Color.BLACK)
mIconColor = taIconColor.getColor(0, Color.BLACK)
taIconColor.recycle()
mReadOnly = mDatabase?.isReadOnly != false || mReadOnly
// Get Entry from UUID
try {
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { entryId ->
mMainEntryId = entryId
intent.removeExtra(KEY_ENTRY)
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
mHistoryPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
intent.removeExtra(KEY_ENTRY_HISTORY_POSITION)
mEntryViewModel.loadEntry(entryId, historyPosition)
}
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")
@@ -139,6 +138,30 @@ class EntryActivity : LockingActivity() {
lockAndExit()
}
mEntryViewModel.mainEntryId.observe(this) { mainEntryId ->
this.mMainEntryId = mainEntryId
invalidateOptionsMenu()
}
mEntryViewModel.historyPosition.observe(this) { historyPosition ->
this.mHistoryPosition = historyPosition
val entryIsHistory = historyPosition > -1
this.mEntryIsHistory = entryIsHistory
// Assign history dedicated view
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
if (entryIsHistory) {
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
taColorAccent.recycle()
}
invalidateOptionsMenu()
}
mEntryViewModel.url.observe(this) { url ->
this.mUrl = url
invalidateOptionsMenu()
}
mEntryViewModel.entryInfo.observe(this) { entryInfo ->
// Manage entry copy to start notification if allowed (at the first start)
if (savedInstanceState == null) {
@@ -146,13 +169,14 @@ class EntryActivity : LockingActivity() {
ClipboardEntryNotificationService.launchNotificationIfAllowed(this, entryInfo)
// Manage entry to populate Magikeyboard and launch keyboard notification if allowed
if (PreferencesUtil.isKeyboardEntrySelectionEnable(this)) {
MagikIME.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
}
}
// Assign title icon
mIcon = entryInfo.icon
titleIconView?.let { iconView ->
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, iconColor)
mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, mIconColor)
}
// Assign title text
@@ -166,18 +190,6 @@ class EntryActivity : LockingActivity() {
loadingView?.hideByFading()
}
mEntryViewModel.entryIsHistory.observe(this) { entryIsHistory ->
// Assign history dedicated view
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
if (entryIsHistory) {
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
taColorAccent.recycle()
}
invalidateOptionsMenu()
}
mEntryViewModel.onOtpElementUpdated.observe(this) { otpElement ->
if (otpElement == null)
entryProgress?.visibility = View.GONE
@@ -204,30 +216,55 @@ class EntryActivity : LockingActivity() {
}
mEntryViewModel.historySelected.observe(this) { historySelected ->
launch(this,
historySelected.nodeId,
historySelected.historyPosition,
mReadOnly)
}
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish()
}
ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
this.showActionErrorIfNeeded(result)
finish()
}
mDatabase?.let { database ->
launch(
this,
database,
historySelected.nodeId,
historySelected.historyPosition
)
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun viewToInvalidateTimeout(): View? {
return coordinatorLayout
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mEntryViewModel.loadEntry(mDatabase, mMainEntryId, mHistoryPosition)
// Assign title icon
mIcon?.let { icon ->
titleIconView?.let { iconView ->
mIconDrawableFactory?.assignDatabaseIcon(iconView, icon, mIconColor)
}
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish()
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
override fun onResume() {
super.onResume()
@@ -260,7 +297,7 @@ class EntryActivity : LockingActivity() {
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
// Reload the current id from database
mEntryViewModel.updateEntry()
mEntryViewModel.updateEntry(mDatabase)
}
}
@@ -283,16 +320,14 @@ class EntryActivity : LockingActivity() {
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu)
if (mEntryViewModel.getEntry()?.url?.isEmpty() != false) {
if (mUrl?.isEmpty() != false) {
menu.findItem(R.id.menu_goto_url)?.isVisible = false
}
val entryIsHistory = mEntryViewModel.getEntryIsHistory()
if (entryIsHistory && !mReadOnly) {
if (mEntryIsHistory && !mDatabaseReadOnly) {
inflater.inflate(R.menu.entry_history, menu)
}
if (entryIsHistory || mReadOnly) {
if (mEntryIsHistory || mDatabaseReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false
}
@@ -343,38 +378,42 @@ class EntryActivity : LockingActivity() {
return true
}
R.id.menu_edit -> {
mEntryViewModel.getEntry()?.let { entry ->
EntryEditActivity.launch(this@EntryActivity, entry)
mDatabase?.let { database ->
mMainEntryId?.let { entryId ->
EntryEditActivity.launchToUpdate(
this,
database,
entryId
)
}
}
return true
}
R.id.menu_goto_url -> {
mEntryViewModel.getEntry()?.url?.let { url ->
mUrl?.let { url ->
UriUtil.gotoUrl(this, url)
}
return true
}
R.id.menu_restore_entry_history -> {
mEntryViewModel.getMainEntry()?.let { mainEntry ->
mProgressDatabaseTaskProvider?.startDatabaseRestoreEntryHistory(
mainEntry,
mEntryViewModel.getEntryHistoryPosition(),
!mReadOnly && mAutoSaveEnable)
mMainEntryId?.let { mainEntryId ->
restoreEntryHistory(
mainEntryId,
mHistoryPosition)
}
}
R.id.menu_delete_entry_history -> {
mEntryViewModel.getMainEntry()?.let { mainEntry ->
mProgressDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(
mainEntry,
mEntryViewModel.getEntryHistoryPosition(),
!mReadOnly && mAutoSaveEnable)
mMainEntryId?.let { mainEntryId ->
deleteEntryHistory(
mainEntryId,
mHistoryPosition)
}
}
R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
saveDatabase()
}
R.id.menu_reload_database -> {
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
reloadDatabase()
}
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
}
@@ -384,7 +423,7 @@ class EntryActivity : LockingActivity() {
override fun finish() {
// Transit data in previous Activity after an update
Intent().apply {
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntryViewModel.getEntry())
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mMainEntryId)
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, this)
}
super.finish()
@@ -401,25 +440,38 @@ class EntryActivity : LockingActivity() {
/**
* Open standard Entry activity
*/
fun launch(activity: Activity, entry: Entry, readOnly: Boolean) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
fun launch(activity: Activity,
database: Database,
entryId: NodeId<UUID>) {
if (database.loaded) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
activity.startActivityForResult(
intent,
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE
)
}
}
}
/**
* Open history Entry activity
*/
fun launch(activity: Activity, entryId: NodeId<UUID>, historyPosition: Int, readOnly: Boolean) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
fun launch(activity: Activity,
database: Database,
entryId: NodeId<UUID>,
historyPosition: Int) {
if (database.loaded) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
activity.startActivityForResult(
intent,
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE
)
}
}
}
}

View File

@@ -46,12 +46,14 @@ import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Compani
import com.kunzisoft.keepass.activities.fragments.EntryEditFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
import com.kunzisoft.keepass.app.database.IOActionTask
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.template.*
@@ -62,10 +64,10 @@ import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil
@@ -75,7 +77,7 @@ import org.joda.time.DateTime
import java.util.*
import kotlin.collections.ArrayList
class EntryEditActivity : LockingActivity(),
class EntryEditActivity : DatabaseLockActivity(),
EntryCustomFieldDialogFragment.EntryCustomFieldListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener,
@@ -93,7 +95,18 @@ class EntryEditActivity : LockingActivity(),
private var lockView: View? = null
private var loadingView: ProgressBar? = null
private var mEntryId: NodeId<UUID>? = null
private var mParentId: NodeId<*>? = null
private var mRegisterInfo: RegisterInfo? = null
private var mSearchInfo: SearchInfo? = null
private val mEntryEditViewModel: EntryEditViewModel by viewModels()
private var mParent: Group? = null
private var mEntry: Entry? = null
private var mIsTemplate: Boolean = false
private var mAllowCustomFields = false
private var mAllowOTP = false
// To manage attachments
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -122,27 +135,26 @@ class EntryEditActivity : LockingActivity(),
validateButton = findViewById(R.id.entry_edit_validate)
loadingView = findViewById(R.id.loading)
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
val registerInfo = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
val searchInfo = EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
mRegisterInfo = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
mSearchInfo = EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
// Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
intent.removeExtra(KEY_ENTRY)
mEntryEditViewModel.initializeEntryToUpdate(entryToUpdate, registerInfo, searchInfo)
mEntryId = entryToUpdate
}
// Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent ->
intent.removeExtra(KEY_PARENT)
mEntryEditViewModel.initializeEntryToCreate(parent, registerInfo, searchInfo)
mParentId = parent
}
retrieveEntry(mDatabase)
// To retrieve attachment
mExternalFileHelper = ExternalFileHelper(this)
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
@@ -165,9 +177,15 @@ class EntryEditActivity : LockingActivity(),
mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
if (dateInstant.type == DateInstant.Type.TIME) {
selectTime(dateInstant)
// Launch the time picker
val dateTime = DateTime(dateInstant.date)
TimePickerFragment.getInstance(dateTime.hourOfDay, dateTime.minuteOfHour)
.show(supportFragmentManager, "TimePickerFragment")
} else {
selectDate(dateInstant)
// Launch the date picker
val dateTime = DateTime(dateInstant.date)
DatePickerFragment.getInstance(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth)
.show(supportFragmentManager, "DatePickerFragment")
}
}
@@ -227,7 +245,7 @@ class EntryEditActivity : LockingActivity(),
templateSelectorSpinner?.apply {
// Build template selector
if (templates.isNotEmpty()) {
adapter = TemplatesSelectorAdapter(this@EntryEditActivity, mDatabase, templates)
adapter = TemplatesSelectorAdapter(this@EntryEditActivity, mIconDrawableFactory, templates)
setSelection(templates.indexOf(defaultTemplate))
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
@@ -245,71 +263,146 @@ class EntryEditActivity : LockingActivity(),
mEntryEditViewModel.onEntrySaved.observe(this) { entrySave ->
// Open a progress dialog and save entry
entrySave.parent?.let { parent ->
mProgressDatabaseTaskProvider?.startDatabaseCreateEntry(
entrySave.newEntry,
parent,
!mReadOnly && mAutoSaveEnable
)
createEntry(entrySave.newEntry, parent)
} ?: run {
mProgressDatabaseTaskProvider?.startDatabaseUpdateEntry(
entrySave.oldEntry,
entrySave.newEntry,
!mReadOnly && mAutoSaveEnable
)
updateEntry(entrySave.oldEntry, entrySave.newEntry)
}
}
}
// Create progress dialog
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
try {
if (result.isSuccess) {
var newNodes: List<Node> = ArrayList()
result.data?.getBundle(DatabaseTaskNotificationService.NEW_NODES_KEY)?.let { newNodesBundle ->
mDatabase?.let { database ->
newNodes = DatabaseTaskNotificationService.getListNodesFromBundle(database, newNodesBundle)
}
}
if (newNodes.size == 1) {
(newNodes[0] as? Entry?)?.let { entry ->
EntrySelectionHelper.doSpecialAction(intent,
{
// Finish naturally
finishForEntryResult(actionTask, entry)
},
{
// Nothing when search retrieved
},
{
entryValidatedForSave(actionTask, entry)
},
{
entryValidatedForKeyboardSelection(actionTask, entry)
},
{ _, _ ->
entryValidatedForAutofillSelection(entry)
},
{
entryValidatedForAutofillRegistration(actionTask, entry)
}
)
}
override fun viewToInvalidateTimeout(): View? {
return coordinatorLayout
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mAllowCustomFields = database?.allowEntryCustomFields() == true
mAllowOTP = database?.allowOTP == true
retrieveEntry(database)
}
private fun retrieveEntry(database: Database?) {
database?.let {
mEntryId?.let {
IOActionTask(
{
// Create an Entry copy to modify from the database entry
mEntry = database.getEntryById(it)
// Retrieve the parent
mEntry?.let { entry ->
// If no parent, add root group as parent
if (entry.parent == null) {
entry.parent = database.rootGroup
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve entry after database action", e)
// Define if current entry is a template (in direct template group)
mIsTemplate = database.entryIsTemplate(mEntry)
},
{
mEntryEditViewModel.loadTemplateEntry(
database,
mEntry,
mIsTemplate,
mRegisterInfo,
mSearchInfo
)
}
}
ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
this.showActionErrorIfNeeded(result)
finish()
).execute()
mEntryId = null
}
mParentId?.let {
IOActionTask(
{
mParent = database.getGroupById(it)
mParent?.let { parentGroup ->
mEntry = database.createEntry()?.apply {
// Add the default icon from parent if not a folder
val parentIcon = parentGroup.icon
// Set default icon
if (parentIcon.custom.isUnknown
&& parentIcon.standard.id != IconImageStandard.FOLDER_ID
) {
icon = IconImage(parentIcon.standard)
}
if (!parentIcon.custom.isUnknown) {
icon = IconImage(parentIcon.custom)
}
// Set default username
username = database.defaultUsername
// Warning only the entry recognize is parent, parent don't yet recognize the new entry
// Useful to recognize child state (ie: entry is a template)
parent = parentGroup
}
}
mIsTemplate = database.entryIsTemplate(mEntry)
},
{
mEntryEditViewModel.loadTemplateEntry(
database,
mEntry,
mIsTemplate,
mRegisterInfo,
mSearchInfo
)
}
).execute()
mParentId = null
}
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
try {
if (result.isSuccess) {
var newNodes: List<Node> = ArrayList()
result.data?.getBundle(DatabaseTaskNotificationService.NEW_NODES_KEY)?.let { newNodesBundle ->
newNodes = DatabaseTaskNotificationService.getListNodesFromBundle(database, newNodesBundle)
}
if (newNodes.size == 1) {
(newNodes[0] as? Entry?)?.let { entry ->
EntrySelectionHelper.doSpecialAction(intent,
{
// Finish naturally
finishForEntryResult(actionTask, entry)
},
{
// Nothing when search retrieved
},
{
entryValidatedForSave(actionTask, entry)
},
{
entryValidatedForKeyboardSelection(database, actionTask, entry)
},
{ _, _ ->
entryValidatedForAutofillSelection(database, entry)
},
{
entryValidatedForAutofillRegistration(actionTask, entry)
}
)
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve entry after database action", e)
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
private fun entryValidatedForSave(actionTask: String, entry: Entry) {
@@ -317,26 +410,22 @@ class EntryEditActivity : LockingActivity(),
finishForEntryResult(actionTask, entry)
}
private fun entryValidatedForKeyboardSelection(actionTask: String, entry: Entry) {
private fun entryValidatedForKeyboardSelection(database: Database, actionTask: String, entry: Entry) {
// Populate Magikeyboard with entry
mDatabase?.let { database ->
populateKeyboardAndMoveAppToBackground(this,
entry.getEntryInfo(database),
intent)
}
populateKeyboardAndMoveAppToBackground(this,
entry.getEntryInfo(database),
intent)
onValidateSpecialMode()
// Don't keep activity history for entry edition
finishForEntryResult(actionTask, entry)
}
private fun entryValidatedForAutofillSelection(entry: Entry) {
private fun entryValidatedForAutofillSelection(database: Database, entry: Entry) {
// Build Autofill response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mDatabase?.let { database ->
AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
database,
entry.getEntryInfo(database))
}
AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
database,
entry.getEntryInfo(database))
}
onValidateSpecialMode()
}
@@ -455,7 +544,7 @@ class EntryEditActivity : LockingActivity(),
*/
private fun saveEntry() {
mAttachmentFileBinderManager?.stopUploadAllAttachments()
mEntryEditViewModel.requestEntryInfoUpdate()
mEntryEditViewModel.requestEntryInfoUpdate(mDatabase, mEntry, mParent)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -466,25 +555,22 @@ class EntryEditActivity : LockingActivity(),
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
val allowCustomField = mDatabase?.allowEntryCustomFields() == true
menu?.findItem(R.id.menu_add_field)?.apply {
isEnabled = allowCustomField
isEnabled = mAllowCustomFields
isVisible = isEnabled
}
menu?.findItem(R.id.menu_add_attachment)?.apply {
// Attachment not compatible below KitKat
isEnabled = !mEntryEditViewModel.entryIsTemplate()
isEnabled = !mIsTemplate
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
isVisible = isEnabled
}
menu?.findItem(R.id.menu_add_otp)?.apply {
val allowOTP = mDatabase?.allowOTP == true
// OTP not compatible below KitKat
isEnabled = allowOTP
&& !mEntryEditViewModel.entryIsTemplate()
isEnabled = mAllowOTP
&& !mIsTemplate
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
isVisible = isEnabled
}
@@ -513,7 +599,7 @@ class EntryEditActivity : LockingActivity(),
if (!generatePasswordEductionPerformed) {
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
val addNewFieldEducationPerformed = mDatabase?.allowEntryCustomFields() == true
val addNewFieldEducationPerformed = mAllowCustomFields
&& addNewFieldView != null
&& addNewFieldView.isVisible
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
@@ -575,25 +661,6 @@ class EntryEditActivity : LockingActivity(),
return super.onOptionsItemSelected(item)
}
// Launch the date picker
private fun selectDate(dateInstant: DateInstant) {
val dateTime = DateTime(dateInstant.date)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear - 1
val defaultDay = dateTime.dayOfMonth
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
.show(supportFragmentManager, "DatePickerFragment")
}
// Launch the time picker
private fun selectTime(dateInstant: DateInstant) {
val dateTime = DateTime(dateInstant.date)
val defaultHour = dateTime.hourOfDay
val defaultMinute = dateTime.minuteOfHour
TimePickerFragment.getInstance(defaultHour, defaultMinute)
.show(supportFragmentManager, "TimePickerFragment")
}
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) {
// To fix android 4.4 issue
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
@@ -650,7 +717,7 @@ class EntryEditActivity : LockingActivity(),
try {
val bundle = Bundle()
val intentEntry = Intent()
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry.nodeId)
intentEntry.putExtras(bundle)
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK -> {
@@ -681,59 +748,67 @@ class EntryEditActivity : LockingActivity(),
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
const val ENTRY_EDIT_FRAGMENT_TAG = "ENTRY_EDIT_FRAGMENT_TAG"
/**
* Launch EntryEditActivity to update an existing entry
*
* @param activity from activity
* @param entry Entry to update
* Launch EntryEditActivity to update an existing entry by his [entryId]
*/
fun launch(activity: Activity,
entry: Entry) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
fun launchToUpdate(activity: Activity,
database: Database,
entryId: NodeId<UUID>) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}
}
/**
* Launch EntryEditActivity to add a new entry
*
* @param activity from activity
* @param group Group who will contains new entry
* Launch EntryEditActivity to add a new entry in an existent group
*/
fun launch(activity: Activity,
group: Group) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, group.nodeId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
fun launchToCreate(activity: Activity,
database: Database,
groupId: NodeId<*>) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, groupId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}
}
fun launchForSave(context: Context,
entry: Entry,
searchInfo: SearchInfo) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)
EntrySelectionHelper.startActivityForSaveModeResult(context,
fun launchToUpdateForSave(context: Context,
database: Database,
entryId: NodeId<UUID>,
searchInfo: SearchInfo) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
EntrySelectionHelper.startActivityForSaveModeResult(
context,
intent,
searchInfo)
searchInfo
)
}
}
}
fun launchForSave(context: Context,
group: Group,
searchInfo: SearchInfo) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, group.nodeId)
EntrySelectionHelper.startActivityForSaveModeResult(context,
fun launchToCreateForSave(context: Context,
database: Database,
groupId: NodeId<*>,
searchInfo: SearchInfo) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, groupId)
EntrySelectionHelper.startActivityForSaveModeResult(
context,
intent,
searchInfo)
searchInfo
)
}
}
}
@@ -741,14 +816,19 @@ class EntryEditActivity : LockingActivity(),
* Launch EntryEditActivity to add a new entry in keyboard selection
*/
fun launchForKeyboardSelectionResult(context: Context,
group: Group,
database: Database,
groupId: NodeId<*>,
searchInfo: SearchInfo? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, group.nodeId)
EntrySelectionHelper.startActivityForKeyboardSelectionModeResult(context,
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, groupId)
EntrySelectionHelper.startActivityForKeyboardSelectionModeResult(
context,
intent,
searchInfo)
searchInfo
)
}
}
}
@@ -757,46 +837,61 @@ class EntryEditActivity : LockingActivity(),
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
database: Database,
autofillComponent: AutofillComponent,
group: Group,
groupId: NodeId<*>,
searchInfo: SearchInfo? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, group.nodeId)
AutofillHelper.startActivityForAutofillResult(activity,
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, groupId)
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
autofillComponent,
searchInfo)
searchInfo
)
}
}
}
/**
* Launch EntryEditActivity to register an updated entry (from autofill)
*/
fun launchForRegistration(context: Context,
entry: Entry,
registerInfo: RegisterInfo? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)
EntrySelectionHelper.startActivityForRegistrationModeResult(context,
fun launchToUpdateForRegistration(context: Context,
database: Database,
entryId: NodeId<UUID>,
registerInfo: RegisterInfo? = null) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
EntrySelectionHelper.startActivityForRegistrationModeResult(
context,
intent,
registerInfo)
registerInfo
)
}
}
}
/**
* Launch EntryEditActivity to register a new entry (from autofill)
*/
fun launchForRegistration(context: Context,
group: Group,
registerInfo: RegisterInfo? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, group.nodeId)
EntrySelectionHelper.startActivityForRegistrationModeResult(context,
fun launchToCreateForRegistration(context: Context,
database: Database,
groupId: NodeId<*>,
registerInfo: RegisterInfo? = null) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
val intent = Intent(context, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, groupId)
EntrySelectionHelper.startActivityForRegistrationModeResult(
context,
intent,
registerInfo)
registerInfo
)
}
}
}
}

View File

@@ -22,14 +22,13 @@ package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields
@@ -39,14 +38,18 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
* Activity to search or select entry in database,
* Commonly used with Magikeyboard
*/
class EntrySelectionLauncherActivity : AppCompatActivity() {
class EntrySelectionLauncherActivity : DatabaseModeActivity() {
private var mDatabase: Database? = null
override fun applyCustomStyle(): Boolean {
return false
}
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
var sharedWebDomain: String? = null
var otpString: String? = null
@@ -72,23 +75,19 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
else -> {}
}
// Build domain search param
val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain
this.otpString = otpString
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database ->
launch(database, searchInfo)
}
launch(database, searchInfo)
}
super.onCreate(savedInstanceState)
}
private fun launch(database: Database,
private fun launch(database: Database?,
searchInfo: SearchInfo) {
if (!searchInfo.containsOnlyNullValues()) {
@@ -96,17 +95,19 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
// If database is open
val readOnly = database.isReadOnly
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ items ->
{ openedDatabase, items ->
// Items found
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(this,
searchInfo,
false)
GroupActivity.launchForSaveResult(
this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
@@ -117,30 +118,32 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
if (items.size == 1) {
// Automatically populate keyboard
val entryPopulate = items[0]
populateKeyboardAndMoveAppToBackground(this,
populateKeyboardAndMoveAppToBackground(
this,
entryPopulate,
intent)
} else {
// Select the one we want
GroupActivity.launchForKeyboardSelectionResult(this,
readOnly,
searchInfo,
true)
openedDatabase,
searchInfo,
true)
}
} else {
GroupActivity.launchForSearchResult(this,
readOnly,
searchInfo,
true)
openedDatabase,
searchInfo,
true)
}
},
{
{ openedDatabase ->
// Show the database UI to select the entry
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(this,
searchInfo,
false)
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
@@ -149,13 +152,14 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
}
} else if (readOnly || searchShareForMagikeyboard) {
GroupActivity.launchForKeyboardSelectionResult(this,
readOnly,
searchInfo,
false)
openedDatabase,
searchInfo,
false)
} else {
GroupActivity.launchForSaveResult(this,
searchInfo,
false)
openedDatabase,
searchInfo,
false)
}
},
{
@@ -189,7 +193,7 @@ fun populateKeyboardAndMoveAppToBackground(activity: Activity,
intent: Intent,
toast: Boolean = true) {
// Populate Magikeyboard with entry
MagikIME.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
// Consume the selection mode
EntrySelectionHelper.removeModesFromIntent(intent)
activity.moveTaskToBack(true)

View File

@@ -45,12 +45,11 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
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.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.model.MainCredential
@@ -61,12 +60,13 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
import java.io.FileNotFoundException
class FileDatabaseSelectActivity : SpecialModeActivity(),
class FileDatabaseSelectActivity : DatabaseModeActivity(),
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
// Views
@@ -85,15 +85,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection)
@@ -195,36 +189,54 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
mAdapterDatabaseHistory?.setDefaultDatabase(it)
}
// Attach the dialog thread to this activity
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri ->
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
}
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
}
ACTION_DATABASE_LOAD_TASK -> {
val database = mDatabase
if (result.isSuccess
&& database?.loaded == true) {
launchGroupActivity(database)
} else {
var resultError = ""
val resultMessage = result.message
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
}
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this@FileDatabaseSelectActivity)) {
databaseFilesViewModel.loadListOfDatabases()
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database != null) {
launchGroupActivityIfLoaded(database)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri ->
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
}
GroupActivity.launch(
this@FileDatabaseSelectActivity,
database,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity)
)
}
ACTION_DATABASE_LOAD_TASK -> {
if (result.isSuccess) {
launchGroupActivityIfLoaded(database)
} else {
var resultError = ""
val resultMessage = result.message
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
}
}
}
@@ -255,13 +267,14 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
{ onLaunchActivitySpecialMode() })
}
private fun launchGroupActivity(database: Database) {
GroupActivity.launch(this,
private fun launchGroupActivityIfLoaded(database: Database) {
if (database.loaded) {
GroupActivity.launch(this,
database,
database.isReadOnly,
{ onValidateSpecialMode() },
{ onCancelSpecialMode() },
{ onLaunchActivitySpecialMode() })
}
}
override fun onValidateSpecialMode() {
@@ -301,30 +314,11 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
}
}
val database = mDatabase
if (database?.loaded == true) {
launchGroupActivity(database)
} else {
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) {
databaseFilesViewModel.loadListOfDatabases()
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
// Register progress task
mProgressDatabaseTaskProvider?.registerProgressTask()
mDatabase?.let { database ->
launchGroupActivityIfLoaded(database)
}
}
override fun onPause() {
// Unregister progress task
mProgressDatabaseTaskProvider?.unregisterProgressTask()
super.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// only to keep the current activity
@@ -334,15 +328,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
}
override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
try {
mDatabaseFileUri?.let { databaseUri ->
// Create the new database
mProgressDatabaseTaskProvider?.startDatabaseCreate(
databaseUri,
mainCredential
)
createDatabase(databaseUri, mainCredential)
}
} catch (e: Exception) {
val error = getString(R.string.error_create_database_file)

View File

@@ -36,8 +36,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
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.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
@@ -50,7 +49,7 @@ import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
import kotlinx.coroutines.*
class IconPickerActivity : LockingActivity() {
class IconPickerActivity : DatabaseLockActivity() {
private lateinit var toolbar: Toolbar
private lateinit var coordinatorLayout: CoordinatorLayout
@@ -84,11 +83,6 @@ class IconPickerActivity : LockingActivity() {
mExternalFileHelper = ExternalFileHelper(this)
uploadButton = findViewById(R.id.icon_picker_upload)
if (mDatabase?.allowCustomIcons == true) {
uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
} else {
uploadButton.visibility = View.GONE
}
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
@@ -114,9 +108,6 @@ class IconPickerActivity : LockingActivity() {
mIconImage = savedInstanceState.getParcelable(EXTRA_ICON) ?: mIconImage
}
// Focus view to reinitialize timeout
findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
iconPickerViewModel.standardIconPicked.observe(this) { iconStandard ->
mIconImage.standard = iconStandard
// Remove the custom icon if a standard one is selected
@@ -150,6 +141,24 @@ class IconPickerActivity : LockingActivity() {
}
}
override fun viewToInvalidateTimeout(): View? {
return findViewById<ViewGroup>(R.id.icon_picker_container)
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database?.allowCustomIcons == true) {
uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
} else {
uploadButton.visibility = View.GONE
}
}
private fun updateIconsSelectedViews() {
if (mIconsSelected.isEmpty()) {
mCustomIconsSelectionMode = false

View File

@@ -31,12 +31,17 @@ import android.widget.ImageView
import androidx.appcompat.widget.Toolbar
import com.igreenwood.loupe.Loupe
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import kotlin.math.max
class ImageViewerActivity : LockingActivity() {
class ImageViewerActivity : DatabaseLockActivity() {
private var imageContainerView: ViewGroup? = null
private lateinit var imageView: ImageView
private lateinit var progressView: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -48,46 +53,11 @@ class ImageViewerActivity : LockingActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
val imageContainerView: ViewGroup = findViewById(R.id.image_viewer_container)
val imageView: ImageView = findViewById(R.id.image_viewer_image)
val progressView: View = findViewById(R.id.image_viewer_progress)
imageContainerView = findViewById(R.id.image_viewer_container)
imageView = findViewById(R.id.image_viewer_image)
progressView = findViewById(R.id.image_viewer_progress)
// Approximately, to not OOM and allow a zoom
val mImagePreviewMaxWidth = max(
resources.displayMetrics.widthPixels * 2,
resources.displayMetrics.heightPixels * 2
)
try {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name
val size = attachment.binaryData.getSize()
supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
mDatabase?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
attachment.binaryData,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
}
}
} ?: finish()
} catch (e: Exception) {
Log.e(TAG, "Unable to view the binary", e)
finish()
}
Loupe.create(imageView, imageContainerView) {
Loupe.create(imageView, imageContainerView!!) {
onViewTranslateListener = object : Loupe.OnViewTranslateListener {
override fun onStart(view: ImageView) {
@@ -110,6 +80,53 @@ class ImageViewerActivity : LockingActivity() {
}
}
override fun viewToInvalidateTimeout(): View? {
return imageContainerView
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
try {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name
val size = attachment.binaryData.getSize()
supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
// Approximately, to not OOM and allow a zoom
val mImagePreviewMaxWidth = max(
resources.displayMetrics.widthPixels * 2,
resources.displayMetrics.heightPixels * 2
)
database?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
attachment.binaryData,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
}
}
} ?: finish()
} catch (e: Exception) {
Log.e(TAG, "Unable to view the binary", e)
finish()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()

View File

@@ -19,38 +19,41 @@
*/
package com.kunzisoft.keepass.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
/**
* Activity to select entry in database and populate it in Magikeyboard
*/
class MagikeyboardLauncherActivity : AppCompatActivity() {
class MagikeyboardLauncherActivity : DatabaseModeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
override fun applyCustomStyle(): Boolean {
return false
}
val database = Database.getInstance()
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
val readOnly = database.isReadOnly
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
SearchHelper.checkAutoSearchInfo(this,
database,
null,
{
// Not called
// if items found directly returns before calling this activity
},
{
// Select if not found
GroupActivity.launchForKeyboardSelectionResult(this, readOnly)
},
{
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
}
database,
null,
{ _, _ ->
// Not called
// if items found directly returns before calling this activity
},
{ openedDatabase ->
// Select if not found
GroupActivity.launchForKeyboardSelectionResult(this, openedDatabase)
},
{
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
}
)
finish()
super.onCreate(savedInstanceState)
}
}

View File

@@ -43,13 +43,12 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.helpers.*
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
@@ -63,6 +62,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
@@ -71,7 +71,7 @@ import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import java.io.FileNotFoundException
open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.BuilderListener {
open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
// Views
private var toolbar: Toolbar? = null
@@ -87,8 +87,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
private var mDatabase: Database? = null
private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
@@ -97,11 +95,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private var mExternalFileHelper: ExternalFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false
private var mReadOnly: Boolean = false
private var mForceReadOnly: Boolean = false
set(value) {
infoContainerView?.visibility = if (value) {
readOnly = true
mReadOnly = true
View.VISIBLE
} else {
View.GONE
@@ -109,15 +107,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
field = value
}
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
setContentView(R.layout.activity_password)
toolbar = findViewById(R.id.toolbar)
@@ -136,7 +130,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
mReadOnly = if (savedInstanceState != null && savedInstanceState.containsKey(KEY_READ_ONLY)) {
savedInstanceState.getBoolean(KEY_READ_ONLY)
} else {
PreferencesUtil.enableReadOnlyDatabase(this)
}
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
@@ -208,74 +206,112 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri)
}
}
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
advancedUnlockFragment?.initAdvancedUnlockMode()
override fun onResume() {
super.onResume()
if (result.isSuccess) {
mDatabaseKeyFileUri = null
clearCredentialsViews(true)
mDatabase?.let { database ->
launchGroupActivity(database)
}
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@PasswordActivity)
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
// Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@PasswordActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
when (resultException) {
is DuplicateUuidDatabaseException -> {
// Relaunch loading if we need to fix UUID
showLoadDatabaseDuplicateUuidMessage {
// Don't allow auto open prompt if lock become when UI visible
mAllowAutoOpenBiometricPrompt = if (DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true)
false
else
mAllowAutoOpenBiometricPrompt
mDatabaseFileUri?.let { databaseFileUri ->
databaseFileViewModel.loadDatabaseFile(databaseFileUri)
}
var databaseUri: Uri? = null
var mainCredential: MainCredential = MainCredential()
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
checkPermission()
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
mainCredential = resultData.getParcelable(MAIN_CREDENTIAL_KEY) ?: mainCredential
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
mDatabase?.let { database ->
launchGroupActivityIfLoaded(database)
}
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
mainCredential,
readOnly,
cipherEntity,
true)
}
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database != null) {
launchGroupActivityIfLoaded(database)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
advancedUnlockFragment?.initAdvancedUnlockMode()
if (result.isSuccess) {
launchGroupActivityIfLoaded(database)
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
when (resultException) {
is DuplicateUuidDatabaseException -> {
// Relaunch loading if we need to fix UUID
showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null
var mainCredential = MainCredential()
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
mainCredential =
resultData.getParcelable(MAIN_CREDENTIAL_KEY)
?: mainCredential
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity =
resultData.getParcelable(CIPHER_ENTITY_KEY)
}
is FileNotFoundDatabaseException -> {
// Remove this default database inaccessible
if (mDefaultDatabase) {
databaseFileViewModel.removeDefaultDatabase()
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
mainCredential,
readOnly,
cipherEntity,
true
)
}
}
}
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
is FileNotFoundDatabaseException -> {
// Remove this default database inaccessible
if (mDefaultDatabase) {
databaseFileViewModel.removeDefaultDatabase()
}
}
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
}
}
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError)
Snackbar.make(
coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG
).asError().show()
}
}
}
@@ -302,14 +338,17 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
getUriFromIntent(intent)
}
private fun launchGroupActivity(database: Database) {
GroupActivity.launch(this,
private fun launchGroupActivityIfLoaded(database: Database) {
// Check if database really loaded
if (database.loaded) {
clearCredentialsViews(true)
GroupActivity.launch(this,
database,
readOnly,
{ onValidateSpecialMode() },
{ onCancelSpecialMode() },
{ onLaunchActivitySpecialMode() }
)
)
}
}
override fun onValidateSpecialMode() {
@@ -359,41 +398,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
}
}
override fun onResume() {
super.onResume()
val database = mDatabase
if (database?.loaded == true) {
launchGroupActivity(database)
} else {
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
if (mDatabase?.loaded == true) {
clearCredentialsViews()
}
mProgressDatabaseTaskProvider?.registerProgressTask()
// Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
// Don't allow auto open prompt if lock become when UI visible
mAllowAutoOpenBiometricPrompt = if (LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true)
false
else
mAllowAutoOpenBiometricPrompt
mDatabaseFileUri?.let { databaseFileUri ->
databaseFileViewModel.loadDatabaseFile(databaseFileUri)
}
checkPermission()
}
}
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
// Define Key File text
if (mRememberKeyFile) {
@@ -417,8 +421,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} else {
// Init Biometric elements
advancedUnlockFragment?.loadDatabase(databaseFileUri,
mAllowAutoOpenBiometricPrompt
&& mProgressDatabaseTaskProvider?.isBinded() != true)
mAllowAutoOpenBiometricPrompt)
}
enableOrNotTheConfirmationButton()
@@ -439,6 +442,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
populatePasswordTextView(null)
if (clearKeyFile) {
mDatabaseKeyFileUri = null
populateKeyFileTextView(null)
}
}
@@ -468,10 +472,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
}
override fun onPause() {
mProgressDatabaseTaskProvider?.unregisterProgressTask()
// Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true
super.onPause()
@@ -482,7 +484,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
}
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
outState.putBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT, false)
super.onSaveInstanceState(outState)
}
@@ -520,7 +522,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
clearCredentialsViews()
}
if (readOnly && (
if (mReadOnly && (
mSpecialMode == SpecialMode.SAVE
|| mSpecialMode == SpecialMode.REGISTRATION)
) {
@@ -534,7 +536,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
showProgressDialogAndLoadDatabase(
databaseUri,
MainCredential(password, keyFileUri),
readOnly,
mReadOnly,
cipherDatabaseEntity,
false)
}
@@ -546,7 +548,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
mProgressDatabaseTaskProvider?.startDatabaseLoad(
loadDatabase(
databaseUri,
mainCredential,
readOnly,
@@ -585,7 +587,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
// Check permission
private fun checkPermission() {
if (Build.VERSION.SDK_INT in 23..28
&& !readOnly
&& !mReadOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
@@ -662,7 +664,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
}
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
if (readOnly) {
if (mReadOnly) {
togglePassword.setTitle(R.string.menu_file_selection_read_only)
togglePassword.setIcon(R.drawable.ic_read_only_white_24dp)
} else {
@@ -676,7 +678,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
when (item.itemId) {
android.R.id.home -> finish()
R.id.menu_open_file_read_mode_key -> {
readOnly = !readOnly
mReadOnly = !mReadOnly
changeOpenFileReadIcon(item)
}
else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
@@ -703,8 +705,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
var keyFileResult = false
mExternalFileHelper?.let {
keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data
) { uri ->
keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
if (uri != null) {
mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri)
@@ -714,9 +715,9 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
if (!keyFileResult) {
// this block if not a key file response
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK -> {
DatabaseLockActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews()
mDatabase?.clearAndClose(this)
closeDatabase()
}
Activity.RESULT_CANCELED -> {
clearCredentialsViews()
@@ -735,6 +736,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW"
private const val KEY_READ_ONLY = "KEY_READ_ONLY"
private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"

View File

@@ -32,7 +32,6 @@ import android.view.View
import android.widget.CompoundButton
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
@@ -41,7 +40,7 @@ import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
class AssignMasterKeyDialogFragment : DialogFragment() {
class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null

View File

@@ -23,12 +23,11 @@ import android.app.Dialog
import android.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
class DatabaseChangedDialogFragment : DialogFragment() {
class DatabaseChangedDialogFragment : DatabaseDialogFragment() {
var actionDatabaseListener: ActionDatabaseChangedListener? = null

View File

@@ -0,0 +1,54 @@
package com.kunzisoft.keepass.activities.dialogs
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabaseViewModel.database.observe(this) { database ->
this.mDatabase = database
resetAppTimeoutWhenViewFocusedOrChanged()
onDatabaseRetrieved(database)
}
mDatabaseViewModel.actionFinished.observe(this) { result ->
onDatabaseActionFinished(result.database, result.actionTask, result.result)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
resetAppTimeoutWhenViewFocusedOrChanged()
}
override fun onDatabaseRetrieved(database: Database?) {
// Can be overridden by a subclass
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// Can be overridden by a subclass
}
private fun resetAppTimeoutWhenViewFocusedOrChanged() {
context?.let {
dialog?.window?.decorView?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded)
}
}
}

View File

@@ -4,9 +4,8 @@ import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
class DatePickerFragment : DialogFragment() {
class DatePickerFragment : DatabaseDialogFragment() {
private var mDefaultYear: Int = 2000
private var mDefaultMonth: Int = 1

View File

@@ -20,63 +20,38 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
import com.kunzisoft.keepass.viewmodels.NodesViewModel
open class DeleteNodesDialogFragment : DialogFragment() {
class DeleteNodesDialogFragment : DatabaseDialogFragment() {
private var mNodesToDelete: List<Node> = ArrayList()
private var mListener: DeleteNodeListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DeleteNodeListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DeleteNodeListener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
protected open fun retrieveMessage(): String {
return getString(R.string.warning_permanently_delete_nodes)
}
private var mNodesToDelete: List<Node> = listOf()
private val mNodesViewModel: NodesViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val database = Database.getInstance()
mNodesViewModel.nodesToDelete.observe(this) { nodes ->
this.mNodesToDelete = nodes
}
var recycleBin = false
arguments?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(database, this)
}
} ?: savedInstanceState?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(database, savedInstanceState)
if (containsKey(RECYCLE_BIN_TAG)) {
recycleBin = this.getBoolean(RECYCLE_BIN_TAG)
}
}
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setMessage(retrieveMessage())
builder.setMessage(if (recycleBin)
getString(R.string.warning_empty_recycle_bin)
else
getString(R.string.warning_permanently_delete_nodes))
builder.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.permanentlyDeleteNodes(mNodesToDelete)
mNodesViewModel.permanentlyDeleteNodes(mNodesToDelete)
}
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
// Create the AlertDialog object and return it
@@ -85,19 +60,14 @@ open class DeleteNodesDialogFragment : DialogFragment() {
return super.onCreateDialog(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putAll(getBundleFromListNodes(mNodesToDelete))
}
interface DeleteNodeListener {
fun permanentlyDeleteNodes(nodes: List<Node>)
}
companion object {
fun getInstance(nodesToDelete: List<Node>): DeleteNodesDialogFragment {
private const val RECYCLE_BIN_TAG = "RECYCLE_BIN_TAG"
fun getInstance(recycleBin: Boolean): DeleteNodesDialogFragment {
return DeleteNodesDialogFragment().apply {
arguments = getBundleFromListNodes(nodesToDelete)
arguments = Bundle().apply {
putBoolean(RECYCLE_BIN_TAG, recycleBin)
}
}
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2020 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.dialogs
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
class EmptyRecycleBinDialogFragment : DeleteNodesDialogFragment() {
override fun retrieveMessage(): String {
return getString(R.string.warning_empty_recycle_bin)
}
companion object {
fun getInstance(nodesToDelete: List<Node>): EmptyRecycleBinDialogFragment {
return EmptyRecycleBinDialogFragment().apply {
arguments = getBundleFromListNodes(nodesToDelete)
}
}
}
}

View File

@@ -31,14 +31,13 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.security.ProtectedString
class EntryCustomFieldDialogFragment: DialogFragment() {
class EntryCustomFieldDialogFragment: DatabaseDialogFragment() {
private var oldField: Field? = null

View File

@@ -25,7 +25,6 @@ import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Field
@@ -34,7 +33,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.applyFontVisibility
class GeneratePasswordDialogFragment : DialogFragment() {
class GeneratePasswordDialogFragment : DatabaseDialogFragment() {
private var mListener: GeneratePasswordListener? = null

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.View
@@ -28,30 +27,29 @@ import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.IconPickerActivity
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.*
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.view.DateTimeEditFieldView
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import org.joda.time.DateTime
class GroupEditDialogFragment : DialogFragment() {
class GroupEditDialogFragment : DatabaseDialogFragment() {
private var mDatabase: Database? = null
private val mGroupEditViewModel: GroupEditViewModel by activityViewModels()
private var mEditGroupListener: EditGroupListener? = null
private var mEditGroupDialogAction = EditGroupDialogAction.NONE
private var mPopulateIconMethod: ((ImageView, IconImage) -> Unit)? = null
private var mEditGroupDialogAction = NONE
private var mGroupInfo = GroupInfo()
private var mGroupNamesNotAllowed: List<String>? = null
private lateinit var iconButtonView: ImageView
private var iconColor: Int = 0
private var mIconColor: Int = 0
private lateinit var nameTextLayoutView: TextInputLayout
private lateinit var nameTextView: TextView
private lateinit var notesTextLayoutView: TextInputLayout
@@ -68,22 +66,51 @@ class GroupEditDialogFragment : DialogFragment() {
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
mEditGroupListener = context as EditGroupListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + GroupEditDialogFragment::class.java.name)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mGroupEditViewModel.onIconSelected.observe(this) { iconImage ->
mGroupInfo.icon = iconImage
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
}
mGroupEditViewModel.onDateSelected.observe(this) { viewModelDate ->
// Save the date
mGroupInfo.expiryTime = DateInstant(
DateTime(mGroupInfo.expiryTime.date)
.withYear(viewModelDate.year)
.withMonthOfYear(viewModelDate.month + 1)
.withDayOfMonth(viewModelDate.day)
.toDate())
expirationView.dateTime = mGroupInfo.expiryTime
if (expirationView.dateTime.type == DateInstant.Type.DATE_TIME) {
val instantTime = DateInstant(mGroupInfo.expiryTime.date, DateInstant.Type.TIME)
// Trick to recall selection with time
mGroupEditViewModel.requestDateTimeSelection(instantTime)
}
}
mGroupEditViewModel.onTimeSelected.observe(this) { viewModelTime ->
// Save the time
mGroupInfo.expiryTime = DateInstant(
DateTime(mGroupInfo.expiryTime.date)
.withHourOfDay(viewModelTime.hours)
.withMinuteOfHour(viewModelTime.minutes)
.toDate(), mGroupInfo.expiryTime.type)
expirationView.dateTime = mGroupInfo.expiryTime
}
mGroupEditViewModel.groupNamesNotAllowed.observe(this) { namesNotAllowed ->
this.mGroupNamesNotAllowed = namesNotAllowed
}
}
override fun onDetach() {
mEditGroupListener = null
super.onDetach()
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
}
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -98,12 +125,9 @@ class GroupEditDialogFragment : DialogFragment() {
// Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
iconColor = ta.getColor(0, Color.WHITE)
mIconColor = ta.getColor(0, Color.WHITE)
ta.recycle()
// Init elements
mDatabase = Database.getInstance()
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_GROUP_INFO)) {
@@ -120,32 +144,22 @@ class GroupEditDialogFragment : DialogFragment() {
}
// populate info in views
populateInfoToViews()
expirationView.setOnDateClickListener = {
expirationView.dateTime.date.let { expiresDate ->
val dateTime = DateTime(expiresDate)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear-1
val defaultDay = dateTime.dayOfMonth
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
.show(parentFragmentManager, "DatePickerFragment")
}
populateInfoToViews(mGroupInfo)
iconButtonView.setOnClickListener { _ ->
mGroupEditViewModel.requestIconSelection(mGroupInfo.icon)
}
expirationView.setOnDateClickListener = { dateInstant ->
mGroupEditViewModel.requestDateTimeSelection(dateInstant)
}
val builder = AlertDialog.Builder(activity)
builder.setView(root)
.setPositiveButton(android.R.string.ok, null)
.setNegativeButton(android.R.string.cancel) { _, _ ->
retrieveGroupInfoFromViews()
mEditGroupListener?.cancelEditGroup(
mEditGroupDialogAction,
mGroupInfo)
// Do nothing
}
iconButtonView.setOnClickListener { _ ->
IconPickerActivity.launch(activity, mGroupInfo.icon)
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
@@ -155,40 +169,34 @@ class GroupEditDialogFragment : DialogFragment() {
super.onResume()
// To prevent auto dismiss
val d = dialog as AlertDialog?
if (d != null) {
val positiveButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button
val alertDialog = dialog as AlertDialog?
if (alertDialog != null) {
val positiveButton = alertDialog.getButton(Dialog.BUTTON_POSITIVE) as Button
positiveButton.setOnClickListener {
retrieveGroupInfoFromViews()
if (isValid()) {
mEditGroupListener?.approveEditGroup(
mEditGroupDialogAction,
mGroupInfo)
d.dismiss()
when (mEditGroupDialogAction) {
CREATION ->
mGroupEditViewModel.approveGroupCreation(mGroupInfo)
UPDATE ->
mGroupEditViewModel.approveGroupUpdate(mGroupInfo)
NONE -> {}
}
alertDialog.dismiss()
}
}
}
}
fun getExpiryTime(): DateInstant {
retrieveGroupInfoFromViews()
return mGroupInfo.expiryTime
}
fun setExpiryTime(expiryTime: DateInstant) {
mGroupInfo.expiryTime = expiryTime
populateInfoToViews()
}
private fun populateInfoToViews() {
assignIconView()
nameTextView.text = mGroupInfo.title
notesTextLayoutView.visibility = if (mGroupInfo.notes == null) View.GONE else View.VISIBLE
mGroupInfo.notes?.let {
private fun populateInfoToViews(groupInfo: GroupInfo) {
mGroupEditViewModel.selectIcon(groupInfo.icon)
nameTextView.text = groupInfo.title
notesTextLayoutView.visibility = if (groupInfo.notes == null) View.GONE else View.VISIBLE
groupInfo.notes?.let {
notesTextView.text = it
}
expirationView.activation = mGroupInfo.expires
expirationView.dateTime = mGroupInfo.expiryTime
expirationView.activation = groupInfo.expires
expirationView.dateTime = groupInfo.expiryTime
}
private fun retrieveGroupInfoFromViews() {
@@ -202,15 +210,6 @@ class GroupEditDialogFragment : DialogFragment() {
mGroupInfo.expiryTime = expirationView.dateTime
}
private fun assignIconView() {
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconButtonView, mGroupInfo.icon, iconColor)
}
fun setIcon(icon: IconImage) {
mGroupInfo.icon = icon
assignIconView()
}
override fun onSaveInstanceState(outState: Bundle) {
retrieveGroupInfoFromViews()
outState.putInt(KEY_ACTION_ID, mEditGroupDialogAction.ordinal)
@@ -219,7 +218,21 @@ class GroupEditDialogFragment : DialogFragment() {
}
private fun isValid(): Boolean {
val error = mEditGroupListener?.isValidGroupName(nameTextView.text.toString()) ?: Error(false, null)
val name = nameTextView.text.toString()
val error = when {
name.isEmpty() -> {
Error(true, R.string.error_no_name)
}
mGroupNamesNotAllowed == null -> {
Error(true, R.string.error_word_reserved)
}
mGroupNamesNotAllowed?.find { it.equals(name, ignoreCase = true) } != null -> {
Error(true, R.string.error_word_reserved)
}
else -> {
Error(false, null)
}
}
error.messageId?.let { messageId ->
nameTextLayoutView.error = getString(messageId)
} ?: kotlin.run {
@@ -230,14 +243,6 @@ class GroupEditDialogFragment : DialogFragment() {
data class Error(val isError: Boolean, val messageId: Int?)
interface EditGroupListener {
fun isValidGroupName(name: String): Error
fun approveEditGroup(action: EditGroupDialogAction,
groupInfo: GroupInfo)
fun cancelEditGroup(action: EditGroupDialogAction,
groupInfo: GroupInfo)
}
companion object {
const val TAG_CREATE_GROUP = "TAG_CREATE_GROUP"

View File

@@ -25,14 +25,13 @@ import android.net.Uri
import android.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment
/**
* Custom Dialog to confirm big file to upload
*/
class ReplaceFileDialogFragment : DialogFragment() {
class ReplaceFileDialogFragment : DatabaseDialogFragment() {
private var mActionChooseListener: ActionChooseListener? = null

View File

@@ -31,7 +31,6 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
@@ -49,7 +48,7 @@ import com.kunzisoft.keepass.otp.TokenCalculator
import com.kunzisoft.keepass.utils.UriUtil
import java.util.*
class SetOTPDialogFragment : DialogFragment() {
class SetOTPDialogFragment : DatabaseDialogFragment() {
private var mCreateOTPElementListener: CreateOtpListener? = null

View File

@@ -22,16 +22,15 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.SortNodeEnum
class SortDialogFragment : DialogFragment() {
class SortDialogFragment : DatabaseDialogFragment() {
private var mListener: SortSelectionListener? = null

View File

@@ -6,9 +6,8 @@ import android.app.TimePickerDialog
import android.content.Context
import android.os.Bundle
import android.text.format.DateFormat
import androidx.fragment.app.DialogFragment
class TimePickerFragment : DialogFragment() {
class TimePickerFragment : DatabaseDialogFragment() {
private var defaultHour: Int = 0
private var defaultMinute: Int = 0

View File

@@ -1,15 +1,51 @@
package com.kunzisoft.keepass.activities.fragments
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseFragment : StylishFragment() {
abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval {
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
protected var mDatabase: Database? = null
override fun onAttach(context: Context) {
super.onAttach(context)
mDatabase = Database.getInstance()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
if (mDatabase == null || mDatabase != database) {
this.mDatabase = database
onDatabaseRetrieved(database)
}
}
mDatabaseViewModel.actionFinished.observe(viewLifecycleOwner) { result ->
onDatabaseActionFinished(result.database, result.actionTask, result.result)
}
}
protected fun resetAppTimeoutWhenViewFocusedOrChanged(view: View?) {
context?.let {
view?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// Can be overridden by a subclass
}
protected fun buildNewBinaryAttachment(): BinaryData? {
return mDatabase?.buildNewBinaryAttachment()
}
}

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.activities.fragments
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
@@ -33,15 +32,13 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.TemplateEditView
import com.kunzisoft.keepass.view.collapse
import com.kunzisoft.keepass.view.expand
@@ -50,10 +47,6 @@ import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
class EntryEditFragment: DatabaseFragment() {
private var iconColor: Int = 0
var drawFactory: IconDrawableFactory? = null
private val mEntryEditViewModel: EntryEditViewModel by activityViewModels()
private lateinit var rootView: View
@@ -62,11 +55,20 @@ class EntryEditFragment: DatabaseFragment() {
private lateinit var attachmentsListView: RecyclerView
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
private var mAllowMultipleAttachments: Boolean = false
private var mIconColor: Int = 0
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
// Retrieve the textColor to tint the icon
val taIconColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
mIconColor = taIconColor?.getColor(0, Color.BLACK) ?: Color.BLACK
taIconColor?.recycle()
return inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_entry_edit, container, false)
}
@@ -75,11 +77,6 @@ class EntryEditFragment: DatabaseFragment() {
savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Retrieve the textColor to tint the icon
val taIconColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE
taIconColor?.recycle()
rootView = view
// Hide only the first time
if (savedInstanceState == null) {
@@ -89,12 +86,14 @@ class EntryEditFragment: DatabaseFragment() {
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), mDatabase)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
templateView.apply {
populateIconMethod = { imageView, icon ->
drawFactory?.assignDatabaseIcon(imageView, icon, iconColor)
}
setOnIconClickListener {
mEntryEditViewModel.requestIconSelection(templateView.getIcon())
}
@@ -109,20 +108,6 @@ class EntryEditFragment: DatabaseFragment() {
}
}
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsAdapter?.database = mDatabase
attachmentsAdapter?.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
attachmentsContainerView.expand(true)
}
}
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
if (savedInstanceState != null) {
val attachments: List<Attachment> =
savedInstanceState.getParcelableArrayList(ATTACHMENTS_TAG) ?: listOf()
@@ -140,10 +125,12 @@ class EntryEditFragment: DatabaseFragment() {
}
// To prevent flickering
rootView.showByFading()
// Apply timeout reset
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
}
mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
mEntryEditViewModel.saveEntryInfo(retrieveEntryInfo())
mEntryEditViewModel.saveEntryInfo(it.database, it.entry, it.parent, retrieveEntryInfo())
}
mEntryEditViewModel.onIconSelected.observe(viewLifecycleOwner) { iconImage ->
@@ -206,10 +193,11 @@ class EntryEditFragment: DatabaseFragment() {
mEntryEditViewModel.onBuildNewAttachment.observe(viewLifecycleOwner) {
val attachmentToUploadUri = it.attachmentToUploadUri
val fileName = it.fileName
mDatabase?.buildNewBinaryAttachment()?.let { binaryAttachment ->
buildNewBinaryAttachment()?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments == false
if ((!mAllowMultipleAttachments
&& containsAttachment()) ||
containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD))) {
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
@@ -224,17 +212,17 @@ class EntryEditFragment: DatabaseFragment() {
when (entryAttachmentState?.downloadState) {
AttachmentState.START -> {
putAttachment(entryAttachmentState)
getAttachmentViewPosition(entryAttachmentState) {
mEntryEditViewModel.binaryPreviewLoaded(entryAttachmentState, it)
getAttachmentViewPosition(entryAttachmentState) { attachment, position ->
mEntryEditViewModel.binaryPreviewLoaded(attachment, position)
}
}
AttachmentState.IN_PROGRESS -> {
putAttachment(entryAttachmentState)
}
AttachmentState.COMPLETE -> {
putAttachment(entryAttachmentState) {
getAttachmentViewPosition(entryAttachmentState) {
mEntryEditViewModel.binaryPreviewLoaded(entryAttachmentState, it)
putAttachment(entryAttachmentState) { entryAttachment ->
getAttachmentViewPosition(entryAttachment) { attachment, position ->
mEntryEditViewModel.binaryPreviewLoaded(attachment, position)
}
}
mEntryEditViewModel.onAttachmentAction(null)
@@ -249,16 +237,22 @@ class EntryEditFragment: DatabaseFragment() {
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
override fun onDatabaseRetrieved(database: Database?) {
drawFactory = mDatabase?.iconDrawableFactory
}
templateView.populateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
}
override fun onDetach() {
super.onDetach()
mAllowMultipleAttachments = database?.allowMultipleAttachments == true
drawFactory = null
attachmentsAdapter?.database = database
attachmentsAdapter?.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
attachmentsContainerView.expand(true)
}
}
}
private fun assignEntryInfo(entryInfo: EntryInfo?) {
@@ -305,15 +299,15 @@ class EntryEditFragment: DatabaseFragment() {
}
private fun putAttachment(attachment: EntryAttachmentState,
onPreviewLoaded: (() -> Unit)? = null) {
onPreviewLoaded: ((attachment: EntryAttachmentState) -> Unit)? = null) {
// When only one attachment is allowed
if (mDatabase?.allowMultipleAttachments == false) {
if (!mAllowMultipleAttachments) {
clearAttachments()
}
attachmentsContainerView.visibility = View.VISIBLE
attachmentsAdapter?.putItem(attachment)
attachmentsAdapter?.onBinaryPreviewLoaded = {
onPreviewLoaded?.invoke()
onPreviewLoaded?.invoke(attachment)
}
}
@@ -325,10 +319,12 @@ class EntryEditFragment: DatabaseFragment() {
attachmentsAdapter?.clear()
}
private fun getAttachmentViewPosition(attachment: EntryAttachmentState, position: (Float) -> Unit) {
private fun getAttachmentViewPosition(attachment: EntryAttachmentState,
position: (attachment: EntryAttachmentState, Float) -> Unit) {
attachmentsListView.postDelayed({
attachmentsAdapter?.indexOf(attachment)?.let { index ->
position.invoke(attachmentsContainerView.y
position.invoke(attachment,
attachmentsContainerView.y
+ attachmentsListView.y
+ (attachmentsListView.getChildAt(index)?.y
?: 0F)

View File

@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryAttachmentState
@@ -63,8 +64,6 @@ class EntryFragment: DatabaseFragment() {
context?.let { context ->
mClipboardHelper = ClipboardHelper(context)
attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
attachmentsAdapter?.database = mDatabase
}
rootView = view
@@ -79,7 +78,6 @@ class EntryFragment: DatabaseFragment() {
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
@@ -101,6 +99,7 @@ class EntryFragment: DatabaseFragment() {
assignEntryInfo(entryInfo)
// Smooth appearing
rootView.showByFading()
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
}
mEntryViewModel.onAttachmentAction.observe(viewLifecycleOwner) { entryAttachmentState ->
@@ -112,6 +111,15 @@ class EntryFragment: DatabaseFragment() {
}
}
override fun onDatabaseRetrieved(database: Database?) {
context?.let { context ->
attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
attachmentsAdapter?.database = database
}
attachmentsListView.adapter = attachmentsAdapter
}
private fun loadTemplateSettings() {
context?.let { context ->
templateView.setFirstTimeAskAllowCopyProtectedFields(PreferencesUtil.isFirstTimeAskAllowCopyProtectedFields(context))

View File

@@ -25,32 +25,40 @@ import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.appcompat.view.ActionMode
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.EntryEditActivity
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.GroupViewModel
import java.util.*
class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
private var nodeClickListener: NodeClickListener? = null
private var onScrollListener: OnScrollListener? = null
private var mNodesRecyclerView: RecyclerView? = null
var mainGroup: Group? = null
private set
private var mLayoutManager: LinearLayoutManager? = null
private var mAdapter: NodeAdapter? = null
private val mGroupViewModel: GroupViewModel by activityViewModels()
private var mCurrentGroup: Group? = null
var nodeActionSelectionMode = false
private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
@@ -61,12 +69,27 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
private var notFoundView: View? = null
private var isASearchResult: Boolean = false
private var readOnly: Boolean = false
private var specialMode: SpecialMode = SpecialMode.DEFAULT
private var mRecycleBinEnable: Boolean = false
private var mRecycleBin: Group? = null
val isEmpty: Boolean
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == SCROLL_STATE_IDLE) {
mGroupViewModel.assignPosition(getFirstVisiblePosition())
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollListener?.onScrolled(dy)
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
@@ -97,130 +120,134 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
arguments?.let { args ->
// Contains all the group in element
if (args.containsKey(GROUP_KEY)) {
mainGroup = args.getParcelable(GROUP_KEY)
}
if (args.containsKey(IS_SEARCH)) {
isASearchResult = args.getBoolean(IS_SEARCH)
}
}
override fun onDatabaseRetrieved(database: Database?) {
mRecycleBinEnable = database?.isRecycleBinEnabled == true
mRecycleBin = database?.recycleBin
contextThemed?.let { context ->
mDatabase?.let { database ->
mAdapter = NodeAdapter(context, database)
}
mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
database?.let { database ->
mAdapter = NodeAdapter(context, database).apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(database: Database, node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
} else {
// Add selected item if not already selected
listActionNodes.add(node)
}
nodeClickListener?.onNodeSelected(database, listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else {
// Add selected item if not already selected
listActionNodes.add(node)
nodeClickListener?.onNodeClick(database, node)
}
nodeClickListener?.onNodeSelected(listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else {
nodeClickListener?.onNodeClick(node)
}
}
override fun onNodeLongClick(node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
override fun onNodeLongClick(database: Database, node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
nodeClickListener?.onNodeSelected(listActionNodes)
nodeClickListener?.onNodeSelected(database, listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
}
return true
}
return true
}
})
})
}
mNodesRecyclerView?.adapter = mAdapter
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState)
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
// Too many special cases to make specific additions or deletions,
// rebuilt the list works well.
if (result.isSuccess) {
rebuildList()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
// To apply theme
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_list_nodes, container, false)
mNodesRecyclerView = rootView.findViewById(R.id.nodes_list)
notFoundView = rootView.findViewById(R.id.not_found_container)
return inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_group, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mNodesRecyclerView = view.findViewById(R.id.nodes_list)
notFoundView = view.findViewById(R.id.not_found_container)
mLayoutManager = LinearLayoutManager(context)
mNodesRecyclerView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
layoutManager = mLayoutManager
adapter = mAdapter
}
resetAppTimeoutWhenViewFocusedOrChanged(view)
onScrollListener?.let { onScrollListener ->
mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollListener.onScrolled(dy)
}
})
mGroupViewModel.group.observe(viewLifecycleOwner) {
mCurrentGroup = it.group
isASearchResult = it.group.isVirtual
rebuildList()
it.showFromPosition?.let { position ->
mNodesRecyclerView?.scrollToPosition(position)
}
}
return rootView
}
override fun onResume() {
super.onResume()
mNodesRecyclerView?.addOnScrollListener(mRecycleViewScrollListener)
activity?.intent?.let {
specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it)
}
}
// Refresh data
try {
rebuildList()
} catch (e: Exception) {
Log.e(TAG, "Unable to rebuild the list during resume")
e.printStackTrace()
}
override fun onPause() {
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
mNodesRecyclerView?.visibility = View.GONE
notFoundView?.visibility = View.VISIBLE
} else {
mNodesRecyclerView?.visibility = View.VISIBLE
notFoundView?.visibility = View.GONE
}
mNodesRecyclerView?.removeOnScrollListener(mRecycleViewScrollListener)
super.onPause()
}
fun getFirstVisiblePosition(): Int {
return mLayoutManager?.findFirstVisibleItemPosition() ?: 0
}
@Throws(IllegalArgumentException::class)
fun rebuildList() {
// Add elements to the list
mainGroup?.let { mainGroup ->
mAdapter?.apply {
private fun rebuildList() {
try {
// Add elements to the list
mCurrentGroup?.let { mainGroup ->
// Thrown an exception when sort cannot be performed
rebuildList(mainGroup)
// To visually change the elements
if (PreferencesUtil.APPEARANCE_CHANGED) {
notifyDataSetChanged()
PreferencesUtil.APPEARANCE_CHANGED = false
}
mAdapter?.rebuildList(mainGroup)
}
} catch (e:Exception) {
Log.e(TAG, "Unable to rebuild the list", e)
}
if (isASearchResult && mAdapter != null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
notFoundView?.visibility = View.VISIBLE
} else {
notFoundView?.visibility = View.GONE
}
}
@@ -236,8 +263,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
rebuildList()
} catch (e:Exception) {
Log.e(TAG, "Unable to rebuild the list with the sort")
e.printStackTrace()
Log.e(TAG, "Unable to sort the list", e)
}
}
@@ -253,7 +279,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (mDatabase?.isRecycleBinEnabled == true) {
if (mRecycleBinEnable) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
@@ -275,94 +301,92 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
}
}
fun actionNodesCallback(nodes: List<Node>,
fun actionNodesCallback(database: Database,
nodes: List<Node>,
menuListener: NodesActionMenuListener?,
actionModeCallback: ActionMode.Callback) : ActionMode.Callback {
onDestroyActionMode: (mode: ActionMode?) -> Unit) : ActionMode.Callback {
return object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
nodeActionSelectionMode = false
nodeActionPasteMode = PasteMode.UNDEFINED
return actionModeCallback.onCreateActionMode(mode, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menu?.clear()
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
} else {
nodeActionSelectionMode = true
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
mDatabase?.let { database ->
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
} else {
nodeActionSelectionMode = true
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
menu?.removeItem(R.id.menu_edit)
}
} else {
menu?.removeItem(R.id.menu_open)
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (database.isReadOnly
|| (mRecycleBinEnable && nodes[0] == mRecycleBin)) {
menu?.removeItem(R.id.menu_edit)
}
// Move
if (readOnly
|| isASearchResult) {
menu?.removeItem(R.id.menu_move)
}
// Copy (not allowed for group)
if (readOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) {
menu?.removeItem(R.id.menu_copy)
}
// Deletion
if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
} else {
menu?.removeItem(R.id.menu_open)
menu?.removeItem(R.id.menu_edit)
}
// Add the number of items selected in title
mode?.title = nodes.size.toString()
// Move
if (database.isReadOnly
|| isASearchResult) {
menu?.removeItem(R.id.menu_move)
}
// Copy (not allowed for group)
if (database.isReadOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) {
menu?.removeItem(R.id.menu_copy)
}
// Deletion
if (database.isReadOnly
|| (mRecycleBinEnable && nodes.any { it == mRecycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
}
return actionModeCallback.onPrepareActionMode(mode, menu)
// Add the number of items selected in title
mode?.title = nodes.size.toString()
return true
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
if (menuListener == null)
return false
return when (item?.itemId) {
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0])
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0])
R.id.menu_open -> menuListener.onOpenMenuClick(database, nodes[0])
R.id.menu_edit -> menuListener.onEditMenuClick(database, nodes[0])
R.id.menu_copy -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
mAdapter?.unselectActionNodes()
val returnValue = menuListener.onCopyMenuClick(nodes)
val returnValue = menuListener.onCopyMenuClick(database, nodes)
nodeActionSelectionMode = false
returnValue
}
R.id.menu_move -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
mAdapter?.unselectActionNodes()
val returnValue = menuListener.onMoveMenuClick(nodes)
val returnValue = menuListener.onMoveMenuClick(database, nodes)
nodeActionSelectionMode = false
returnValue
}
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes)
R.id.menu_delete -> menuListener.onDeleteMenuClick(database, nodes)
R.id.menu_paste -> {
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes)
val returnValue = menuListener.onPasteMenuClick(database, nodeActionPasteMode, nodes)
nodeActionPasteMode = PasteMode.UNDEFINED
nodeActionSelectionMode = false
returnValue
}
else -> actionModeCallback.onActionItemClicked(mode, item)
else -> false
}
}
@@ -372,7 +396,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
mAdapter?.unselectActionNodes()
nodeActionPasteMode = PasteMode.UNDEFINED
nodeActionSelectionMode = false
actionModeCallback.onDestroyActionMode(mode)
onDestroyActionMode(mode)
}
}
}
@@ -384,71 +408,39 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { changedNode ->
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
addNode(changedNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE)
mAdapter?.notifyDataSetChanged()
data?.getParcelableExtra<NodeId<UUID>>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let {
// Simply refresh the list
rebuildList()
// Scroll to the new entry
mDatabase?.getEntryById(it)?.let { entry ->
mAdapter?.indexOf(entry)?.let { position ->
mNodesRecyclerView?.scrollToPosition(position)
}
}
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
}
}
}
}
fun contains(node: Node): Boolean {
return mAdapter?.contains(node) ?: false
}
fun addNode(newNode: Node) {
mAdapter?.addNode(newNode)
}
fun addNodes(newNodes: List<Node>) {
mAdapter?.addNodes(newNodes)
}
fun updateNode(oldNode: Node, newNode: Node? = null) {
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
}
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
mAdapter?.updateNodes(oldNodes, newNodes)
}
fun removeNode(pwNode: Node) {
mAdapter?.removeNode(pwNode)
}
fun removeNodes(nodes: List<Node>) {
mAdapter?.removeNodes(nodes)
}
fun removeNodeAt(position: Int) {
mAdapter?.removeNodeAt(position)
}
fun removeNodesAt(positions: IntArray) {
mAdapter?.removeNodesAt(positions)
}
/**
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickListener {
fun onNodeClick(node: Node)
fun onNodeSelected(nodes: List<Node>): Boolean
fun onNodeClick(database: Database, node: Node)
fun onNodeSelected(database: Database, nodes: List<Node>): Boolean
}
/**
* Menu listener to redefine to do an action in menu
*/
interface NodesActionMenuListener {
fun onOpenMenuClick(node: Node): Boolean
fun onEditMenuClick(node: Node): Boolean
fun onCopyMenuClick(nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
fun onOpenMenuClick(database: Database, node: Node): Boolean
fun onEditMenuClick(database: Database, node: Node): Boolean
fun onCopyMenuClick(database: Database, nodes: List<Node>): Boolean
fun onMoveMenuClick(database: Database, nodes: List<Node>): Boolean
fun onDeleteMenuClick(database: Database, nodes: List<Node>): Boolean
fun onPasteMenuClick(database: Database, pasteMode: PasteMode?, nodes: List<Node>): Boolean
}
enum class PasteMode {
@@ -467,22 +459,6 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
}
companion object {
private val TAG = ListNodesFragment::class.java.name
private const val GROUP_KEY = "GROUP_KEY"
private const val IS_SEARCH = "IS_SEARCH"
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
val bundle = Bundle()
if (group != null) {
bundle.putParcelable(GROUP_KEY, group)
}
bundle.putBoolean(IS_SEARCH, isASearch)
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly)
val listNodesFragment = ListNodesFragment()
listNodesFragment.arguments = bundle
return listNodesFragment
}
private val TAG = GroupFragment::class.java.name
}
}

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.activities.fragments
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
@@ -31,8 +32,8 @@ class IconCustomFragment : IconFragment<IconImageCustom>() {
return R.layout.fragment_icon_grid
}
override fun defineIconList() {
mDatabase?.doForEachCustomIcons { customIcon, _ ->
override fun defineIconList(database: Database?) {
database?.doForEachCustomIcons { customIcon, _ ->
iconPickerAdapter.addIcon(customIcon, false)
}
}

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.activities.fragments
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
@@ -29,6 +28,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageDraw
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
import kotlinx.coroutines.CoroutineScope
@@ -47,24 +47,37 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
abstract fun retrieveMainLayoutId(): Int
abstract fun defineIconList()
abstract fun defineIconList(database: Database?)
override fun onAttach(context: Context) {
super.onAttach(context)
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(retrieveMainLayoutId(), container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Retrieve the textColor to tint the icon
val ta = contextThemed?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
val tintColor = ta?.getColor(0, Color.BLACK) ?: Color.BLACK
ta?.recycle()
iconPickerAdapter = IconPickerAdapter<T>(context, tintColor).apply {
iconDrawableFactory = mDatabase?.iconDrawableFactory
}
iconsGridView = view.findViewById(R.id.icons_grid_view)
iconPickerAdapter = IconPickerAdapter(requireContext(), tintColor)
iconPickerAdapter.iconPickerListener = this
iconsGridView.adapter = iconPickerAdapter
resetAppTimeoutWhenViewFocusedOrChanged(view)
}
override fun onDatabaseRetrieved(database: Database?) {
iconPickerAdapter.iconDrawableFactory = database?.iconDrawableFactory
CoroutineScope(Dispatchers.IO).launch {
val populateList = launch {
iconPickerAdapter.clear()
defineIconList()
defineIconList(database)
}
withContext(Dispatchers.Main) {
populateList.join()
@@ -73,21 +86,6 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
val root = inflater.inflate(retrieveMainLayoutId(), container, false)
iconsGridView = root.findViewById(R.id.icons_grid_view)
iconsGridView.adapter = iconPickerAdapter
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
iconPickerAdapter.iconPickerListener = this
}
fun onIconDeleteClicked() {
iconActionSelectionMode = false
}

View File

@@ -10,12 +10,14 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerPagerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
class IconPickerFragment : DatabaseFragment() {
private var iconPickerPagerAdapter: IconPickerPagerAdapter? = null
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private val iconPickerViewModel: IconPickerViewModel by activityViewModels()
@@ -28,17 +30,11 @@ class IconPickerFragment : DatabaseFragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.icon_picker_pager)
val tabLayout = view.findViewById<TabLayout>(R.id.icon_picker_tabs)
iconPickerPagerAdapter = IconPickerPagerAdapter(this,
if (mDatabase?.allowCustomIcons == true) 2 else 1)
viewPager.adapter = iconPickerPagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = when (position) {
1 -> getString(R.string.icon_section_custom)
else -> getString(R.string.icon_section_standard)
}
}.attach()
tabLayout = view.findViewById(R.id.icon_picker_tabs)
resetAppTimeoutWhenViewFocusedOrChanged(view)
arguments?.apply {
if (containsKey(ICON_TAB_ARG)) {
@@ -52,6 +48,18 @@ class IconPickerFragment : DatabaseFragment() {
}
}
override fun onDatabaseRetrieved(database: Database?) {
iconPickerPagerAdapter = IconPickerPagerAdapter(this,
if (database?.allowCustomIcons == true) 2 else 1)
viewPager.adapter = iconPickerPagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = when (position) {
1 -> getString(R.string.icon_section_custom)
else -> getString(R.string.icon_section_standard)
}
}.attach()
}
enum class IconTab {
STANDARD, CUSTOM
}

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.activities.fragments
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
@@ -29,8 +30,8 @@ class IconStandardFragment : IconFragment<IconImageStandard>() {
return R.layout.fragment_icon_grid
}
override fun defineIconList() {
mDatabase?.doForEachStandardIcons { standardIcon ->
override fun defineIconList(database: Database?) {
database?.doForEachStandardIcons { standardIcon ->
iconPickerAdapter.addIcon(standardIcon, false)
}
}

View File

@@ -1,78 +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.content.Context
import android.content.Intent
import android.os.Bundle
import com.kunzisoft.keepass.settings.PreferencesUtil
object ReadOnlyHelper {
private const val READ_ONLY_KEY = "READ_ONLY_KEY"
const val READ_ONLY_DEFAULT = false
fun retrieveReadOnlyFromIntent(intent: Intent): Boolean {
return intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT)
}
fun retrieveReadOnlyFromInstanceStateOrPreference(context: Context, savedInstanceState: Bundle?): Boolean {
return if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
savedInstanceState.getBoolean(READ_ONLY_KEY)
} else {
PreferencesUtil.enableReadOnlyDatabase(context)
}
}
fun retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState: Bundle?, arguments: Bundle?): Boolean {
var readOnly = READ_ONLY_DEFAULT
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY)
} else if (arguments != null && arguments.containsKey(READ_ONLY_KEY)) {
readOnly = arguments.getBoolean(READ_ONLY_KEY)
}
return readOnly
}
fun retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState: Bundle?, intent: Intent?): Boolean {
var readOnly = READ_ONLY_DEFAULT
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY)
} else {
if (intent != null)
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT)
}
return readOnly
}
fun putReadOnlyInIntent(intent: Intent, readOnly: Boolean) {
intent.putExtra(READ_ONLY_KEY, readOnly)
}
fun putReadOnlyInBundle(bundle: Bundle, readOnly: Boolean) {
bundle.putBoolean(READ_ONLY_KEY, readOnly)
}
fun onSaveInstanceState(outState: Bundle, readOnly: Boolean) {
outState.putBoolean(READ_ONLY_KEY, readOnly)
}
}

View File

@@ -0,0 +1,90 @@
package com.kunzisoft.keepass.activities.legacy
import android.net.Uri
import android.os.Bundle
import androidx.activity.viewModels
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
import java.util.*
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
protected val mDatabaseViewModel: DatabaseViewModel by viewModels()
protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null
protected var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabaseTaskProvider = DatabaseTaskProvider(this)
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
if (mDatabase == null || mDatabase != database) {
onDatabaseRetrieved(database)
}
}
mDatabaseTaskProvider?.onActionFinish = { database, actionTask, result ->
onDatabaseActionFinished(database, actionTask, result)
}
}
override fun onDatabaseRetrieved(database: Database?) {
mDatabase = database
mDatabaseViewModel.defineDatabase(database)
// optional method implementation
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
mDatabaseViewModel.onActionFinished(database, actionTask, result)
// optional method implementation
}
fun createDatabase(databaseUri: Uri,
mainCredential: MainCredential) {
mDatabaseTaskProvider?.startDatabaseCreate(databaseUri, mainCredential)
}
fun loadDatabase(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
cipherEntity: CipherDatabaseEntity?,
fixDuplicateUuid: Boolean) {
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEntity, fixDuplicateUuid)
}
protected fun closeDatabase() {
mDatabase?.clearAndClose(this)
}
override fun reloadActivity() {
super.reloadActivity()
mDatabase?.wasReloaded = false
}
override fun onResume() {
super.onResume()
if (mDatabase?.wasReloaded == true) {
reloadActivity()
}
mDatabaseTaskProvider?.registerProgressTask()
}
override fun onPause() {
mDatabaseTaskProvider?.unregisterProgressTask()
super.onPause()
}
}

View File

@@ -0,0 +1,476 @@
/*
* 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.legacy
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.viewModels
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.NodesViewModel
import java.util.*
abstract class DatabaseLockActivity : DatabaseModeActivity(),
PasswordEncodingDialogFragment.Listener {
private val mNodesViewModel: NodesViewModel by viewModels()
protected var mTimeoutEnable: Boolean = true
private var mLockReceiver: LockReceiver? = null
private var mExitLock: Boolean = false
protected var mDatabaseReadOnly: Boolean = true
private var mAutoSaveEnable: Boolean = true
protected var mIconDrawableFactory: IconDrawableFactory? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)
) {
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
} else {
if (intent != null)
mTimeoutEnable =
intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
}
mNodesViewModel.nodesToPermanentlyDelete.observe(this) { nodes ->
deleteDatabaseNodes(nodes)
}
mDatabaseViewModel.saveDatabase.observe(this) { save ->
mDatabaseTaskProvider?.startDatabaseSave(save)
}
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
mDatabaseTaskProvider?.startDatabaseReload(fixDuplicateUuid)
}
mDatabaseViewModel.saveName.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveName(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveDescription.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveDescription(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveDefaultUsername.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveName(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveColor.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveColor(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveCompression.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveCompression(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.removeUnlinkData.observe(this) {
mDatabaseTaskProvider?.startDatabaseRemoveUnlinkedData(it)
}
mDatabaseViewModel.saveRecycleBin.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveRecycleBin(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveTemplatesGroup.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveTemplatesGroup(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveMaxHistoryItems.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveMaxHistoryItems(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveMaxHistorySize.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveEncryption.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveEncryption(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveKeyDerivation.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveKeyDerivation(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveIterations.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveIterations(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveMemoryUsage.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveParallelism.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveParallelism(it.oldValue, it.newValue, it.save)
}
mExitLock = false
}
open fun finishActivityIfDatabaseNotLoaded(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
// End activity if database not loaded
if (finishActivityIfDatabaseNotLoaded() && (database == null || !database.loaded)) {
finish()
}
// Focus view to reinitialize timeout,
// view is not necessary loaded so retry later in resume
viewToInvalidateTimeout()
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database?.loaded)
database?.let {
// check timeout
if (mTimeoutEnable) {
if (mLockReceiver == null) {
mLockReceiver = LockReceiver {
mDatabase = null
closeDatabase(database)
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
// Add onActivityForResult response
setResult(RESULT_EXIT_LOCK)
closeOptionsMenu()
finish()
}
registerLockReceiver(mLockReceiver)
}
// After the first creation
// or If simply swipe with another application
// If the time is out -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
// If onCreate already record time
if (!mExitLock)
TimeoutHelper.recordTime(this, database.loaded)
}
mDatabaseReadOnly = database.isReadOnly
mIconDrawableFactory = database.iconDrawableFactory
checkRegister()
}
}
abstract fun viewToInvalidateTimeout(): View?
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
reloadActivity()
} else {
this.showActionErrorIfNeeded(result)
finish()
}
}
}
}
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
mainCredential: MainCredential) {
assignDatabasePassword(databaseUri, mainCredential)
}
private fun assignDatabasePassword(databaseUri: Uri?,
mainCredential: MainCredential) {
if (databaseUri != null) {
mDatabaseTaskProvider?.startDatabaseAssignPassword(databaseUri, mainCredential)
}
}
fun assignPassword(mainCredential: MainCredential) {
mDatabase?.let { database ->
database.fileUri?.let { databaseUri ->
// Show the progress dialog now or after dialog confirmation
if (database.validatePasswordEncoding(mainCredential)) {
assignDatabasePassword(databaseUri, mainCredential)
} else {
PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
.show(supportFragmentManager, "passwordEncodingTag")
}
}
}
}
fun saveDatabase() {
mDatabaseTaskProvider?.startDatabaseSave(true)
}
fun reloadDatabase() {
mDatabaseTaskProvider?.startDatabaseReload(false)
}
fun createEntry(newEntry: Entry,
parent: Group) {
mDatabaseTaskProvider?.startDatabaseCreateEntry(newEntry, parent, mAutoSaveEnable)
}
fun updateEntry(oldEntry: Entry,
entryToUpdate: Entry) {
mDatabaseTaskProvider?.startDatabaseUpdateEntry(oldEntry, entryToUpdate, mAutoSaveEnable)
}
fun copyNodes(nodesToCopy: List<Node>,
newParent: Group) {
mDatabaseTaskProvider?.startDatabaseCopyNodes(nodesToCopy, newParent, mAutoSaveEnable)
}
fun moveNodes(nodesToMove: List<Node>,
newParent: Group) {
mDatabaseTaskProvider?.startDatabaseMoveNodes(nodesToMove, newParent, mAutoSaveEnable)
}
private fun eachNodeRecyclable(database: Database, nodes: List<Node>): Boolean {
return nodes.find { node ->
var cannotRecycle = true
if (node is Entry) {
cannotRecycle = !database.canRecycle(node)
} else if (node is Group) {
cannotRecycle = !database.canRecycle(node)
}
cannotRecycle
} == null
}
fun deleteNodes(nodes: List<Node>, recycleBin: Boolean = false) {
mDatabase?.let { database ->
// If recycle bin enabled, ensure it exists
if (database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources)
}
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (eachNodeRecyclable(database, nodes)) {
deleteDatabaseNodes(nodes)
}
// else open the dialog to confirm deletion
else {
DeleteNodesDialogFragment.getInstance(recycleBin)
.show(supportFragmentManager, "deleteNodesDialogFragment")
mNodesViewModel.deleteNodes(nodes)
}
}
}
private fun deleteDatabaseNodes(nodes: List<Node>) {
mDatabaseTaskProvider?.startDatabaseDeleteNodes(nodes, mAutoSaveEnable)
}
fun createGroup(parent: Group,
groupInfo: GroupInfo?) {
// Build the group
mDatabase?.createGroup()?.let { newGroup ->
groupInfo?.let { info ->
newGroup.setGroupInfo(info)
}
// Not really needed here because added in runnable but safe
newGroup.parent = parent
mDatabaseTaskProvider?.startDatabaseCreateGroup(newGroup, parent, mAutoSaveEnable)
}
}
fun updateGroup(oldGroup: Group,
groupInfo: GroupInfo) {
// If group updated save it in the database
val updateGroup = Group(oldGroup).let { updateGroup ->
updateGroup.apply {
// WARNING remove parent and children to keep memory
removeParent()
removeChildren()
this.setGroupInfo(groupInfo)
}
}
mDatabaseTaskProvider?.startDatabaseUpdateGroup(oldGroup, updateGroup, mAutoSaveEnable)
}
fun restoreEntryHistory(mainEntryId: NodeId<UUID>,
entryHistoryPosition: Int) {
mDatabaseTaskProvider
?.startDatabaseRestoreEntryHistory(mainEntryId, entryHistoryPosition, mAutoSaveEnable)
}
fun deleteEntryHistory(mainEntryId: NodeId<UUID>,
entryHistoryPosition: Int) {
mDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(mainEntryId, entryHistoryPosition, mAutoSaveEnable)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_EXIT_LOCK) {
mExitLock = true
lockAndExit()
}
}
private fun checkRegister() {
// If in ave or registration mode, don't allow read only
if ((mSpecialMode == SpecialMode.SAVE
|| mSpecialMode == SpecialMode.REGISTRATION)
&& mDatabaseReadOnly) {
Toast.makeText(this, R.string.error_registration_read_only , Toast.LENGTH_LONG).show()
EntrySelectionHelper.removeModesFromIntent(intent)
finish()
}
}
override fun onResume() {
super.onResume()
checkRegister()
// To refresh when back to normal workflow from selection workflow
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
// Invalidate timeout by touch
mDatabase?.let { database ->
viewToInvalidateTimeout()
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database.loaded)
}
invalidateOptionsMenu()
LOCKING_ACTIVITY_UI_VISIBLE = true
}
protected fun checkTimeAndLockIfTimeoutOrResetTimeout(action: (() -> Unit)? = null) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
mDatabase?.loaded == true,
action)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
super.onSaveInstanceState(outState)
}
override fun onPause() {
LOCKING_ACTIVITY_UI_VISIBLE = false
super.onPause()
if (mTimeoutEnable) {
// If the time is out during our navigation in activity -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
}
}
override fun onDestroy() {
unregisterLockReceiver(mLockReceiver)
super.onDestroy()
}
protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
}
override fun onBackPressed() {
if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
mDatabase?.loaded == true) {
super.onBackPressed()
}
} else {
super.onBackPressed()
}
}
companion object {
const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
private var LOCKING_ACTIVITY_UI_VISIBLE = false
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
}
}
/**
* To reset the app timeout when a view is focused or changed
*/
@SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoaded: Boolean?) {
// Log.d(LockingActivity.TAG, "View prepared to reset app timeout")
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
}
}
false
}
setOnFocusChangeListener { _, _ ->
// Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context, databaseLoaded)
}
}
}

View File

@@ -1,4 +1,4 @@
package com.kunzisoft.keepass.activities.selection
package com.kunzisoft.keepass.activities.legacy
import android.os.Bundle
import android.view.View
@@ -7,15 +7,14 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.helpers.TypeMode
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.SpecialModeView
/**
* Activity to manage special mode (ie: selection mode)
* Activity to manage database special mode (ie: selection mode)
*/
abstract class SpecialModeActivity : StylishActivity() {
abstract class DatabaseModeActivity : DatabaseActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
private var mTypeMode: TypeMode = TypeMode.DEFAULT

View File

@@ -0,0 +1,11 @@
package com.kunzisoft.keepass.activities.legacy
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
interface DatabaseRetrieval {
fun onDatabaseRetrieved(database: Database?)
fun onDatabaseActionFinished(database: Database,
actionTask: String,
result: ActionRunnable.Result)
}

View File

@@ -1,222 +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.lock
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
abstract class LockingActivity : SpecialModeActivity() {
protected var mTimeoutEnable: Boolean = true
private var mLockReceiver: LockReceiver? = null
private var mExitLock: Boolean = false
protected var mDatabase: Database? = null
// Force readOnly if Entry Selection mode
protected var mReadOnly: Boolean
get() {
return mReadOnlyToSave
}
set(value) {
mReadOnlyToSave = value
}
private var mReadOnlyToSave: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
super.onCreate(savedInstanceState)
if (savedInstanceState != null
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) {
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
} else {
if (intent != null)
mTimeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
}
if (mTimeoutEnable) {
mLockReceiver = LockReceiver {
closeDatabase()
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
// Add onActivityForResult response
setResult(RESULT_EXIT_LOCK)
closeOptionsMenu()
finish()
}
registerLockReceiver(mLockReceiver)
}
mExitLock = false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_EXIT_LOCK) {
mExitLock = true
if (mDatabase?.loaded == true) {
lockAndExit()
}
}
}
override fun onResume() {
super.onResume()
// If in ave or registration mode, don't allow read only
if ((mSpecialMode == SpecialMode.SAVE
|| mSpecialMode == SpecialMode.REGISTRATION)
&& mReadOnly) {
Toast.makeText(this, R.string.error_registration_read_only , Toast.LENGTH_LONG).show()
EntrySelectionHelper.removeModesFromIntent(intent)
finish()
}
mProgressDatabaseTaskProvider?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow
mReadOnlyToSave = ReadOnlyHelper.retrieveReadOnlyFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu()
if (mTimeoutEnable) {
// End activity if database not loaded
// TODO generalize
if (mDatabase?.loaded != true) {
finish()
return
}
// After the first creation
// or If simply swipe with another application
// If the time is out -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
// If onCreate already record time
mDatabase?.let { database ->
if (!mExitLock)
TimeoutHelper.recordTime(this, database)
}
}
LOCKING_ACTIVITY_UI_VISIBLE = true
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
super.onSaveInstanceState(outState)
}
override fun onPause() {
LOCKING_ACTIVITY_UI_VISIBLE = false
mProgressDatabaseTaskProvider?.unregisterProgressTask()
super.onPause()
if (mTimeoutEnable) {
// If the time is out during our navigation in activity -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
}
}
override fun onDestroy() {
unregisterLockReceiver(mLockReceiver)
super.onDestroy()
}
protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
}
override fun onBackPressed() {
if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase) {
super.onBackPressed()
}
} else {
super.onBackPressed()
}
}
companion object {
const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
private var LOCKING_ACTIVITY_UI_VISIBLE = false
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
}
}
/**
* To reset the app timeout when a view is focused or changed
*/
@SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, database: Database?) {
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
//Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context, database)
}
}
false
}
setOnFocusChangeListener { _, _ ->
//Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context, database)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context, database)
}
}
}

View File

@@ -22,10 +22,11 @@ package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.view.WindowManager
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_APPEARANCE_PREFERENCE_CHANGED
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
@@ -35,6 +36,7 @@ abstract class StylishActivity : AppCompatActivity() {
@StyleRes
private var themeId: Int = 0
private var customStyle = true
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
@@ -52,10 +54,30 @@ abstract class StylishActivity : AppCompatActivity() {
}
}
open fun applyCustomStyle(): Boolean {
return true
}
open fun finishActivityIfReloadRequested(): Boolean {
return false
}
open fun reloadActivity() {
if (!finishActivityIfReloadRequested()) {
startActivity(intent)
}
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this)
setTheme(themeId)
customStyle = applyCustomStyle()
if (customStyle) {
this.themeId = Stylish.getThemeId(this)
setTheme(themeId)
}
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
@@ -63,7 +85,10 @@ abstract class StylishActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
if (Stylish.getThemeId(this) != this.themeId) {
if ((customStyle && Stylish.getThemeId(this) != this.themeId)
|| DATABASE_APPEARANCE_PREFERENCE_CHANGED) {
DATABASE_APPEARANCE_PREFERENCE_CHANGED = false
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
this.recreate()
}

View File

@@ -107,7 +107,7 @@ class NodeAdapter (private val context: Context,
taTextColor.recycle()
}
fun assignPreferences() {
private fun assignPreferences() {
this.mPrefSizeMultiplier = PreferencesUtil.getListTextSize(context)
notifyChangeSort(
@@ -142,9 +142,19 @@ class NodeAdapter (private val context: Context,
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
return oldItem.type == newItem.type
var typeContentTheSame = true
if (oldItem is Entry && newItem is Entry) {
typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle()
&& oldItem.username == newItem.username
&& oldItem.containsAttachment() == newItem.containsAttachment()
} else if (oldItem is Group && newItem is Group) {
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
}
return typeContentTheSame
&& oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
&& oldItem.isCurrentlyExpires == newItem.isCurrentlyExpires
}
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
@@ -237,6 +247,10 @@ class NodeAdapter (private val context: Context,
mNodeSortedList.endBatchedUpdates()
}
fun indexOf(node: Node): Int {
return mNodeSortedList.indexOf(node)
}
fun notifyNodeChanged(node: Node) {
notifyItemChanged(mNodeSortedList.indexOf(node))
}
@@ -346,7 +360,7 @@ class NodeAdapter (private val context: Context,
if (mShowNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getNumberOfChildEntries(mEntryFilters)
.numberOfChildEntries
.toString()
setTextSize(mTextSizeUnit, mNumberChildrenTextDefaultDimension, mPrefSizeMultiplier)
visibility = View.VISIBLE
@@ -358,10 +372,10 @@ class NodeAdapter (private val context: Context,
// Assign click
holder.container.setOnClickListener {
mNodeClickCallback?.onNodeClick(subNode)
mNodeClickCallback?.onNodeClick(database, subNode)
}
holder.container.setOnLongClickListener {
mNodeClickCallback?.onNodeLongClick(subNode) ?: false
mNodeClickCallback?.onNodeLongClick(database, subNode) ?: false
}
}
@@ -380,8 +394,8 @@ class NodeAdapter (private val context: Context,
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickCallback {
fun onNodeClick(node: Node)
fun onNodeLongClick(node: Node): Boolean
fun onNodeClick(database: Database, node: Node)
fun onNodeLongClick(database: Database, node: Node): Boolean
}
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -12,10 +12,11 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.icons.IconDrawableFactory
class TemplatesSelectorAdapter(private val context: Context,
private val database: Database?,
private val iconDrawableFactory: IconDrawableFactory?,
private var templates: List<Template>): BaseAdapter() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
@@ -43,7 +44,7 @@ class TemplatesSelectorAdapter(private val context: Context,
}
holder.icon?.let { icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(icon, template.icon, mIconColor)
iconDrawableFactory?.assignDatabaseIcon(icon, template.icon, mIconColor)
}
holder.name?.text = TemplateField.getLocalizedName(context, template.title)

View File

@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.app
import androidx.multidex.MultiDexApplication
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.database.element.Database
class App : MultiDexApplication() {
@@ -31,9 +30,4 @@ class App : MultiDexApplication() {
Stylish.load(this)
PRNGFixes.apply()
}
override fun onTerminate() {
Database.getInstance().clearAndClose(this)
super.onTerminate()
}
}

View File

@@ -36,6 +36,7 @@ import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.CreditCard
@@ -50,22 +51,32 @@ import java.util.concurrent.atomic.AtomicBoolean
@RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() {
var applicationIdBlocklist: Set<String>? = null
var webDomainBlocklist: Set<String>? = null
var askToSaveData: Boolean = false
var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean()
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
private var mDatabase: Database? = null
private var applicationIdBlocklist: Set<String>? = null
private var webDomainBlocklist: Set<String>? = null
private var askToSaveData: Boolean = false
private var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean()
override fun onCreate() {
super.onCreate()
mDatabase = Database.getInstance()
mDatabaseTaskProvider = DatabaseTaskProvider(this)
mDatabaseTaskProvider?.registerProgressTask()
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
this.mDatabase = database
}
getPreferences()
}
override fun onDestroy() {
mDatabaseTaskProvider?.unregisterProgressTask()
super.onDestroy()
}
private fun getPreferences() {
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this)
@@ -102,20 +113,18 @@ class KeeAutofillService : AutofillService() {
} else {
null
}
mDatabase?.let { database ->
launchSelection(database,
searchInfo,
parseResult,
inlineSuggestionsRequest,
callback)
}
launchSelection(mDatabase,
searchInfo,
parseResult,
inlineSuggestionsRequest,
callback)
}
}
}
}
}
private fun launchSelection(database: Database,
private fun launchSelection(database: Database?,
searchInfo: SearchInfo,
parseResult: StructureParser.Result,
inlineSuggestionsRequest: InlineSuggestionsRequest?,
@@ -123,9 +132,9 @@ class KeeAutofillService : AutofillService() {
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ items ->
{ openedDatabase, items ->
callback.onSuccess(
AutofillHelper.buildResponse(this, database,
AutofillHelper.buildResponse(this, openedDatabase,
items, parseResult, inlineSuggestionsRequest)
)
},

View File

@@ -19,9 +19,9 @@
*/
package com.kunzisoft.keepass.database.action
import android.app.Service
import android.content.*
import android.content.Context.BIND_ABOVE_CLIENT
import android.content.Context.BIND_NOT_FOREGROUND
import android.content.Context.*
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
@@ -35,6 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Co
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
@@ -83,12 +84,23 @@ import kotlinx.coroutines.launch
import java.util.*
import kotlin.collections.ArrayList
class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
/**
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
* Useful to retrieve a database instance and sending tasks commands
*/
class DatabaseTaskProvider {
var onActionFinish: ((actionTask: String,
private var activity: FragmentActivity? = null
private var service: Service? = null
private var context: Context
var onDatabaseRetrieved: ((database: Database?) -> Unit)? = null
var onActionFinish: ((database: Database,
actionTask: String,
result: ActionRunnable.Result) -> Unit)? = null
private var intentDatabaseTask = Intent(activity.applicationContext, DatabaseTaskNotificationService::class.java)
private var intentDatabaseTask: Intent
private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null
private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null
@@ -98,17 +110,31 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
constructor(activity: FragmentActivity) {
this.activity = activity
this.context = activity
this.intentDatabaseTask = Intent(activity.applicationContext,
DatabaseTaskNotificationService::class.java)
}
constructor(service: Service) {
this.service = service
this.context = service
this.intentDatabaseTask = Intent(service.applicationContext,
DatabaseTaskNotificationService::class.java)
}
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
override fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
startDialog(titleId, messageId, warningId)
}
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
override fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
updateDialog(titleId, messageId, warningId)
}
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(actionTask, result)
override fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(database, actionTask, result)
// Remove the progress task
stopDialog()
}
@@ -123,43 +149,55 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener {
override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
newDatabaseInfo: SnapFileDatabaseInfo) {
activity.lifecycleScope.launch {
if (databaseChangedDialogFragment == null) {
databaseChangedDialogFragment = activity.supportFragmentManager
.findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
}
if (progressTaskDialogFragment == null) {
databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(
previousDatabaseInfo,
newDatabaseInfo
)
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
databaseChangedDialogFragment?.show(
activity.supportFragmentManager,
DATABASE_CHANGED_DIALOG_TAG
)
activity?.let { activity ->
activity.lifecycleScope.launch {
if (databaseChangedDialogFragment == null) {
databaseChangedDialogFragment = activity.supportFragmentManager
.findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
databaseChangedDialogFragment?.actionDatabaseListener =
mActionDatabaseListener
}
if (progressTaskDialogFragment == null) {
databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(
previousDatabaseInfo,
newDatabaseInfo
)
databaseChangedDialogFragment?.actionDatabaseListener =
mActionDatabaseListener
databaseChangedDialogFragment?.show(
activity.supportFragmentManager,
DATABASE_CHANGED_DIALOG_TAG
)
}
}
}
}
}
private var databaseListener = object: DatabaseTaskNotificationService.DatabaseListener {
override fun onDatabaseRetrieved(database: Database?) {
onDatabaseRetrieved?.invoke(database)
}
}
private fun startDialog(titleId: Int? = null,
messageId: Int? = null,
warningId: Int? = null) {
activity.lifecycleScope.launch {
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = activity.supportFragmentManager
.findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
activity?.let { activity ->
activity.lifecycleScope.launch {
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = activity.supportFragmentManager
.findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
}
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment()
progressTaskDialogFragment?.show(
activity.supportFragmentManager,
PROGRESS_TASK_DIALOG_TAG
)
}
updateDialog(titleId, messageId, warningId)
}
if (progressTaskDialogFragment == null) {
progressTaskDialogFragment = ProgressTaskDialogFragment()
progressTaskDialogFragment?.show(
activity.supportFragmentManager,
PROGRESS_TASK_DIALOG_TAG
)
}
updateDialog(titleId, messageId, warningId)
}
}
@@ -187,16 +225,19 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener)
addDatabaseListener(databaseListener)
addDatabaseFileInfoListener(databaseInfoListener)
getService().checkAction()
addActionTaskListener(actionTaskListener)
getService().checkDatabase()
getService().checkDatabaseInfo()
getService().checkAction()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null
}
}
@@ -206,7 +247,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private fun bindService() {
initServiceConnection()
serviceConnection?.let {
activity.bindService(intentDatabaseTask, it, BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
}
}
@@ -215,7 +256,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
*/
private fun unBindService() {
serviceConnection?.let {
activity.unbindService(it)
context.unbindService(it)
}
serviceConnection = null
}
@@ -243,7 +284,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
}
}
activity.registerReceiver(databaseTaskBroadcastReceiver,
context.registerReceiver(databaseTaskBroadcastReceiver,
IntentFilter().apply {
addAction(DATABASE_START_TASK_ACTION)
addAction(DATABASE_STOP_TASK_ACTION)
@@ -257,14 +298,15 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
fun unregisterProgressTask() {
stopDialog()
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null
unBindService()
try {
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
context.unregisterReceiver(databaseTaskBroadcastReceiver)
} catch (e: IllegalArgumentException) {
// If receiver not register, do nothing
}
@@ -275,7 +317,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
if (bundle != null)
intentDatabaseTask.putExtras(bundle)
intentDatabaseTask.action = actionTask
activity.startService(intentDatabaseTask)
context.startService(intentDatabaseTask)
} catch (e: Exception) {
Log.e(TAG, "Unable to perform database action", e)
Toast.makeText(activity, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
@@ -388,9 +430,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
nodesPaste.forEach { nodeVersioned ->
when (nodeVersioned.type) {
Type.GROUP -> {
(nodeVersioned as Group).nodeId?.let { groupId ->
groupsIdToCopy.add(groupId)
}
groupsIdToCopy.add((nodeVersioned as Group).nodeId)
}
Type.ENTRY -> {
entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
@@ -433,22 +473,22 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
-----------------
*/
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
fun startDatabaseRestoreEntryHistory(mainEntryId: NodeId<UUID>,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntryId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
}
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
fun startDatabaseDeleteEntryHistory(mainEntryId: NodeId<UUID>,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntryId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
@@ -639,6 +679,6 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
companion object {
private val TAG = ProgressDatabaseTaskProvider::class.java.name
private val TAG = DatabaseTaskProvider::class.java.name
}
}

View File

@@ -34,54 +34,52 @@ class UpdateEntryRunnable constructor(
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
// Keep backup of original values in case save fails
private var mBackupEntryHistory: Entry = Entry(mOldEntry)
override fun nodeAction() {
// WARNING : Re attribute parent removed in entry edit activity to save memory
mNewEntry.addParentFrom(mOldEntry)
if (mOldEntry.nodeId == mNewEntry.nodeId) {
// WARNING : Re attribute parent removed in entry edit activity to save memory
mNewEntry.addParentFrom(mOldEntry)
// Build oldest attachments
val oldEntryAttachments = mOldEntry.getAttachments(database.attachmentPool, true)
val newEntryAttachments = mNewEntry.getAttachments(database.attachmentPool, true)
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
// Not use equals because only check name
newEntryAttachments.forEach { newAttachment ->
oldEntryAttachments.forEach { oldAttachment ->
if (oldAttachment.name == newAttachment.name
&& oldAttachment.binaryData == newAttachment.binaryData)
attachmentsToRemove.remove(oldAttachment)
// Build oldest attachments
val oldEntryAttachments = mOldEntry.getAttachments(database.attachmentPool, true)
val newEntryAttachments = mNewEntry.getAttachments(database.attachmentPool, true)
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
// Not use equals because only check name
newEntryAttachments.forEach { newAttachment ->
oldEntryAttachments.forEach { oldAttachment ->
if (oldAttachment.name == newAttachment.name
&& oldAttachment.binaryData == newAttachment.binaryData
)
attachmentsToRemove.remove(oldAttachment)
}
}
}
// Update entry with new values
mOldEntry.updateWith(mNewEntry)
mNewEntry.touch(modified = true, touchParents = true)
// Update entry with new values
mNewEntry.touch(modified = true, touchParents = true)
// Create an entry history (an entry history don't have history)
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
database.removeOldestEntryHistory(mOldEntry, database.attachmentPool)
// Create an entry history (an entry history don't have history)
mNewEntry.addEntryToHistory(Entry(mOldEntry, copyHistory = false))
database.removeOldestEntryHistory(mNewEntry, database.attachmentPool)
// Only change data in index
database.updateEntry(mOldEntry)
// Only change data in index
database.updateEntry(mNewEntry)
// Remove oldest attachments
attachmentsToRemove.forEach {
database.removeAttachmentIfNotUsed(it)
// Remove oldest attachments
attachmentsToRemove.forEach {
database.removeAttachmentIfNotUsed(it)
}
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
mOldEntry.updateWith(mBackupEntryHistory)
// If we fail to save, back out changes to global structure
database.updateEntry(mOldEntry)
}
val oldNodesReturn = ArrayList<Node>()
oldNodesReturn.add(mBackupEntryHistory)
oldNodesReturn.add(mOldEntry)
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mOldEntry)
newNodesReturn.add(mNewEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}
}

View File

@@ -33,33 +33,30 @@ class UpdateGroupRunnable constructor(
afterActionNodesFinish: AfterActionNodesFinish?)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
// Keep backup of original values in case save fails
private val mBackupGroup: Group = Group(mOldGroup)
override fun nodeAction() {
// WARNING : Re attribute parent and children removed in group activity to save memory
mNewGroup.addParentFrom(mOldGroup)
mNewGroup.addChildrenFrom(mOldGroup)
if (mOldGroup.nodeId == mNewGroup.nodeId) {
// WARNING : Re attribute parent and children removed in group activity to save memory
mNewGroup.addParentFrom(mOldGroup)
mNewGroup.addChildrenFrom(mOldGroup)
// Update group with new values
mOldGroup.updateWith(mNewGroup)
mOldGroup.touch(modified = true, touchParents = true)
// Update group with new values
mNewGroup.touch(modified = true, touchParents = true)
// Only change data in index
database.updateGroup(mOldGroup)
// Only change data in index
database.updateGroup(mNewGroup)
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
// If we fail to save, back out changes to global structure
mOldGroup.updateWith(mBackupGroup)
database.updateGroup(mOldGroup)
}
val oldNodesReturn = ArrayList<Node>()
oldNodesReturn.add(mBackupGroup)
oldNodesReturn.add(mOldGroup)
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mOldGroup)
newNodesReturn.add(mNewGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}
}

View File

@@ -670,6 +670,7 @@ class Database {
searchInOther = true
searchInUUIDs = false
searchInTags = false
searchInTemplates = false
}, omitBackup, max)
}

View File

@@ -45,15 +45,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
var entryKDBX: EntryKDBX? = null
private set
fun updateWith(entry: Entry, copyHistory: Boolean = true) {
entry.entryKDB?.let {
this.entryKDB?.updateWith(it)
}
entry.entryKDBX?.let {
this.entryKDBX?.updateWith(it, copyHistory)
}
}
/**
* Use this constructor to copy an Entry with exact same values
*/
@@ -64,7 +55,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
if (entry.entryKDBX != null) {
this.entryKDBX = EntryKDBX()
}
updateWith(entry, copyHistory)
entry.entryKDB?.let {
this.entryKDB?.updateWith(it)
}
entry.entryKDBX?.let {
this.entryKDBX?.updateWith(it, copyHistory)
}
}
constructor(entry: EntryKDB) {

View File

@@ -44,14 +44,7 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
// Virtual group is used to defined a detached database group
var isVirtual = false
fun updateWith(group: Group) {
group.groupKDB?.let {
this.groupKDB?.updateWith(it)
}
group.groupKDBX?.let {
this.groupKDBX?.updateWith(it)
}
}
var numberOfChildEntries: Int = 0
/**
* Use this constructor to copy a Group
@@ -65,7 +58,12 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
if (this.groupKDBX == null)
this.groupKDBX = GroupKDBX()
}
updateWith(group)
group.groupKDB?.let {
this.groupKDB?.updateWith(it)
}
group.groupKDBX?.let {
this.groupKDBX?.updateWith(it)
}
}
constructor(group: GroupKDB) {
@@ -118,8 +116,8 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
dest.writeByte((if (isVirtual) 1 else 0).toByte())
}
override val nodeId: NodeId<*>?
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId
override val nodeId: NodeId<*>
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId ?: NodeIdUUID()
override var title: String
get() = groupKDB?.title ?: groupKDBX?.title ?: ""
@@ -270,6 +268,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
ArrayList()
}
fun getFilteredChildGroups(filters: Array<ChildFilter>): List<Group> {
return groupKDB?.getChildGroups()?.map {
Group(it).apply {
this.refreshNumberOfChildEntries(filters)
}
} ?:
groupKDBX?.getChildGroups()?.map {
Group(it).apply {
this.refreshNumberOfChildEntries(filters)
}
} ?:
ArrayList()
}
override fun getChildEntries(): List<Entry> {
return groupKDB?.getChildEntries()?.map {
Entry(it)
@@ -306,8 +318,8 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
ArrayList()
}
fun getNumberOfChildEntries(filters: Array<ChildFilter> = emptyArray()): Int {
return getFilteredChildEntries(filters).size
fun refreshNumberOfChildEntries(filters: Array<ChildFilter> = emptyArray()) {
this.numberOfChildEntries = getFilteredChildEntries(filters).size
}
/**
@@ -319,7 +331,9 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
}
fun getFilteredChildren(filters: Array<ChildFilter>): List<Node> {
return getChildGroups() + getFilteredChildEntries(filters)
val nodes = getFilteredChildGroups(filters) + getFilteredChildEntries(filters)
refreshNumberOfChildEntries(filters)
return nodes
}
override fun addChildGroup(group: Group) {
@@ -340,6 +354,24 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
}
}
override fun updateChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.updateChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.updateChildGroup(it)
}
}
override fun updateChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.updateChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.updateChildEntry(it)
}
}
override fun removeChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.removeChildGroup(it)

View File

@@ -242,13 +242,6 @@ abstract class DatabaseVersioned<
}
}
fun updateGroupIndex(group: Group) {
val groupId = group.nodeId
if (groupIndexes.containsKey(groupId)) {
groupIndexes[groupId] = group
}
}
fun removeGroupIndex(group: Group) {
this.groupIndexes.remove(group.nodeId)
}
@@ -291,13 +284,6 @@ abstract class DatabaseVersioned<
}
}
fun updateEntryIndex(entry: Entry) {
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
entryIndexes[entryId] = entry
}
}
fun removeEntryIndex(entry: Entry) {
this.entryIndexes.remove(entry.nodeId)
}
@@ -329,7 +315,11 @@ abstract class DatabaseVersioned<
}
fun updateGroup(group: Group) {
updateGroupIndex(group)
group.parent?.updateChildGroup(group)
val groupId = group.nodeId
if (groupIndexes.containsKey(groupId)) {
groupIndexes[groupId] = group
}
}
fun removeGroupFrom(groupToRemove: Group, parent: Group?) {
@@ -346,7 +336,11 @@ abstract class DatabaseVersioned<
}
open fun updateEntry(entry: Entry) {
updateEntryIndex(entry)
entry.parent?.updateChildEntry(entry)
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
entryIndexes[entryId] = entry
}
}
open fun removeEntryFrom(entryToRemove: Entry, parent: Group?) {

View File

@@ -98,6 +98,24 @@ abstract class GroupVersioned
this.childEntries.add(entry)
}
override fun updateChildGroup(group: Group) {
val index = this.childGroups.indexOfFirst { it.nodeId == group.nodeId }
if (index >= 0) {
val oldGroup = this.childGroups.removeAt(index)
group.nodeIndexInParentForNaturalOrder = oldGroup.nodeIndexInParentForNaturalOrder
this.childGroups.add(index, group)
}
}
override fun updateChildEntry(entry: Entry) {
val index = this.childEntries.indexOfFirst { it.nodeId == entry.nodeId }
if (index >= 0) {
val oldEntry = this.childEntries.removeAt(index)
entry.nodeIndexInParentForNaturalOrder = oldEntry.nodeIndexInParentForNaturalOrder
this.childEntries.add(index, entry)
}
}
override fun removeChildGroup(group: Group) {
this.childGroups.remove(group)
}

View File

@@ -32,6 +32,10 @@ interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>,
fun addChildEntry(entry: Entry)
fun updateChildGroup(group: Group)
fun updateChildEntry(entry: Entry)
fun removeChildGroup(group: Group)
fun removeChildEntry(entry: Entry)

View File

@@ -51,6 +51,8 @@ class SearchHelper {
override fun operate(node: Entry): Boolean {
if (incrementEntry >= max)
return false
if (database.entryIsTemplate(node) && !searchParameters.searchInTemplates)
return false
if (entryContainsString(database, node, searchParameters)) {
searchGroup?.addChildEntry(node)
incrementEntry++
@@ -92,12 +94,15 @@ class SearchHelper {
* Utility method to perform actions if item is found or not after an auto search in [database]
*/
fun checkAutoSearchInfo(context: Context,
database: Database,
database: Database?,
searchInfo: SearchInfo?,
onItemsFound: (items: List<EntryInfo>) -> Unit,
onItemNotFound: () -> Unit,
onItemsFound: (openedDatabase: Database,
items: List<EntryInfo>) -> Unit,
onItemNotFound: (openedDatabase: Database) -> Unit,
onDatabaseClosed: () -> Unit) {
if (database.loaded && TimeoutHelper.checkTime(context)) {
if (database == null || !database.loaded) {
onDatabaseClosed.invoke()
} else if (TimeoutHelper.checkTime(context)) {
var searchWithoutUI = false
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
&& searchInfo != null
@@ -108,18 +113,16 @@ class SearchHelper {
PreferencesUtil.omitBackup(context),
MAX_SEARCH_ENTRY
)?.let { searchGroup ->
if (searchGroup.getNumberOfChildEntries() > 0) {
if (searchGroup.numberOfChildEntries > 0) {
searchWithoutUI = true
onItemsFound.invoke(
onItemsFound.invoke(database,
searchGroup.getChildEntriesInfo(database))
}
}
}
if (!searchWithoutUI) {
onItemNotFound.invoke()
onItemNotFound.invoke(database)
}
} else {
onDatabaseClosed.invoke()
}
}

View File

@@ -34,4 +34,6 @@ class SearchParameters {
var searchInOther = true
var searchInUUIDs = false
var searchInTags = true
var searchInTemplates = false
}

View File

@@ -64,6 +64,11 @@ class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
*/
private val standardIconMap = HashMap<CacheKey, WeakReference<Drawable>>()
/**
* To load an icon pack only if current one is different
*/
private var mCurrentIconPack: IconPack? = null
/**
* Get the [SuperDrawable] [iconDraw] (from cache, or build it and add it to the cache if not exists yet), then tint it with [tintColor] if needed
*/
@@ -79,7 +84,11 @@ class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
return SuperDrawable(it)
}
}
val iconPack = IconPackChooser.getSelectedIconPack(context, this)
val iconPack = IconPackChooser.getSelectedIconPack(context)
if (mCurrentIconPack != iconPack) {
this.mCurrentIconPack = iconPack
this.clearCache()
}
iconPack?.iconToResId(icon.standard.id)?.let { iconId ->
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tintColor), iconPack.tintable())
} ?: run {

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.icons
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_APPEARANCE_PREFERENCE_CHANGED
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
@@ -90,10 +90,14 @@ object IconPackChooser {
}
fun setSelectedIconPack(iconDrawableFactory: IconDrawableFactory, iconPackIdString: String?) {
fun setSelectedIconPack(iconPackIdString: String?) {
for (iconPack in iconPackList) {
if (iconPack.id == iconPackIdString) {
iconDrawableFactory.clearCache()
// To change list items appearance
if (iconPackSelected != null
&& iconPackSelected != iconPack) {
DATABASE_APPEARANCE_PREFERENCE_CHANGED = true
}
iconPackSelected = iconPack
break
}
@@ -106,10 +110,11 @@ object IconPackChooser {
* @param context Context to build the icon pack if not already build
* @return IconPack currently in usage
*/
fun getSelectedIconPack(context: Context, iconDrawableFactory: IconDrawableFactory): IconPack? {
fun getSelectedIconPack(context: Context): IconPack? {
build(context)
if (iconPackSelected == null)
setSelectedIconPack(iconDrawableFactory, PreferencesUtil.getIconPackSelectedId(context))
if (iconPackSelected == null) {
setSelectedIconPack(PreferencesUtil.getIconPackSelectedId(context))
}
return iconPackSelected
}

View File

@@ -38,15 +38,19 @@ import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity
import com.kunzisoft.keepass.adapters.FieldsAdapter
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionListener {
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
private var mDatabase: Database? = null
private var keyboardView: KeyboardView? = null
private var entryText: TextView? = null
@@ -61,6 +65,11 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
override fun onCreate() {
super.onCreate()
mDatabaseTaskProvider = DatabaseTaskProvider(this)
mDatabaseTaskProvider?.registerProgressTask()
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
this.mDatabase = database
}
// Remove the entry and lock the keyboard when the lock signal is receive
lockReceiver = LockReceiver {
removeEntryInfo()
@@ -111,13 +120,13 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
// Remove entry info if the database is not loaded
// or if entry info timestamp is before database loaded timestamp
val database = Database.getInstance()
val databaseTime = database.loadTimestamp
val databaseTime = mDatabase?.loadTimestamp
val entryTime = entryInfoTimestamp
if (!database.loaded
|| databaseTime == null
|| entryTime == null
|| entryTime < databaseTime) {
if (mDatabase == null
|| mDatabase?.loaded != true
|| databaseTime == null
|| entryTime == null
|| entryTime < databaseTime) {
removeEntryInfo()
}
assignKeyboardView()
@@ -323,11 +332,12 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
override fun onDestroy() {
dismissCustomKeys()
unregisterLockReceiver(lockReceiver)
mDatabaseTaskProvider?.unregisterProgressTask()
super.onDestroy()
}
companion object {
private val TAG = MagikIME::class.java.name
private val TAG = MagikeyboardService::class.java.name
const val KEY_BACK_KEYBOARD = 600
const val KEY_CHANGE_KEYBOARD = 601

View File

@@ -27,6 +27,7 @@ import android.os.Binder
import android.os.IBinder
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.AttachmentState
@@ -41,6 +42,10 @@ import java.util.concurrent.CopyOnWriteArrayList
class AttachmentFileNotificationService: LockNotificationService() {
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
private var mDatabase: Database? = null
private val mPendingCommands: MutableList<Intent?> = mutableListOf()
override val notificationId: Int = 10000
private val attachmentNotificationList = CopyOnWriteArrayList<AttachmentNotification>()
@@ -49,8 +54,6 @@ class AttachmentFileNotificationService: LockNotificationService() {
private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabase: Database? = Database.getInstance()
override fun retrieveChannelId(): String {
return CHANNEL_ATTACHMENT_ID
}
@@ -82,6 +85,23 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
}
override fun onCreate() {
super.onCreate()
mDatabaseTaskProvider = DatabaseTaskProvider(this)
mDatabaseTaskProvider?.registerProgressTask()
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
this.mDatabase = database
// Execute each command in wait state
val commandIterator = this.mPendingCommands.iterator()
while (commandIterator.hasNext()) {
val command = commandIterator.next()
actionRequested(command)
commandIterator.remove()
}
}
}
interface ActionTaskListener {
fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState)
}
@@ -93,6 +113,18 @@ class AttachmentFileNotificationService: LockNotificationService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
// Wait for database to execute action request
if (mDatabase != null) {
actionRequested(intent)
} else {
mPendingCommands.add(intent)
}
return START_REDELIVER_INTENT
}
private fun actionRequested(intent: Intent?) {
val downloadFileUri: Uri? = if (intent?.hasExtra(FILE_URI_KEY) == true) {
intent.getParcelableExtra(FILE_URI_KEY)
} else null
@@ -100,16 +132,16 @@ class AttachmentFileNotificationService: LockNotificationService() {
when(intent?.action) {
ACTION_ATTACHMENT_FILE_START_UPLOAD -> {
actionStartUploadOrDownload(downloadFileUri,
intent,
StreamDirection.UPLOAD)
intent,
StreamDirection.UPLOAD)
}
ACTION_ATTACHMENT_FILE_STOP_UPLOAD -> {
actionStopUpload()
}
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
actionStartUploadOrDownload(downloadFileUri,
intent,
StreamDirection.DOWNLOAD)
intent,
StreamDirection.DOWNLOAD)
}
ACTION_ATTACHMENT_REMOVE -> {
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
@@ -130,8 +162,6 @@ class AttachmentFileNotificationService: LockNotificationService() {
}
}
}
return START_REDELIVER_INTENT
}
@Synchronized
@@ -249,6 +279,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
notificationManager?.cancel(attachmentNotification.notificationId)
}
attachmentNotificationList.clear()
mDatabaseTaskProvider?.unregisterProgressTask()
super.onDestroy()
}

View File

@@ -302,5 +302,9 @@ class ClipboardEntryNotificationService : LockNotificationService() {
if (!startService)
context.stopService(intent)
}
fun removeNotification(context: Context?) {
context?.stopService(Intent(context, ClipboardEntryNotificationService::class.java))
}
}
}

View File

@@ -26,7 +26,6 @@ import android.os.*
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.*
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
@@ -54,15 +53,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
override val notificationId: Int = 575
private lateinit var mDatabase: Database
private var mDatabase: Database? = null
private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabaseListeners = LinkedList<DatabaseListener>()
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mActionTaskBinder = ActionTaskBinder()
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
private var mActionRunning = false
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mTaskRemovedRequested = false
private var mIconId: Int = R.drawable.notification_ic_database_load
private var mTitleId: Int = R.string.database_opened
@@ -81,13 +81,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun getService(): DatabaseTaskNotificationService = this@DatabaseTaskNotificationService
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
if (!mActionTaskListeners.contains(actionTaskListener))
mActionTaskListeners.add(actionTaskListener)
fun addDatabaseListener(databaseListener: DatabaseListener) {
if (!mDatabaseListeners.contains(databaseListener))
mDatabaseListeners.add(databaseListener)
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.remove(actionTaskListener)
fun removeDatabaseListener(databaseListener: DatabaseListener) {
mDatabaseListeners.remove(databaseListener)
}
fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
@@ -98,12 +98,19 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
mDatabaseInfoListeners.remove(databaseInfoListener)
}
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
if (!mActionTaskListeners.contains(actionTaskListener))
mActionTaskListeners.add(actionTaskListener)
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.remove(actionTaskListener)
}
}
interface ActionTaskListener {
fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?)
fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?)
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
interface DatabaseListener {
fun onDatabaseRetrieved(database: Database?)
}
interface DatabaseInfoListener {
@@ -111,20 +118,21 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
newDatabaseInfo: SnapFileDatabaseInfo)
}
/**
* Force to call [ActionTaskListener.onStartAction] if the action is still running
*/
fun checkAction() {
if (mActionRunning) {
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId)
}
interface ActionTaskListener {
fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?)
fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?)
fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result)
}
fun checkDatabase() {
mDatabaseListeners.forEach { databaseListener ->
databaseListener.onDatabaseRetrieved(mDatabase)
}
}
fun checkDatabaseInfo() {
try {
mDatabase.fileUri?.let {
mDatabase?.fileUri?.let {
val previousDatabaseInfo = mSnapFileDatabaseInfo
val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
FileDatabaseInfo(applicationContext, it))
@@ -165,7 +173,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun saveDatabaseInfo() {
try {
mDatabase.fileUri?.let {
mDatabase?.fileUri?.let {
mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
FileDatabaseInfo(applicationContext, it))
Log.i(TAG, "Database file saved $mSnapFileDatabaseInfo")
@@ -175,6 +183,19 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
/**
* Force to call [ActionTaskListener.onStartAction] if the action is still running
*/
fun checkAction() {
mDatabase?.let { database ->
if (mActionRunning) {
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(database, mTitleId, mMessageId, mWarningId)
}
}
}
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return mActionTaskBinder
@@ -183,33 +204,39 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
mDatabase = Database.getInstance()
val database = Database.getInstance()
if (mDatabase != database) {
mDatabase = database
mDatabaseListeners.forEach { listener ->
listener.onDatabaseRetrieved(mDatabase)
}
}
// Create the notification
buildMessage(intent)
buildMessage(intent, database.isReadOnly)
val intentAction = intent?.action
if (intentAction == null && !mDatabase.loaded) {
if (intentAction == null && !database.loaded) {
stopSelf()
}
val actionRunnable: ActionRunnable? = when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent)
ACTION_DATABASE_UPDATE_ENTRY_TASK -> buildDatabaseUpdateEntryActionTask(intent)
ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent)
ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent)
ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent)
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent)
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent, database)
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent, database)
ACTION_DATABASE_UPDATE_ENTRY_TASK -> buildDatabaseUpdateEntryActionTask(intent, database)
ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent, database)
ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent, database)
ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent, database)
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent, database)
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent, database)
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent, database)
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent, database)
ACTION_DATABASE_UPDATE_NAME_TASK,
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK,
@@ -222,8 +249,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK,
ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK,
ACTION_DATABASE_UPDATE_PARALLELISM_TASK,
ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> buildDatabaseUpdateElementActionTask(intent)
ACTION_DATABASE_SAVE -> buildDatabaseSave(intent)
ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> buildDatabaseUpdateElementActionTask(intent, database)
ACTION_DATABASE_SAVE -> buildDatabaseSave(intent, database)
else -> null
}
@@ -243,7 +270,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
})
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId)
actionTaskListener.onStartAction(database, mTitleId, mMessageId, mWarningId)
}
},
@@ -253,17 +280,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
{ result ->
try {
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStopAction(intentAction!!, result)
mTaskRemovedRequested = false
actionTaskListener.onStopAction(database, intentAction!!, result)
}
} finally {
// Save the database info before performing action
if (intentAction == ACTION_DATABASE_LOAD_TASK) {
saveDatabaseInfo()
}
val save = !database.isReadOnly
&& (intentAction == ACTION_DATABASE_SAVE
|| intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true)
// Save the database info after performing save action
if (intentAction == ACTION_DATABASE_SAVE
|| intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true) {
mDatabase.fileUri?.let {
if (save) {
database.fileUri?.let {
val newSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
FileDatabaseInfo(applicationContext, it))
mLastLocalSaveTime = System.currentTimeMillis()
@@ -272,8 +302,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
removeIntentData(intent)
TimeoutHelper.releaseTemporarilyDisableTimeout()
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
if (!mDatabase.loaded) {
// Stop service after save if user remove task
if (save && mTaskRemovedRequested) {
actionOnLock()
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
if (!database.loaded) {
stopSelf()
} else {
// Restart the service to open lock notification
@@ -283,6 +316,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} catch (e: IllegalStateException) {}
}
}
mTaskRemovedRequested = false
}
sendBroadcast(Intent(DATABASE_STOP_TASK_ACTION))
@@ -306,13 +340,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildMessage(intent: Intent?) {
private fun buildMessage(intent: Intent?, readOnly: Boolean) {
// Assign elements for updates
val intentAction = intent?.action
var saveAction = false
if (intent != null && intent.hasExtra(SAVE_DATABASE_KEY)) {
saveAction = intent.getBooleanExtra(SAVE_DATABASE_KEY, saveAction)
saveAction = !readOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, saveAction)
}
mIconId = if (intentAction == null)
@@ -364,25 +398,31 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
if (intentAction == null) {
// Database is normally open
if (mDatabase.loaded) {
// Build Intents for notification action
val pendingDatabaseIntent = PendingIntent.getActivity(this,
mDatabase?.let { database ->
// Database is normally open
if (database.loaded) {
// Build Intents for notification action
val pendingDatabaseIntent = PendingIntent.getActivity(
this,
0,
Intent(this, GroupActivity::class.java).apply {
ReadOnlyHelper.putReadOnlyInIntent(this, mDatabase.isReadOnly)
},
PendingIntent.FLAG_UPDATE_CURRENT)
val pendingDeleteIntent = PendingIntent.getBroadcast(this,
4576, Intent(LOCK_ACTION), 0)
// Add actions in notifications
notificationBuilder.apply {
setContentText(mDatabase.name + " (" + mDatabase.version + ")")
setContentIntent(pendingDatabaseIntent)
// Unfortunately swipe is disabled in lollipop+
setDeleteIntent(pendingDeleteIntent)
addAction(R.drawable.ic_lock_white_24dp, getString(R.string.lock),
pendingDeleteIntent)
Intent(this, GroupActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT
)
val pendingDeleteIntent = PendingIntent.getBroadcast(
this,
4576, Intent(LOCK_ACTION), 0
)
// Add actions in notifications
notificationBuilder.apply {
setContentText(database.name + " (" + database.version + ")")
setContentIntent(pendingDatabaseIntent)
// Unfortunately swipe is disabled in lollipop+
setDeleteIntent(pendingDeleteIntent)
addAction(
R.drawable.ic_lock_white_24dp, getString(R.string.lock),
pendingDeleteIntent
)
}
}
}
}
@@ -442,14 +482,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
override fun updateMessage(resId: Int) {
mMessageId = resId
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onUpdateAction(mTitleId, mMessageId, mWarningId)
mDatabase?.let { database ->
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onUpdateAction(database, mTitleId, mMessageId, mWarningId)
}
}
}
override fun actionOnLock() {
if (!TimeoutHelper.temporarilyDisableTimeout) {
closeDatabase()
if (!TimeoutHelper.temporarilyDisableLock) {
closeDatabase(mDatabase)
// Remove the lock timer (no more needed if it exists)
TimeoutHelper.cancelLockTimer(this)
// Service is stopped after receive the broadcast
@@ -457,7 +499,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseCreateActionTask(intent: Intent): ActionRunnable? {
override fun onTaskRemoved(rootIntent: Intent?) {
if (TimeoutHelper.temporarilyDisableLock) {
mTaskRemovedRequested = true
}
super.onTaskRemoved(rootIntent)
}
private fun buildDatabaseCreateActionTask(intent: Intent, database: Database): ActionRunnable? {
if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
@@ -469,12 +518,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
return null
return CreateDatabaseRunnable(this,
mDatabase,
databaseUri,
getString(R.string.database_default_name),
getString(R.string.database),
getString(R.string.template_group_name),
mainCredential
database,
databaseUri,
getString(R.string.database_default_name),
getString(R.string.database),
getString(R.string.template_group_name),
mainCredential
) { result ->
result.data = Bundle().apply {
putParcelable(DATABASE_URI_KEY, databaseUri)
@@ -486,7 +535,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseLoadActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseLoadActionTask(intent: Intent, database: Database): ActionRunnable? {
if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
@@ -504,7 +553,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
return LoadDatabaseRunnable(
this,
mDatabase,
database,
databaseUri,
mainCredential,
readOnly,
@@ -525,10 +574,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseReloadActionTask(): ActionRunnable {
private fun buildDatabaseReloadActionTask(database: Database): ActionRunnable {
return ReloadDatabaseRunnable(
this,
mDatabase,
database,
this
) { result ->
// No need to add each info to reload database
@@ -536,15 +585,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseAssignPasswordActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
) {
val databaseUri: Uri = intent.getParcelableExtra(DATABASE_URI_KEY) ?: return null
AssignPasswordInDatabaseRunnable(this,
mDatabase,
databaseUri,
intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
database,
databaseUri,
intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
)
} else {
null
@@ -561,7 +610,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseCreateGroupActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseCreateGroupActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(GROUP_KEY)
&& intent.hasExtra(PARENT_ID_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
@@ -573,20 +622,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|| newGroup == null)
return null
mDatabase.getGroupById(parentId)?.let { parent ->
database.getGroupById(parentId)?.let { parent ->
AddGroupRunnable(this,
mDatabase,
newGroup,
parent,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
newGroup,
parent,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
}
} else {
null
}
}
private fun buildDatabaseUpdateGroupActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseUpdateGroupActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(GROUP_ID_KEY)
&& intent.hasExtra(GROUP_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
@@ -598,20 +647,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|| newGroup == null)
return null
mDatabase.getGroupById(groupId)?.let { oldGroup ->
database.getGroupById(groupId)?.let { oldGroup ->
UpdateGroupRunnable(this,
mDatabase,
oldGroup,
newGroup,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
oldGroup,
newGroup,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
}
} else {
null
}
}
private fun buildDatabaseCreateEntryActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseCreateEntryActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(ENTRY_KEY)
&& intent.hasExtra(PARENT_ID_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
@@ -623,20 +672,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|| newEntry == null)
return null
mDatabase.getGroupById(parentId)?.let { parent ->
database.getGroupById(parentId)?.let { parent ->
AddEntryRunnable(this,
mDatabase,
newEntry,
parent,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
newEntry,
parent,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
}
} else {
null
}
}
private fun buildDatabaseUpdateEntryActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseUpdateEntryActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(ENTRY_ID_KEY)
&& intent.hasExtra(ENTRY_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
@@ -648,20 +697,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|| newEntry == null)
return null
mDatabase.getEntryById(entryId)?.let { oldEntry ->
database.getEntryById(entryId)?.let { oldEntry ->
UpdateEntryRunnable(this,
mDatabase,
oldEntry,
newEntry,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
oldEntry,
newEntry,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
}
} else {
null
}
}
private fun buildDatabaseCopyNodesActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseCopyNodesActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(GROUPS_ID_KEY)
&& intent.hasExtra(ENTRIES_ID_KEY)
&& intent.hasExtra(PARENT_ID_KEY)
@@ -669,20 +718,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
) {
val parentId: NodeId<*> = intent.getParcelableExtra(PARENT_ID_KEY) ?: return null
mDatabase.getGroupById(parentId)?.let { newParent ->
database.getGroupById(parentId)?.let { newParent ->
CopyNodesRunnable(this,
mDatabase,
getListNodesFromBundle(mDatabase, intent.extras!!),
newParent,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
getListNodesFromBundle(database, intent.extras!!),
newParent,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
}
} else {
null
}
}
private fun buildDatabaseMoveNodesActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseMoveNodesActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(GROUPS_ID_KEY)
&& intent.hasExtra(ENTRIES_ID_KEY)
&& intent.hasExtra(PARENT_ID_KEY)
@@ -690,73 +739,73 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
) {
val parentId: NodeId<*> = intent.getParcelableExtra(PARENT_ID_KEY) ?: return null
mDatabase.getGroupById(parentId)?.let { newParent ->
database.getGroupById(parentId)?.let { newParent ->
MoveNodesRunnable(this,
mDatabase,
getListNodesFromBundle(mDatabase, intent.extras!!),
newParent,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
getListNodesFromBundle(database, intent.extras!!),
newParent,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
}
} else {
null
}
}
private fun buildDatabaseDeleteNodesActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseDeleteNodesActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(GROUPS_ID_KEY)
&& intent.hasExtra(ENTRIES_ID_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
) {
DeleteNodesRunnable(this,
mDatabase,
getListNodesFromBundle(mDatabase, intent.extras!!),
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
database,
getListNodesFromBundle(database, intent.extras!!),
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
AfterActionNodesRunnable())
} else {
null
}
}
private fun buildDatabaseRestoreEntryHistoryActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseRestoreEntryHistoryActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(ENTRY_ID_KEY)
&& intent.hasExtra(ENTRY_HISTORY_POSITION_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
) {
val entryId: NodeId<UUID> = intent.getParcelableExtra(ENTRY_ID_KEY) ?: return null
mDatabase.getEntryById(entryId)?.let { mainEntry ->
database.getEntryById(entryId)?.let { mainEntry ->
RestoreEntryHistoryDatabaseRunnable(this,
mDatabase,
mainEntry,
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
database,
mainEntry,
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
}
} else {
null
}
}
private fun buildDatabaseDeleteEntryHistoryActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseDeleteEntryHistoryActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(ENTRY_ID_KEY)
&& intent.hasExtra(ENTRY_HISTORY_POSITION_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
) {
val entryId: NodeId<UUID> = intent.getParcelableExtra(ENTRY_ID_KEY) ?: return null
mDatabase.getEntryById(entryId)?.let { mainEntry ->
database.getEntryById(entryId)?.let { mainEntry ->
DeleteEntryHistoryDatabaseRunnable(this,
mDatabase,
mainEntry,
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
database,
mainEntry,
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
}
} else {
null
}
}
private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseUpdateCompressionActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(OLD_ELEMENT_KEY)
&& intent.hasExtra(NEW_ELEMENT_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)) {
@@ -769,10 +818,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
return null
return UpdateCompressionBinariesDatabaseRunnable(this,
mDatabase,
oldElement,
newElement,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
database,
oldElement,
newElement,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
).apply {
mAfterSaveDatabase = { result ->
result.data = intent.extras
@@ -783,12 +832,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseRemoveUnlinkedDataActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseRemoveUnlinkedDataActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
return RemoveUnlinkedDataDatabaseRunnable(this,
mDatabase,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
database,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
).apply {
mAfterSaveDatabase = { result ->
result.data = intent.extras
@@ -799,11 +848,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? {
private fun buildDatabaseUpdateElementActionTask(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
return SaveDatabaseRunnable(this,
mDatabase,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
database,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
).apply {
mAfterSaveDatabase = { result ->
result.data = intent.extras
@@ -817,22 +866,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
/**
* Save database without parameter
*/
private fun buildDatabaseSave(intent: Intent): ActionRunnable? {
private fun buildDatabaseSave(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
SaveDatabaseRunnable(this,
mDatabase,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
database,
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
} else {
null
}
}
override fun onDestroy() {
super.onDestroy()
if (mDatabase.loaded)
actionOnLock()
}
companion object {
private val TAG = DatabaseTaskNotificationService::class.java.name
@@ -916,9 +959,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
nodes.forEach { nodeVersioned ->
when (nodeVersioned.type) {
Type.GROUP -> {
(nodeVersioned as Group).nodeId?.let { groupId ->
groupsId.add(groupId)
}
groupsId.add((nodeVersioned as Group).nodeId)
}
Type.ENTRY -> {
entriesId.add((nodeVersioned as Entry).nodeId)

View File

@@ -26,7 +26,7 @@ import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
@@ -117,14 +117,14 @@ class KeyboardEntryNotificationService : LockNotificationService() {
}
override fun onTaskRemoved(rootIntent: Intent?) {
MagikIME.removeEntry(this)
MagikeyboardService.removeEntry(this)
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
// Remove the entry from the keyboard
MagikIME.removeEntry(this)
MagikeyboardService.removeEntry(this)
pendingDeleteIntent?.cancel()
@@ -165,7 +165,7 @@ class KeyboardEntryNotificationService : LockNotificationService() {
})
}
} else {
MagikIME.removeEntry(context)
MagikeyboardService.removeEntry(context)
}
if (!startService)

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.services
import android.content.Intent
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.LockReceiver
import com.kunzisoft.keepass.utils.registerLockReceiver
import com.kunzisoft.keepass.utils.unregisterLockReceiver
@@ -42,7 +43,9 @@ abstract class LockNotificationService : NotificationService() {
}
override fun onTaskRemoved(rootIntent: Intent?) {
stopSelf()
if (!TimeoutHelper.temporarilyDisableLock) {
actionOnLock()
}
super.onTaskRemoved(rootIntent)
}

View File

@@ -23,9 +23,9 @@ import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
class AutofillSettingsActivity : SpecialModeActivity() {
class AutofillSettingsActivity : DatabaseModeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -1,16 +0,0 @@
package com.kunzisoft.keepass.settings
import android.content.Context
import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.database.element.Database
abstract class DatabasePreferenceFragment : PreferenceFragmentCompat() {
protected var mDatabase: Database? = null
override fun onAttach(context: Context) {
super.onAttach(context)
mDatabase = Database.getInstance()
}
}

View File

@@ -24,9 +24,9 @@ import androidx.appcompat.widget.Toolbar
import android.view.MenuItem
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
class MagikeyboardSettingsActivity : SpecialModeActivity() {
class MagikeyboardSettingsActivity : DatabaseModeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -21,13 +21,20 @@ package com.kunzisoft.keepass.settings
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
class MainPreferenceFragment : DatabasePreferenceFragment() {
class MainPreferenceFragment : PreferenceFragmentCompat() {
private var mCallback: Callback? = null
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
private var mDatabaseLoaded: Boolean = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -42,6 +49,18 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
mCallback = null
super.onDetach()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
mDatabaseLoaded = database?.loaded == true
checkDatabaseLoaded()
}
super.onViewCreated(view, savedInstanceState)
}
private fun checkDatabaseLoaded() {
findPreference<Preference>(getString(R.string.settings_database_key))
?.isEnabled = mDatabaseLoaded
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
@@ -80,9 +99,6 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE)
false
}
if (mDatabase?.loaded != true) {
isEnabled = false
}
}
findPreference<Preference>(getString(R.string.settings_database_security_key))?.apply {
@@ -98,6 +114,8 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
false
}
}
checkDatabaseLoaded()
}
interface Callback {

View File

@@ -45,6 +45,7 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
import com.kunzisoft.keepass.utils.UriUtil
@@ -190,6 +191,13 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
false
}
findPreference<Preference>(getString(R.string.clipboard_notifications_key))?.setOnPreferenceChangeListener { _, newValue ->
if (!(newValue as Boolean)) {
ClipboardEntryNotificationService.removeNotification(context)
}
true
}
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
UriUtil.gotoUrl(requireContext(), R.string.clipboard_explanation_url)
false
@@ -384,8 +392,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
private fun onCreateAppearancePreferences(rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_appearance, rootKey)
// To change list items appearance
PreferencesUtil.APPEARANCE_CHANGED = true
DATABASE_APPEARANCE_PREFERENCE_CHANGED = true
activity?.let { activity ->
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
@@ -402,7 +409,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
Stylish.assignStyle(activity, styleIdString)
// Relaunch the current activity to redraw theme
(activity as? SettingsActivity?)?.apply {
relaunchCurrentScreen()
reloadActivity()
}
}
styleEnabled
@@ -410,7 +417,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<ListPreference>(getString(R.string.setting_style_brightness_key))?.setOnPreferenceChangeListener { _, _ ->
(activity as? SettingsActivity?)?.apply {
relaunchCurrentScreen()
reloadActivity()
}
true
}
@@ -426,9 +433,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
}
if (iconPackEnabled) {
mDatabase?.let {
IconPackChooser.setSelectedIconPack(it.iconDrawableFactory, iconPackId)
}
IconPackChooser.setSelectedIconPack(iconPackId)
}
iconPackEnabled
}
@@ -507,5 +512,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
companion object {
private const val REQUEST_CODE_AUTOFILL = 5201
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
var DATABASE_APPEARANCE_PREFERENCE_CHANGED = false
}
}

View File

@@ -24,15 +24,18 @@ import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -40,12 +43,17 @@ import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetrieval {
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
private var mDatabase: Database? = null
private var mDatabaseReadOnly: Boolean = false
private var mDatabaseAutoSaveEnabled: Boolean = true
private var mScreen: Screen? = null
private var dbNamePref: InputTextPreference? = null
private var dbDescriptionPref: InputTextPreference? = null
private var dbDefaultUsername: InputTextPreference? = null
@@ -61,179 +69,220 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
private var mMemoryPref: InputKdfSizePreference? = null
private var mParallelismPref: InputKdfNumberPreference? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
mDatabase = database
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database?.loaded)
onDatabaseRetrieved(database)
}
mDatabaseViewModel.actionFinished.observe(viewLifecycleOwner) {
onDatabaseActionFinished(it.database, it.actionTask, it.result)
}
}
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
setHasOptionsMenu(true)
mDatabaseReadOnly = mDatabase?.isReadOnly == true
|| ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
mScreen = screen
val database = mDatabase
// Load the preferences from an XML resource
when (screen) {
Screen.DATABASE -> {
onCreateDatabasePreference(rootKey)
setPreferencesFromResource(R.xml.preferences_database, rootKey)
if (database?.loaded == true)
onCreateDatabasePreference(database)
}
Screen.DATABASE_SECURITY -> {
onCreateDatabaseSecurityPreference(rootKey)
setPreferencesFromResource(R.xml.preferences_database_security, rootKey)
if (database?.loaded == true)
onCreateDatabaseSecurityPreference(database)
}
Screen.DATABASE_MASTER_KEY -> {
onCreateDatabaseMasterKeyPreference(rootKey)
setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey)
if (database?.loaded == true)
onCreateDatabaseMasterKeyPreference(database)
}
else -> {
}
else -> {}
}
}
private fun onCreateDatabasePreference(rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_database, rootKey)
private fun saveDatabase(save: Boolean) {
mDatabaseViewModel.saveDatabase(save)
}
mDatabase?.let { database ->
if (database.loaded) {
private fun reloadDatabase() {
mDatabaseViewModel.reloadDatabase(false)
}
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
override fun onDatabaseRetrieved(database: Database?) {
mDatabase = database
mDatabaseReadOnly = database?.isReadOnly == true
// Database name
dbNamePref = findPreference(getString(R.string.database_name_key))
if (database.allowName) {
dbNamePref?.summary = database.name
} else {
dbGeneralPrefCategory?.removePreference(dbNamePref)
}
// Database description
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
if (database.allowDescription) {
dbDescriptionPref?.summary = database.description
} else {
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
}
// Database default username
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
if (database.allowDefaultUsername) {
dbDefaultUsername?.summary = database.defaultUsername
} else {
dbDefaultUsername?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
}
// Database custom color
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
if (database.allowCustomColor) {
dbCustomColorPref?.apply {
try {
color = Color.parseColor(database.customColor)
summary = database.customColor
} catch (e: Exception) {
color = DialogColorPreference.DISABLE_COLOR
summary = ""
}
mDatabase?.let {
if (it.loaded) {
when (mScreen) {
Screen.DATABASE -> {
onCreateDatabasePreference(it)
}
} else {
dbCustomColorPref?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
}
// Version
findPreference<Preference>(getString(R.string.database_version_key))
?.summary = database.version
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_data_key))
// Database compression
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
if (database.allowDataCompression) {
dbDataCompressionPref?.summary = (database.compressionAlgorithm
?: CompressionAlgorithm.None).getName(resources)
} else {
dbCompressionPrefCategory?.isVisible = false
}
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
// Recycle bin
if (database.allowConfigurableRecycleBin) {
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
recycleBinEnablePref?.apply {
isChecked = database.isRecycleBinEnabled
isEnabled = if (!mDatabaseReadOnly) {
setOnPreferenceChangeListener { _, newValue ->
val recycleBinEnabled = newValue as Boolean
database.enableRecycleBin(recycleBinEnabled, resources)
refreshRecycleBinGroup()
// Save the database if not in readonly mode
(context as SettingsActivity?)?.
mProgressDatabaseTaskProvider?.startDatabaseSave(mDatabaseAutoSaveEnabled)
true
}
true
} else {
false
}
Screen.DATABASE_SECURITY -> {
onCreateDatabaseSecurityPreference(it)
}
// Change the recycle bin group
recycleBinGroupPref?.setOnPreferenceClickListener {
true
Screen.DATABASE_MASTER_KEY -> {
onCreateDatabaseMasterKeyPreference(it)
}
// Recycle Bin group
refreshRecycleBinGroup()
} else {
recycleBinGroupPref?.onPreferenceClickListener = null
dbRecycleBinPrefCategory?.isVisible = false
}
// Templates
val templatesGroupPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_templates_key))
templatesGroupPref = findPreference(getString(R.string.templates_group_uuid_key))
if (database.allowConfigurableTemplatesGroup) {
val templatesEnablePref: SwitchPreference? = findPreference(getString(R.string.templates_group_enable_key))
templatesEnablePref?.apply {
isChecked = database.isTemplatesEnabled
isEnabled = if (!mDatabaseReadOnly) {
setOnPreferenceChangeListener { _, newValue ->
val templatesEnabled = newValue as Boolean
database.enableTemplates(templatesEnabled, resources)
refreshTemplatesGroup()
// Save the database if not in readonly mode
(context as SettingsActivity?)?.
mProgressDatabaseTaskProvider?.startDatabaseSave(mDatabaseAutoSaveEnabled)
true
}
true
} else {
false
}
else -> {
}
// Recycle Bin group
refreshTemplatesGroup()
} else {
templatesGroupPrefCategory?.isVisible = false
}
// History
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
?.isVisible = database.manageHistory == true
// Max history items
dbMaxHistoryItemsPref = findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))?.apply {
summary = database.historyMaxItems.toString()
}
// Max history size
dbMaxHistorySizePref = findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))?.apply {
summary = database.historyMaxSize.toString()
}
} else {
Log.e(javaClass.name, "Database isn't ready")
}
}
}
private fun refreshRecycleBinGroup() {
private fun onCreateDatabasePreference(database: Database) {
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
// Database name
dbNamePref = findPreference(getString(R.string.database_name_key))
if (database.allowName) {
dbNamePref?.summary = database.name
} else {
dbGeneralPrefCategory?.removePreference(dbNamePref)
}
// Database description
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
if (database.allowDescription) {
dbDescriptionPref?.summary = database.description
} else {
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
}
// Database default username
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
if (database.allowDefaultUsername) {
dbDefaultUsername?.summary = database.defaultUsername
} else {
dbDefaultUsername?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
}
// Database custom color
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
if (database.allowCustomColor) {
dbCustomColorPref?.apply {
try {
color = Color.parseColor(database.customColor)
summary = database.customColor
} catch (e: Exception) {
color = DialogColorPreference.DISABLE_COLOR
summary = ""
}
}
} else {
dbCustomColorPref?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
}
// Version
findPreference<Preference>(getString(R.string.database_version_key))
?.summary = database.version
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_data_key))
// Database compression
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
if (database.allowDataCompression) {
dbDataCompressionPref?.summary = (database.compressionAlgorithm
?: CompressionAlgorithm.None).getName(resources)
} else {
dbCompressionPrefCategory?.isVisible = false
}
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
// Recycle bin
if (database.allowConfigurableRecycleBin) {
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
recycleBinEnablePref?.apply {
isChecked = database.isRecycleBinEnabled
isEnabled = if (!mDatabaseReadOnly) {
setOnPreferenceChangeListener { _, newValue ->
val recycleBinEnabled = newValue as Boolean
database.enableRecycleBin(recycleBinEnabled, resources)
refreshRecycleBinGroup(database)
// Save the database if not in readonly mode
saveDatabase(mDatabaseAutoSaveEnabled)
true
}
true
} else {
false
}
}
// Change the recycle bin group
recycleBinGroupPref?.setOnPreferenceClickListener {
true
}
// Recycle Bin group
refreshRecycleBinGroup(database)
} else {
recycleBinGroupPref?.onPreferenceClickListener = null
dbRecycleBinPrefCategory?.isVisible = false
}
// Templates
val templatesGroupPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_templates_key))
templatesGroupPref = findPreference(getString(R.string.templates_group_uuid_key))
if (database.allowConfigurableTemplatesGroup) {
val templatesEnablePref: SwitchPreference? = findPreference(getString(R.string.templates_group_enable_key))
templatesEnablePref?.apply {
isChecked = database.isTemplatesEnabled
isEnabled = if (!mDatabaseReadOnly) {
setOnPreferenceChangeListener { _, newValue ->
val templatesEnabled = newValue as Boolean
database.enableTemplates(templatesEnabled, resources)
refreshTemplatesGroup(database)
// Save the database if not in readonly mode
saveDatabase(mDatabaseAutoSaveEnabled)
true
}
true
} else {
false
}
}
// Recycle Bin group
refreshTemplatesGroup(database)
} else {
templatesGroupPrefCategory?.isVisible = false
}
// History
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
?.isVisible = database.manageHistory == true
// Max history items
dbMaxHistoryItemsPref = findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))?.apply {
summary = database.historyMaxItems.toString()
}
// Max history size
dbMaxHistorySizePref = findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))?.apply {
summary = database.historyMaxSize.toString()
}
}
private fun refreshRecycleBinGroup(database: Database?) {
recycleBinGroupPref?.apply {
if (mDatabase?.isRecycleBinEnabled == true) {
summary = mDatabase?.recycleBin?.toString()
if (database?.isRecycleBinEnabled == true) {
summary = database.recycleBin?.toString()
isEnabled = true
} else {
summary = null
@@ -242,10 +291,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
}
}
private fun refreshTemplatesGroup() {
private fun refreshTemplatesGroup(database: Database?) {
templatesGroupPref?.apply {
if (mDatabase?.isTemplatesEnabled == true) {
summary = mDatabase?.templatesGroup?.toString()
if (database?.isTemplatesEnabled == true) {
summary = database.templatesGroup?.toString()
isEnabled = true
} else {
summary = null
@@ -254,60 +303,44 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
}
}
private fun onCreateDatabaseSecurityPreference(rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_database_security, rootKey)
private fun onCreateDatabaseSecurityPreference(database: Database) {
// Encryption Algorithm
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
summary = database.getEncryptionAlgorithmName()
}
mDatabase?.let { database ->
if (database.loaded) {
// Encryption Algorithm
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
summary = database.getEncryptionAlgorithmName()
}
// Key derivation function
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
summary = database.getKeyDerivationName()
}
// Key derivation function
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
summary = database.getKeyDerivationName()
}
// Round encryption
mRoundPref = findPreference<InputKdfNumberPreference>(getString(R.string.transform_rounds_key))?.apply {
summary = database.numberKeyEncryptionRounds.toString()
}
// Round encryption
mRoundPref = findPreference<InputKdfNumberPreference>(getString(R.string.transform_rounds_key))?.apply {
summary = database.numberKeyEncryptionRounds.toString()
}
// Memory Usage
mMemoryPref = findPreference<InputKdfSizePreference>(getString(R.string.memory_usage_key))?.apply {
summary = database.memoryUsage.toString()
}
// Memory Usage
mMemoryPref = findPreference<InputKdfSizePreference>(getString(R.string.memory_usage_key))?.apply {
summary = database.memoryUsage.toString()
}
// Parallelism
mParallelismPref = findPreference<InputKdfNumberPreference>(getString(R.string.parallelism_key))?.apply {
summary = database.parallelism.toString()
}
} else {
Log.e(javaClass.name, "Database isn't ready")
}
// Parallelism
mParallelismPref = findPreference<InputKdfNumberPreference>(getString(R.string.parallelism_key))?.apply {
summary = database.parallelism.toString()
}
}
private fun onCreateDatabaseMasterKeyPreference(rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey)
mDatabase?.let { database ->
if (database.loaded) {
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
isEnabled = if (!mDatabaseReadOnly) {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AssignMasterKeyDialogFragment.getInstance(database.allowNoMasterKey)
.show(parentFragmentManager, "passwordDialog")
false
}
true
} else {
false
}
private fun onCreateDatabaseMasterKeyPreference(database: Database) {
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
isEnabled = if (!mDatabaseReadOnly) {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AssignMasterKeyDialogFragment.getInstance(database.allowNoMasterKey)
.show(parentFragmentManager, "passwordDialog")
false
}
true
} else {
Log.e(javaClass.name, "Database isn't ready")
false
}
}
}
@@ -333,8 +366,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
return view
}
override fun onProgressDialogThreadResult(actionTask: String,
result: ActionRunnable.Result) {
// TODO check error
override fun onDatabaseActionFinished(database: Database,
actionTask: String,
result: ActionRunnable.Result) {
result.data?.let { data ->
if (data.containsKey(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
&& data.containsKey(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)) {
@@ -351,7 +386,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
if (result.isSuccess) {
newName
} else {
mDatabase?.name = oldName
database.name = oldName
oldName
}
dbNamePref?.summary = nameToShow
@@ -363,7 +398,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
if (result.isSuccess) {
newDescription
} else {
mDatabase?.description = oldDescription
database.description = oldDescription
oldDescription
}
dbDescriptionPref?.summary = descriptionToShow
@@ -415,7 +450,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
oldRecycleBin
}
mDatabase?.setRecycleBin(recycleBinToShow)
refreshRecycleBinGroup()
refreshRecycleBinGroup(database)
}
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_TEMPLATES_GROUP_TASK -> {
val oldTemplatesGroup = data.getParcelable<Group?>(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
@@ -427,7 +462,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
oldTemplatesGroup
}
mDatabase?.setTemplatesGroup(templatesGroupToShow)
refreshTemplatesGroup()
refreshTemplatesGroup(database)
}
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK -> {
val oldMaxHistoryItems = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
@@ -563,10 +598,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
dialogFragment = DatabaseTemplatesGroupPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.max_history_items_key) -> {
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
dialogFragment = DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.max_history_size_key) -> {
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
dialogFragment = DatabaseMaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
}
// Security
@@ -582,13 +617,13 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
dialogFragment = keyDerivationDialogFragment
}
getString(R.string.transform_rounds_key) -> {
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
dialogFragment = DatabaseRoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.memory_usage_key) -> {
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
dialogFragment = DatabaseMemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.parallelism_key) -> {
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
dialogFragment = DatabaseParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
@@ -621,37 +656,27 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val settingActivity = activity as SettingsActivity?
return when (item.itemId) {
R.id.menu_save_database -> {
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly)
saveDatabase(!mDatabaseReadOnly)
true
}
R.id.menu_reload_database -> {
settingActivity?.apply {
keepCurrentScreen()
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
}
reloadDatabase()
return true
}
else -> {
// Check the time lock before launching settings
settingActivity?.let {
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
// TODO activity menu
(activity as SettingsActivity?)?.let {
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, true)
}
super.onOptionsItemSelected(item)
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
ReadOnlyHelper.onSaveInstanceState(outState, mDatabaseReadOnly)
super.onSaveInstanceState(outState)
}
companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
}

View File

@@ -22,13 +22,12 @@ package com.kunzisoft.keepass.settings
import android.content.res.Resources
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.tasks.ActionRunnable
abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
enum class Screen {
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY
@@ -47,9 +46,6 @@ abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?)
open fun onProgressDialogThreadResult(actionTask: String,
result: ActionRunnable.Result) {}
protected fun preferenceInDevelopment(preferenceInDev: Preference) {
preferenceInDev.setOnPreferenceClickListener { preference ->
try { // don't check if we can
@@ -65,7 +61,7 @@ abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
private const val TAG_KEY = "NESTED_KEY"
fun newInstance(key: Screen, databaseReadOnly: Boolean = ReadOnlyHelper.READ_ONLY_DEFAULT)
fun newInstance(key: Screen)
: NestedSettingsFragment {
val fragment: NestedSettingsFragment = when (key) {
Screen.APPLICATION,
@@ -79,7 +75,6 @@ abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
// supply arguments to bundle.
val args = Bundle()
args.putInt(TAG_KEY, key.ordinal)
ReadOnlyHelper.putReadOnlyInBundle(args, databaseReadOnly)
fragment.arguments = args
return fragment
}

View File

@@ -37,8 +37,6 @@ import java.util.*
object PreferencesUtil {
var APPEARANCE_CHANGED = false
fun saveDefaultDatabasePath(context: Context, defaultDatabaseUri: Uri?) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs?.edit()?.apply {

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.settings
import android.app.Activity
import android.app.backup.BackupManager
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
@@ -33,24 +32,20 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
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.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import org.joda.time.DateTime
import java.util.*
open class SettingsActivity
: LockingActivity(),
: DatabaseLockActivity(),
MainPreferenceFragment.Callback,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
PasswordEncodingDialogFragment.Listener {
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -60,14 +55,6 @@ open class SettingsActivity
private var toolbar: Toolbar? = null
private var lockView: View? = null
/**
* Retrieve the main fragment to show in first
* @return The main fragment
*/
protected open fun retrieveMainFragment(): Fragment {
return MainPreferenceFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -90,9 +77,6 @@ open class SettingsActivity
lockAndExit()
}
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, retrieveMainFragment())
@@ -103,29 +87,6 @@ open class SettingsActivity
backupManager = BackupManager(this)
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
when (actionTask) {
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} else {
this.showActionErrorIfNeeded(result)
finish()
}
}
else -> {
// Call result in fragment
(supportFragmentManager
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
?.onProgressDialogThreadResult(actionTask, result)
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
// To reload the current screen
if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName ->
@@ -136,6 +97,37 @@ open class SettingsActivity
}
}
/**
* Retrieve the main fragment to show in first
* @return The main fragment
*/
protected open fun retrieveMainFragment(): Fragment {
return MainPreferenceFragment()
}
override fun viewToInvalidateTimeout(): View? {
return coordinatorLayout
}
override fun finishActivityIfDatabaseNotLoaded(): Boolean {
return false
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
coordinatorLayout?.showActionErrorIfNeeded(result)
}
override fun reloadActivity() {
keepCurrentScreen()
super.reloadActivity()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> onBackPressed()
@@ -149,31 +141,8 @@ open class SettingsActivity
super.onStop()
}
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
mainCredential: MainCredential) {
databaseUri?.let {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
mainCredential
)
}
}
override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
mDatabase?.let { database ->
database.fileUri?.let { databaseUri ->
// Show the progress dialog now or after dialog confirmation
if (database.validatePasswordEncoding(mainCredential)) {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
mainCredential
)
} else {
PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
.show(supportFragmentManager, "passwordEncodingTag")
}
}
}
assignPassword(mainCredential)
}
override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {}
@@ -215,7 +184,7 @@ open class SettingsActivity
setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
}
replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
addToBackStack(TAG_NESTED)
commit()
}
@@ -224,17 +193,10 @@ open class SettingsActivity
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
*/
fun keepCurrentScreen() {
private fun keepCurrentScreen() {
(supportFragmentManager.findFragmentByTag(TAG_NESTED) as? NestedSettingsFragment?)
?.getScreen()?.let { fragmentKey ->
intent.putExtra(FRAGMENT_ARG, fragmentKey.name)
@@ -243,7 +205,7 @@ open class SettingsActivity
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
if (mTimeoutEnable)
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase) {
checkTimeAndLockIfTimeoutOrResetTimeout {
replaceFragment(key, reload)
}
else
@@ -264,8 +226,8 @@ open class SettingsActivity
// Import app properties result
try {
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedfileUri ->
selectedfileUri?.let { uri ->
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedFileUri ->
selectedFileUri?.let { uri ->
val appProperties = Properties()
contentResolver?.openInputStream(uri)?.use { inputStream ->
appProperties.load(inputStream)
@@ -273,7 +235,7 @@ open class SettingsActivity
PreferencesUtil.setAppProperties(this, appProperties)
// Restart the current activity
relaunchCurrentScreen()
reloadActivity()
Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show()
}
}
@@ -299,7 +261,7 @@ open class SettingsActivity
}
} 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)
Log.e(DatabaseLockActivity.TAG, "Unable to export app properties", e)
}
}
@@ -319,9 +281,8 @@ open class SettingsActivity
private const val TAG_NESTED = "TAG_NESTED"
private const val FRAGMENT_ARG = "FRAGMENT_ARG"
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
fun launch(activity: Activity, timeoutEnable: Boolean) {
val intent = Intent(activity, SettingsActivity::class.java)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intent.putExtra(TIMEOUT_ENABLE_KEY, timeoutEnable)
if (!timeoutEnable) {
activity.startActivity(intent)

View File

@@ -45,7 +45,7 @@ class IconPackListPreference @JvmOverloads constructor(context: Context,
setEntries(entries.toTypedArray())
entryValues = values.toTypedArray()
IconPackChooser.getSelectedIconPack(context, Database.getInstance().iconDrawableFactory)?.let { selectedIconPack ->
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
setDefaultValue(selectedIconPack.id)
}
}

View File

@@ -36,6 +36,7 @@ import com.kunzisoft.androidclearchroma.colormode.ColorMode
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment.*
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
@@ -46,51 +47,12 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
var onColorSelectedListener: ((enable: Boolean, color: Int) -> Unit)? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = AlertDialog.Builder(requireActivity())
rootView = requireActivity().layoutInflater.inflate(R.layout.pref_dialog_input_color, null)
enableSwitchView = rootView.findViewById(R.id.switch_element)
val fragmentManager = childFragmentManager
chromaColorFragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_COLORS) as ChromaColorFragment?
mDatabase?.let { database ->
val initColor = try {
enableSwitchView.isChecked = true
Color.parseColor(database.customColor)
} catch (e: Exception) {
enableSwitchView.isChecked = false
DEFAULT_COLOR
}
arguments?.putInt(ARG_INITIAL_COLOR, initColor)
}
if (chromaColorFragment == null) {
chromaColorFragment = newInstance(arguments)
fragmentManager.beginTransaction().apply {
add(com.kunzisoft.androidclearchroma.R.id.color_dialog_container, chromaColorFragment!!, TAG_FRAGMENT_COLORS)
commit()
}
}
alertDialogBuilder.setPositiveButton(android.R.string.ok) { _, _ ->
val currentColor = chromaColorFragment!!.currentColor
val customColorEnable = enableSwitchView.isChecked
onColorSelectedListener?.invoke(customColorEnable, currentColor)
mDatabase?.let { database ->
val newColor = if (customColorEnable) {
ChromaUtil.getFormattedColorString(currentColor, false)
} else {
""
}
val oldColor = database.customColor
database.customColor = newColor
mProgressDatabaseTaskProvider?.startDatabaseSaveColor(oldColor, newColor, mDatabaseAutoSaveEnable)
}
onDialogClosed(true)
dismiss()
}
@@ -111,8 +73,50 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
return dialog
}
override fun onDialogClosed(positiveResult: Boolean) {
// Nothing here
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
val initColor = try {
enableSwitchView.isChecked = true
Color.parseColor(it.customColor)
} catch (e: Exception) {
enableSwitchView.isChecked = false
DEFAULT_COLOR
}
arguments?.putInt(ARG_INITIAL_COLOR, initColor)
}
val fragmentManager = childFragmentManager
chromaColorFragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_COLORS) as ChromaColorFragment?
if (chromaColorFragment == null) {
chromaColorFragment = newInstance(arguments)
fragmentManager.beginTransaction().apply {
add(com.kunzisoft.androidclearchroma.R.id.color_dialog_container, chromaColorFragment!!, TAG_FRAGMENT_COLORS)
commit()
}
}
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
val customColorEnable = enableSwitchView.isChecked
chromaColorFragment?.currentColor?.let { currentColor ->
onColorSelectedListener?.invoke(customColorEnable, currentColor)
database?.let {
val newColor = if (customColorEnable) {
ChromaUtil.getFormattedColorString(currentColor, false)
} else {
""
}
val oldColor = database.customColor
database.customColor = newColor
saveColor(oldColor, newColor)
}
}
}
}
/**

View File

@@ -24,6 +24,7 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
@@ -31,6 +32,8 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
: DatabaseSavePreferenceDialogFragmentCompat(),
ListRadioItemAdapter.RadioItemSelectedCallback<CompressionAlgorithm> {
private var mRecyclerView: RecyclerView? = null
private var mCompressionAdapter: ListRadioItemAdapter<CompressionAlgorithm>? = null
private var compressionSelected: CompressionAlgorithm? = null
override fun onBindDialogView(view: View) {
@@ -38,32 +41,38 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
setExplanationText(R.string.database_data_compression_summary)
val recyclerView = view.findViewById<RecyclerView>(R.id.pref_dialog_list)
recyclerView.layoutManager = LinearLayoutManager(context)
mRecyclerView = view.findViewById(R.id.pref_dialog_list)
mRecyclerView?.layoutManager = LinearLayoutManager(context)
activity?.let { activity ->
val compressionAdapter = ListRadioItemAdapter<CompressionAlgorithm>(activity)
compressionAdapter.setRadioItemSelectedCallback(this)
recyclerView.adapter = compressionAdapter
mDatabase?.let { database ->
compressionSelected = database.compressionAlgorithm
compressionAdapter.setItems(database.availableCompressionAlgorithms, compressionSelected)
}
mCompressionAdapter = ListRadioItemAdapter<CompressionAlgorithm>(activity)
mCompressionAdapter?.setRadioItemSelectedCallback(this)
}
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
setExplanationText(R.string.database_data_compression_summary)
mRecyclerView?.adapter = mCompressionAdapter
database?.let {
compressionSelected = it.compressionAlgorithm
mCompressionAdapter?.setItems(it.availableCompressionAlgorithms, compressionSelected)
}
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
if (compressionSelected != null) {
val newCompression = compressionSelected
val oldCompression = database.compressionAlgorithm
database.compressionAlgorithm = newCompression
if (oldCompression != null && newCompression != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveCompression(oldCompression, newCompression, mDatabaseAutoSaveEnable)
saveCompression(oldCompression, newCompression)
}
}
}

View File

@@ -20,23 +20,23 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.database.element.Database
class DatabaseDefaultUsernamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
inputText = mDatabase?.defaultUsername?: ""
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
inputText = database?.defaultUsername?: ""
}
override fun onDialogClosed(positiveResult: Boolean) {
mDatabase?.let { database ->
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
database?.let {
if (positiveResult) {
val newDefaultUsername = inputText
val oldDefaultUsername = database.defaultUsername
database.defaultUsername = newDefaultUsername
mProgressDatabaseTaskProvider?.startDatabaseSaveDefaultUsername(oldDefaultUsername, newDefaultUsername, mDatabaseAutoSaveEnable)
saveDefaultUsername(oldDefaultUsername, newDefaultUsername)
}
}
}

View File

@@ -20,23 +20,23 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.database.element.Database
class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
inputText = mDatabase?.description ?: ""
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
inputText = database?.description ?: ""
}
override fun onDialogClosed(positiveResult: Boolean) {
mDatabase?.let { database ->
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
database?.let {
if (positiveResult) {
val newDescription = inputText
val oldDescription = database.description
database.description = newDescription
mProgressDatabaseTaskProvider?.startDatabaseSaveDescription(oldDescription, newDescription, mDatabaseAutoSaveEnable)
saveDescription(oldDescription, newDescription)
}
}
}

View File

@@ -25,12 +25,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
: DatabaseSavePreferenceDialogFragmentCompat(),
ListRadioItemAdapter.RadioItemSelectedCallback<EncryptionAlgorithm> {
private var mRecyclerView: RecyclerView? = null
private var mEncryptionAlgorithmAdapter: ListRadioItemAdapter<EncryptionAlgorithm>? = null
private var algorithmSelected: EncryptionAlgorithm? = null
override fun onBindDialogView(view: View) {
@@ -38,32 +41,35 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
setExplanationText(R.string.encryption_explanation)
val recyclerView = view.findViewById<RecyclerView>(R.id.pref_dialog_list)
recyclerView.layoutManager = LinearLayoutManager(context)
mRecyclerView = view.findViewById(R.id.pref_dialog_list)
mRecyclerView?.layoutManager = LinearLayoutManager(context)
activity?.let { activity ->
val encryptionAlgorithmAdapter = ListRadioItemAdapter<EncryptionAlgorithm>(activity)
encryptionAlgorithmAdapter.setRadioItemSelectedCallback(this)
recyclerView.adapter = encryptionAlgorithmAdapter
mDatabase?.let { database ->
algorithmSelected = database.encryptionAlgorithm
encryptionAlgorithmAdapter.setItems(database.availableEncryptionAlgorithms, algorithmSelected)
}
mEncryptionAlgorithmAdapter = ListRadioItemAdapter(activity)
mEncryptionAlgorithmAdapter?.setRadioItemSelectedCallback(this)
mRecyclerView?.adapter = mEncryptionAlgorithmAdapter
}
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
algorithmSelected = database.encryptionAlgorithm
mEncryptionAlgorithmAdapter?.setItems(database.availableEncryptionAlgorithms, algorithmSelected)
}
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
if (algorithmSelected != null) {
val newAlgorithm = algorithmSelected
val oldAlgorithm = database.encryptionAlgorithm
database.encryptionAlgorithm = newAlgorithm
if (oldAlgorithm != null && newAlgorithm != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable)
saveEncryption(oldAlgorithm, newAlgorithm)
}
}
}

View File

@@ -26,6 +26,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
class DatabaseKeyDerivationPreferenceDialogFragmentCompat
@@ -33,6 +34,7 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
ListRadioItemAdapter.RadioItemSelectedCallback<KdfEngine> {
private var kdfEngineSelected: KdfEngine? = null
private var mKdfAdapter: ListRadioItemAdapter<KdfEngine>? = null
private var roundPreference: Preference? = null
private var memoryPreference: Preference? = null
private var parallelismPreference: Preference? = null
@@ -46,26 +48,30 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
recyclerView.layoutManager = LinearLayoutManager(context)
activity?.let { activity ->
val kdfAdapter = ListRadioItemAdapter<KdfEngine>(activity)
kdfAdapter.setRadioItemSelectedCallback(this)
recyclerView.adapter = kdfAdapter
mDatabase?.let { database ->
kdfEngineSelected = database.kdfEngine
kdfAdapter.setItems(database.availableKdfEngines, kdfEngineSelected)
}
mKdfAdapter = ListRadioItemAdapter(activity)
mKdfAdapter?.setRadioItemSelectedCallback(this)
recyclerView.adapter = mKdfAdapter
}
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
kdfEngineSelected = database.kdfEngine
mKdfAdapter?.setItems(database.availableKdfEngines, kdfEngineSelected)
}
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
if (database.allowKdfModification) {
val newKdfEngine = kdfEngineSelected
val oldKdfEngine = database.kdfEngine
if (newKdfEngine != null && oldKdfEngine != null) {
database.kdfEngine = newKdfEngine
mProgressDatabaseTaskProvider?.startDatabaseSaveKeyDerivation(oldKdfEngine, newKdfEngine, mDatabaseAutoSaveEnable)
saveKeyDerivation(oldKdfEngine, newKdfEngine)
}
}
}

View File

@@ -22,14 +22,18 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.max_history_items_summary)
mDatabase?.historyMaxItems?.let { maxItemsDatabase ->
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.historyMaxItems?.let { maxItemsDatabase ->
inputText = maxItemsDatabase.toString()
setSwitchAction({ isChecked ->
inputText = if (!isChecked) {
@@ -42,9 +46,10 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
}
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
var maxHistoryItems: Int = try {
inputText.toInt()
} catch (e: NumberFormatException) {
@@ -60,7 +65,7 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
// Remove all history items
database.removeOldestHistoryForEachEntry()
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems, mDatabaseAutoSaveEnable)
saveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems)
}
}
}
@@ -70,8 +75,8 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
const val DEFAULT_MAX_HISTORY_ITEMS = 10
const val NONE_MAX_HISTORY_ITEMS = -1
fun newInstance(key: String): MaxHistoryItemsPreferenceDialogFragmentCompat {
val fragment = MaxHistoryItemsPreferenceDialogFragmentCompat()
fun newInstance(key: String): DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat {
val fragment = DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle

View File

@@ -22,19 +22,23 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.utils.DataByte
class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseMaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
private var dataByte = DataByte(2L, DataByte.ByteFormat.MEBIBYTE)
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.max_history_size_summary)
mDatabase?.historyMaxSize?.let { maxItemsDatabase ->
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.historyMaxSize?.let { maxItemsDatabase ->
dataByte = DataByte(maxItemsDatabase, DataByte.ByteFormat.BYTE)
.toBetterByteFormat()
.toBetterByteFormat()
inputText = dataByte.number.toString()
if (dataByte.number >= 0) {
setUnitText(dataByte.format.stringId)
@@ -54,13 +58,13 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
}
showInputText(isChecked)
}, maxItemsDatabase > INFINITE_MAX_HISTORY_SIZE)
}
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
val maxHistorySize: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
@@ -80,7 +84,7 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
val oldMaxHistorySize = database.historyMaxSize
database.historyMaxSize = numberOfBytes
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, numberOfBytes, mDatabaseAutoSaveEnable)
saveMaxHistorySize(oldMaxHistorySize, numberOfBytes)
}
}
}
@@ -92,8 +96,8 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
private val INFINITE_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(INFINITE_MAX_HISTORY_SIZE, DataByte.ByteFormat.MEBIBYTE)
private val DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(6L, DataByte.ByteFormat.MEBIBYTE)
fun newInstance(key: String): MaxHistorySizePreferenceDialogFragmentCompat {
val fragment = MaxHistorySizePreferenceDialogFragmentCompat()
fun newInstance(key: String): DatabaseMaxHistorySizePreferenceDialogFragmentCompat {
val fragment = DatabaseMaxHistorySizePreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle

View File

@@ -22,27 +22,32 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.utils.DataByte
class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseMemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
private var dataByte = DataByte(MIN_MEMORY_USAGE, DataByte.ByteFormat.BYTE)
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.memory_usage_explanation)
val memoryBytes = mDatabase?.memoryUsage ?: MIN_MEMORY_USAGE
dataByte = DataByte(memoryBytes, DataByte.ByteFormat.BYTE)
.toBetterByteFormat()
inputText = dataByte.number.toString()
setUnitText(dataByte.format.stringId)
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
val memoryBytes = database.memoryUsage
dataByte = DataByte(memoryBytes, DataByte.ByteFormat.BYTE)
.toBetterByteFormat()
inputText = dataByte.number.toString()
setUnitText(dataByte.format.stringId)
}
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
var newMemoryUsage: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
@@ -61,7 +66,7 @@ class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
val oldMemoryUsage = database.memoryUsage
database.memoryUsage = numberOfBytes
mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, numberOfBytes, mDatabaseAutoSaveEnable)
saveMemoryUsage(oldMemoryUsage, numberOfBytes)
}
}
}
@@ -70,8 +75,8 @@ class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
const val MIN_MEMORY_USAGE = 1L
fun newInstance(key: String): MemoryUsagePreferenceDialogFragmentCompat {
val fragment = MemoryUsagePreferenceDialogFragmentCompat()
fun newInstance(key: String): DatabaseMemoryUsagePreferenceDialogFragmentCompat {
val fragment = DatabaseMemoryUsagePreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle

View File

@@ -20,23 +20,23 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.database.element.Database
class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
inputText = mDatabase?.name ?: ""
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
inputText = database?.name ?: ""
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
val newName = inputText
val oldName = database.name
database.name = newName
mProgressDatabaseTaskProvider?.startDatabaseSaveName(oldName, newName, mDatabaseAutoSaveEnable)
saveName(oldName, newName)
}
}
}

View File

@@ -22,19 +22,23 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.parallelism_explanation)
inputText = mDatabase?.parallelism?.toString() ?: MIN_PARALLELISM.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
inputText = database?.parallelism?.toString() ?: MIN_PARALLELISM.toString()
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
val parallelism: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
@@ -44,10 +48,7 @@ class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
val oldParallelism = database.parallelism
database.parallelism = parallelism
mProgressDatabaseTaskProvider?.startDatabaseSaveParallelism(
oldParallelism,
parallelism,
mDatabaseAutoSaveEnable)
saveParallelism(oldParallelism, parallelism)
}
}
}
@@ -56,8 +57,8 @@ class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
const val MIN_PARALLELISM = 1L
fun newInstance(key: String): ParallelismPreferenceDialogFragmentCompat {
val fragment = ParallelismPreferenceDialogFragmentCompat()
fun newInstance(key: String): DatabaseParallelismPreferenceDialogFragmentCompat {
val fragment = DatabaseParallelismPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle

View File

@@ -24,6 +24,7 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
@@ -31,6 +32,7 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
: DatabaseSavePreferenceDialogFragmentCompat(),
ListRadioItemAdapter.RadioItemSelectedCallback<Group> {
private var mGroupsAdapter: ListRadioItemAdapter<Group>? = null
private var mGroupRecycleBin: Group? = null
override fun onBindDialogView(view: View) {
@@ -40,14 +42,17 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
recyclerView.layoutManager = LinearLayoutManager(context)
activity?.let { activity ->
val groupsAdapter = ListRadioItemAdapter<Group>(activity)
groupsAdapter.setRadioItemSelectedCallback(this)
recyclerView.adapter = groupsAdapter
mGroupsAdapter = ListRadioItemAdapter(activity)
mGroupsAdapter?.setRadioItemSelectedCallback(this)
recyclerView.adapter = mGroupsAdapter
}
}
mDatabase?.let { database ->
mGroupRecycleBin = database.recycleBin
groupsAdapter.setItems(database.getAllGroupsWithoutRoot(), mGroupRecycleBin)
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
mGroupRecycleBin = database.recycleBin
mGroupsAdapter?.setItems(database.getAllGroupsWithoutRoot(), mGroupRecycleBin)
}
}
@@ -55,18 +60,15 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
mGroupRecycleBin = item
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
if (database.allowConfigurableRecycleBin) {
val oldGroup = database.recycleBin
val newGroup = mGroupRecycleBin
database.setRecycleBin(newGroup)
mProgressDatabaseTaskProvider?.startDatabaseSaveRecycleBin(
oldGroup,
newGroup,
mDatabaseAutoSaveEnable
)
saveRecycleBin(oldGroup, newGroup)
}
}
}

View File

@@ -23,6 +23,7 @@ import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
@@ -36,10 +37,11 @@ class DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat : DatabaseSavePre
}.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
mDatabase?.let { _ ->
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
database?.let {
if (positiveResult) {
mProgressDatabaseTaskProvider?.startDatabaseRemoveUnlinkedData(mDatabaseAutoSaveEnable)
removeUnlinkedData()
}
}
}

View File

@@ -23,19 +23,23 @@ import android.os.Bundle
import android.view.View
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseRoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
explanationText = getString(R.string.rounds_explanation)
inputText = mDatabase?.numberKeyEncryptionRounds?.toString() ?: MIN_ITERATIONS.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
inputText = database?.numberKeyEncryptionRounds?.toString() ?: MIN_ITERATIONS.toString()
}
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
var rounds: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
@@ -54,7 +58,7 @@ class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmen
database.numberKeyEncryptionRounds = Long.MAX_VALUE
}
mProgressDatabaseTaskProvider?.startDatabaseSaveIterations(oldRounds, rounds, mDatabaseAutoSaveEnable)
saveIterations(oldRounds, rounds)
}
}
}
@@ -63,8 +67,8 @@ class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmen
const val MIN_ITERATIONS = 1L
fun newInstance(key: String): RoundsPreferenceDialogFragmentCompat {
val fragment = RoundsPreferenceDialogFragmentCompat()
fun newInstance(key: String): DatabaseRoundsPreferenceDialogFragmentCompat {
val fragment = DatabaseRoundsPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle

View File

@@ -20,28 +20,134 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.content.Context
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import android.os.Bundle
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
abstract class DatabaseSavePreferenceDialogFragmentCompat
: InputPreferenceDialogFragmentCompat(), DatabaseRetrieval {
protected var mDatabaseAutoSaveEnable = true
protected var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mDatabaseAutoSaveEnable = true
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
private var mDatabase: Database? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// Attach dialog thread to start action
if (context is SettingsActivity) {
mProgressDatabaseTaskProvider = context.mProgressDatabaseTaskProvider
}
this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
}
override fun onDetach() {
mProgressDatabaseTaskProvider = null
super.onDetach()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabaseViewModel.database.observe(this) { database ->
onDatabaseRetrieved(database)
}
}
override fun onResume() {
super.onResume()
onDatabaseRetrieved(mDatabase)
}
override fun onDatabaseRetrieved(database: Database?) {
this.mDatabase = database
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// Not used
}
override fun onDialogClosed(positiveResult: Boolean) {
onDialogClosed(mDatabase, positiveResult)
}
open fun onDialogClosed(database: Database?, positiveResult: Boolean) {
// To inherit to save element in database
}
protected fun saveColor(oldColor: String,
newColor: String) {
mDatabaseViewModel.saveColor(oldColor, newColor, mDatabaseAutoSaveEnable)
}
protected fun saveCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) {
mDatabaseViewModel.saveCompression(oldCompression, newCompression, mDatabaseAutoSaveEnable)
}
protected fun saveDefaultUsername(oldUsername: String,
newUsername: String) {
mDatabaseViewModel.saveDefaultUsername(oldUsername, newUsername, mDatabaseAutoSaveEnable)
}
protected fun saveDescription(oldDescription: String,
newDescription: String) {
mDatabaseViewModel.saveDescription(oldDescription, newDescription, mDatabaseAutoSaveEnable)
}
protected fun saveEncryption(oldEncryption: EncryptionAlgorithm,
newEncryptionAlgorithm: EncryptionAlgorithm) {
mDatabaseViewModel.saveEncryption(oldEncryption, newEncryptionAlgorithm, mDatabaseAutoSaveEnable)
}
protected fun saveKeyDerivation(oldKeyDerivation: KdfEngine,
newKeyDerivation: KdfEngine) {
mDatabaseViewModel.saveKeyDerivation(oldKeyDerivation, newKeyDerivation, mDatabaseAutoSaveEnable)
}
protected fun saveName(oldName: String,
newName: String) {
mDatabaseViewModel.saveName(oldName, newName, mDatabaseAutoSaveEnable)
}
protected fun saveRecycleBin(oldGroup: Group?,
newGroup: Group?) {
mDatabaseViewModel.saveRecycleBin(oldGroup, newGroup, mDatabaseAutoSaveEnable)
}
protected fun removeUnlinkedData() {
mDatabaseViewModel.removeUnlinkedData(mDatabaseAutoSaveEnable)
}
protected fun saveTemplatesGroup(oldGroup: Group?,
newGroup: Group?) {
mDatabaseViewModel.saveTemplatesGroup(oldGroup, newGroup, mDatabaseAutoSaveEnable)
}
protected fun saveMaxHistoryItems(oldNumber: Int,
newNumber: Int) {
mDatabaseViewModel.saveMaxHistoryItems(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveMaxHistorySize(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveMaxHistorySize(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveMemoryUsage(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveMemoryUsage(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveParallelism(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveParallelism(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveIterations(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveIterations(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
companion object {

View File

@@ -24,6 +24,7 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
@@ -31,6 +32,7 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
: DatabaseSavePreferenceDialogFragmentCompat(),
ListRadioItemAdapter.RadioItemSelectedCallback<Group> {
private var mGroupsAdapter: ListRadioItemAdapter<Group>? = null
private var mGroupTemplates: Group? = null
override fun onBindDialogView(view: View) {
@@ -40,14 +42,17 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
recyclerView.layoutManager = LinearLayoutManager(context)
activity?.let { activity ->
val groupsAdapter = ListRadioItemAdapter<Group>(activity)
groupsAdapter.setRadioItemSelectedCallback(this)
recyclerView.adapter = groupsAdapter
mGroupsAdapter = ListRadioItemAdapter(activity)
mGroupsAdapter?.setRadioItemSelectedCallback(this)
recyclerView.adapter = mGroupsAdapter
}
}
mDatabase?.let { database ->
mGroupTemplates = database.templatesGroup
groupsAdapter.setItems(database.getAllGroupsWithoutRoot(), mGroupTemplates)
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
mGroupTemplates = database.templatesGroup
mGroupsAdapter?.setItems(database.getAllGroupsWithoutRoot(), mGroupTemplates)
}
}
@@ -55,18 +60,15 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
mGroupTemplates = item
}
override fun onDialogClosed(positiveResult: Boolean) {
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
mDatabase?.let { database ->
database?.let {
if (database.allowConfigurableTemplatesGroup) {
val oldGroup = database.templatesGroup
val newGroup = mGroupTemplates
database.setTemplatesGroup(newGroup)
mProgressDatabaseTaskProvider?.startDatabaseSaveTemplatesGroup(
oldGroup,
newGroup,
mDatabaseAutoSaveEnable
)
saveTemplatesGroup(oldGroup, newGroup)
}
}
}

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
@@ -30,7 +29,6 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceDialogFragmentCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
@@ -41,14 +39,6 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
private var mOnInputTextEditorActionListener: TextView.OnEditorActionListener? = null
protected var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
}
var inputText: String
get() = this.inputTextView?.text?.toString() ?: ""
set(inputText) {

View File

@@ -25,7 +25,6 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION
@@ -39,7 +38,7 @@ object TimeoutHelper {
private const val TAG = "TimeoutHelper"
private var lastAppTimeoutRecord: Long? = null
var temporarilyDisableTimeout = false
var temporarilyDisableLock = false
private set
private fun getLockPendingIntent(context: Context): PendingIntent {
@@ -53,8 +52,8 @@ object TimeoutHelper {
* Start the lock timer by creating an alarm,
* if the method is recalled with a previous lock timer pending, the previous one is deleted
*/
private fun startLockTimer(context: Context, database: Database) {
if (database.loaded) {
private fun startLockTimer(context: Context, databaseLoaded: Boolean) {
if (databaseLoaded) {
val timeout = PreferencesUtil.getAppTimeout(context)
if (timeout != NEVER) {
// No timeout don't start timeout service
@@ -84,14 +83,14 @@ object TimeoutHelper {
/**
* Record the current time, to check it later with checkTime and start a new lock timer
*/
fun recordTime(context: Context, database: Database) {
fun recordTime(context: Context, databaseLoaded: Boolean) {
// To prevent spam registration, record after at least 2 seconds
if (lastAppTimeoutRecord == null
|| lastAppTimeoutRecord!! + 2000 <= System.currentTimeMillis()) {
Log.d(TAG, "Record app timeout")
// Record timeout time in case timeout service is killed
PreferencesUtil.saveCurrentTime(context)
startLockTimer(context, database)
startLockTimer(context, databaseLoaded)
lastAppTimeoutRecord = System.currentTimeMillis()
}
}
@@ -103,7 +102,7 @@ object TimeoutHelper {
*/
fun checkTime(context: Context, timeoutAction: (() -> Unit)? = null): Boolean {
// No effect if temporarily disable
if (temporarilyDisableTimeout)
if (temporarilyDisableLock)
return true
// Check whether the timeout has expired
@@ -147,12 +146,10 @@ object TimeoutHelper {
* Check the time previously record then, if timeout lock the database, else reset the timer
*/
fun checkTimeAndLockIfTimeoutOrResetTimeout(context: Context,
database: Database?,
databaseLoaded: Boolean,
action: (() -> Unit)? = null) {
if (checkTimeAndLockIfTimeout(context)) {
database?.let {
recordTime(context, it)
}
recordTime(context, databaseLoaded)
action?.invoke()
}
}
@@ -161,13 +158,13 @@ object TimeoutHelper {
* Temporarily disable timeout, checkTime() function always return true
*/
fun temporarilyDisableTimeout() {
temporarilyDisableTimeout = true
temporarilyDisableLock = true
}
/**
* Release the temporarily disable timeout
*/
fun releaseTemporarilyDisableTimeout() {
temporarilyDisableTimeout = false
temporarilyDisableLock = false
}
}

View File

@@ -31,7 +31,7 @@ import android.os.Build
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -51,7 +51,7 @@ class LockReceiver(var lockAction: () -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// If allowed, lock and exit
if (!TimeoutHelper.temporarilyDisableTimeout) {
if (!TimeoutHelper.temporarilyDisableLock) {
intent.action?.let {
when (it) {
Intent.ACTION_SCREEN_ON -> {
@@ -125,10 +125,10 @@ fun Context.unregisterLockReceiver(lockReceiver: LockReceiver?) {
}
}
fun Context.closeDatabase() {
fun Context.closeDatabase(database: Database?) {
// Stop the Magikeyboard service
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
MagikIME.removeEntry(this)
MagikeyboardService.removeEntry(this)
// Stop the notification service
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
cancelAll()
}
// Clear data
Database.getInstance().clearAndClose(this)
database?.clearAndClose(this)
}

View File

@@ -28,7 +28,6 @@ import android.view.MenuItem
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AboutActivity
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper.READ_ONLY_DEFAULT
import com.kunzisoft.keepass.settings.SettingsActivity
object MenuUtil {
@@ -52,7 +51,6 @@ object MenuUtil {
*/
fun onDefaultMenuOptionsItemSelected(activity: Activity,
item: MenuItem,
readOnly: Boolean = READ_ONLY_DEFAULT,
timeoutEnable: Boolean = false) {
when (item.itemId) {
R.id.menu_contribute -> {
@@ -60,7 +58,7 @@ object MenuUtil {
}
R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(activity, readOnly, timeoutEnable)
SettingsActivity.launch(activity, timeoutEnable)
}
R.id.menu_about -> {
val intent = Intent(activity, AboutActivity::class.java)

View File

@@ -28,8 +28,8 @@ import android.os.Build
import androidx.annotation.RequiresApi
import android.util.AttributeSet
import com.kunzisoft.keepass.magikeyboard.MagikIME.Companion.KEY_BACK_KEYBOARD
import com.kunzisoft.keepass.magikeyboard.MagikIME.Companion.KEY_CHANGE_KEYBOARD
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService.Companion.KEY_BACK_KEYBOARD
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService.Companion.KEY_CHANGE_KEYBOARD
class MagikeyboardView : KeyboardView {

View File

@@ -71,6 +71,16 @@ abstract class TemplateAbstractView<
// To show icon image
var populateIconMethod: ((ImageView, IconImage) -> Unit)? = null
set(value) {
field = value
refreshIcon()
}
fun refreshIcon() {
mEntryInfo?.icon?.let {
populateIconMethod?.invoke(entryIconView, it)
}
}
fun setTemplate(template: Template?) {
if (mTemplate != template) {

View File

@@ -193,9 +193,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
}
override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<FieldId> {
mEntryInfo?.let { entryInfo ->
setIcon(entryInfo.icon)
}
refreshIcon()
return super.populateViewsWithEntryInfo(showEmptyFields)
}

View File

@@ -0,0 +1,205 @@
package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.tasks.ActionRunnable
class DatabaseViewModel: ViewModel() {
val database : LiveData<Database?> get() = _database
private val _database = MutableLiveData<Database?>()
val actionFinished : LiveData<ActionResult> get() = _actionFinished
private val _actionFinished = SingleLiveEvent<ActionResult>()
val saveDatabase : LiveData<Boolean> get() = _saveDatabase
private val _saveDatabase = SingleLiveEvent<Boolean>()
val reloadDatabase : LiveData<Boolean> get() = _reloadDatabase
private val _reloadDatabase = SingleLiveEvent<Boolean>()
val saveName : LiveData<SuperString> get() = _saveName
private val _saveName = SingleLiveEvent<SuperString>()
val saveDescription : LiveData<SuperString> get() = _saveDescription
private val _saveDescription = SingleLiveEvent<SuperString>()
val saveDefaultUsername : LiveData<SuperString> get() = _saveDefaultUsername
private val _saveDefaultUsername = SingleLiveEvent<SuperString>()
val saveColor : LiveData<SuperString> get() = _saveColor
private val _saveColor = SingleLiveEvent<SuperString>()
val saveCompression : LiveData<SuperCompression> get() = _saveCompression
private val _saveCompression = SingleLiveEvent<SuperCompression>()
val removeUnlinkData : LiveData<Boolean> get() = _removeUnlinkData
private val _removeUnlinkData = SingleLiveEvent<Boolean>()
val saveRecycleBin : LiveData<SuperGroup> get() = _saveRecycleBin
private val _saveRecycleBin = SingleLiveEvent<SuperGroup>()
val saveTemplatesGroup : LiveData<SuperGroup> get() = _saveTemplatesGroup
private val _saveTemplatesGroup = SingleLiveEvent<SuperGroup>()
val saveMaxHistoryItems : LiveData<SuperInt> get() = _saveMaxHistoryItems
private val _saveMaxHistoryItems = SingleLiveEvent<SuperInt>()
val saveMaxHistorySize : LiveData<SuperLong> get() = _saveMaxHistorySize
private val _saveMaxHistorySize = SingleLiveEvent<SuperLong>()
val saveEncryption : LiveData<SuperEncryption> get() = _saveEncryption
private val _saveEncryption = SingleLiveEvent<SuperEncryption>()
val saveKeyDerivation : LiveData<SuperKeyDerivation> get() = _saveKeyDerivation
private val _saveKeyDerivation = SingleLiveEvent<SuperKeyDerivation>()
val saveIterations : LiveData<SuperLong> get() = _saveIterations
private val _saveIterations = SingleLiveEvent<SuperLong>()
val saveMemoryUsage : LiveData<SuperLong> get() = _saveMemoryUsage
private val _saveMemoryUsage = SingleLiveEvent<SuperLong>()
val saveParallelism : LiveData<SuperLong> get() = _saveParallelism
private val _saveParallelism = SingleLiveEvent<SuperLong>()
fun defineDatabase(database: Database?) {
this._database.value = database
}
fun onActionFinished(database: Database,
actionTask: String,
result: ActionRunnable.Result) {
this._actionFinished.value = ActionResult(database, actionTask, result)
}
fun saveDatabase(save: Boolean) {
_saveDatabase.value = save
}
fun reloadDatabase(fixDuplicateUuid: Boolean) {
_reloadDatabase.value = fixDuplicateUuid
}
fun saveName(oldValue: String,
newValue: String,
save: Boolean) {
_saveName.value = SuperString(oldValue, newValue, save)
}
fun saveDescription(oldValue: String,
newValue: String,
save: Boolean) {
_saveDescription.value = SuperString(oldValue, newValue, save)
}
fun saveDefaultUsername(oldValue: String,
newValue: String,
save: Boolean) {
_saveDefaultUsername.value = SuperString(oldValue, newValue, save)
}
fun saveColor(oldValue: String,
newValue: String,
save: Boolean) {
_saveColor.value = SuperString(oldValue, newValue, save)
}
fun saveCompression(oldValue: CompressionAlgorithm,
newValue: CompressionAlgorithm,
save: Boolean) {
_saveCompression.value = SuperCompression(oldValue, newValue, save)
}
fun removeUnlinkedData(save: Boolean) {
_removeUnlinkData.value = save
}
fun saveRecycleBin(oldValue: Group?,
newValue: Group?,
save: Boolean) {
_saveRecycleBin.value = SuperGroup(oldValue, newValue, save)
}
fun saveTemplatesGroup(oldValue: Group?,
newValue: Group?,
save: Boolean) {
_saveTemplatesGroup.value = SuperGroup(oldValue, newValue, save)
}
fun saveMaxHistoryItems(oldValue: Int,
newValue: Int,
save: Boolean) {
_saveMaxHistoryItems.value = SuperInt(oldValue, newValue, save)
}
fun saveMaxHistorySize(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveMaxHistorySize.value = SuperLong(oldValue, newValue, save)
}
fun saveEncryption(oldValue: EncryptionAlgorithm,
newValue: EncryptionAlgorithm,
save: Boolean) {
_saveEncryption.value = SuperEncryption(oldValue, newValue, save)
}
fun saveKeyDerivation(oldValue: KdfEngine,
newValue: KdfEngine,
save: Boolean) {
_saveKeyDerivation.value = SuperKeyDerivation(oldValue, newValue, save)
}
fun saveIterations(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveIterations.value = SuperLong(oldValue, newValue, save)
}
fun saveMemoryUsage(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveMemoryUsage.value = SuperLong(oldValue, newValue, save)
}
fun saveParallelism(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveParallelism.value = SuperLong(oldValue, newValue, save)
}
data class ActionResult(val database: Database,
val actionTask: String,
val result: ActionRunnable.Result)
data class SuperString(val oldValue: String,
val newValue: String,
val save: Boolean)
data class SuperInt(val oldValue: Int,
val newValue: Int,
val save: Boolean)
data class SuperLong(val oldValue: Long,
val newValue: Long,
val save: Boolean)
data class SuperCompression(val oldValue: CompressionAlgorithm,
val newValue: CompressionAlgorithm,
val save: Boolean)
data class SuperEncryption(val oldValue: EncryptionAlgorithm,
val newValue: EncryptionAlgorithm,
val save: Boolean)
data class SuperKeyDerivation(val oldValue: KdfEngine,
val newValue: KdfEngine,
val save: Boolean)
data class SuperGroup(val oldValue: Group?,
val newValue: Group?,
val save: Boolean)
}

View File

@@ -3,7 +3,6 @@ package com.kunzisoft.keepass.viewmodels
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.app.database.IOActionTask
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
@@ -12,26 +11,18 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.view.DataDate
import com.kunzisoft.keepass.view.DataTime
import java.util.*
class EntryEditViewModel: ViewModel() {
private val mDatabase: Database? = Database.getInstance()
private var mParent : Group? = null
private var mEntry : Entry? = null
private var mIsTemplate: Boolean = false
class EntryEditViewModel: NodeEditViewModel() {
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
private val _entryInfo = MutableLiveData<EntryInfo>()
val requestEntryInfoUpdate : LiveData<Void?> get() = _requestEntryInfoUpdate
private val _requestEntryInfoUpdate = SingleLiveEvent<Void?>()
val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate
private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>()
val onEntrySaved : LiveData<EntrySave> get() = _onEntrySaved
private val _onEntrySaved = SingleLiveEvent<EntrySave>()
@@ -40,11 +31,6 @@ class EntryEditViewModel: ViewModel() {
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
private val _onTemplateChanged = SingleLiveEvent<Template>()
val requestIconSelection : LiveData<IconImage> get() = _requestIconSelection
private val _requestIconSelection = SingleLiveEvent<IconImage>()
val onIconSelected : LiveData<IconImage> get() = _onIconSelected
private val _onIconSelected = SingleLiveEvent<IconImage>()
val requestPasswordSelection : LiveData<Field> get() = _requestPasswordSelection
private val _requestPasswordSelection = SingleLiveEvent<Field>()
val onPasswordSelected : LiveData<Field> get() = _onPasswordSelected
@@ -57,13 +43,6 @@ class EntryEditViewModel: ViewModel() {
val onCustomFieldError : LiveData<Void?> get() = _onCustomFieldError
private val _onCustomFieldError = SingleLiveEvent<Void?>()
val requestDateTimeSelection : LiveData<DateInstant> get() = _requestDateTimeSelection
private val _requestDateTimeSelection = SingleLiveEvent<DateInstant>()
val onDateSelected : LiveData<DataDate> get() = _onDateSelected
private val _onDateSelected = SingleLiveEvent<DataDate>()
val onTimeSelected : LiveData<DataTime> get() = _onTimeSelected
private val _onTimeSelected = SingleLiveEvent<DataTime>()
val requestSetupOtp : LiveData<Void?> get() = _requestSetupOtp
private val _requestSetupOtp = SingleLiveEvent<Void?>()
val onOtpCreated : LiveData<OtpElement> get() = _onOtpCreated
@@ -80,41 +59,16 @@ class EntryEditViewModel: ViewModel() {
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
fun initializeEntryToUpdate(entryId: NodeId<UUID>,
registerInfo: RegisterInfo?,
searchInfo: SearchInfo?) {
IOActionTask(
{
mEntry = getEntry(entryId)
mIsTemplate = isEntryATemplate()
},
{
loadTemplateEntry(registerInfo, searchInfo)
}
).execute()
}
fun initializeEntryToCreate(parentId: NodeId<*>,
registerInfo: RegisterInfo?,
searchInfo: SearchInfo?) {
fun loadTemplateEntry(database: Database,
entry: Entry?,
isTemplate: Boolean,
registerInfo: RegisterInfo?,
searchInfo: SearchInfo?) {
IOActionTask(
{
mParent = getGroup(parentId)
mEntry = createEntry(mParent)
mIsTemplate = isEntryATemplate()
},
{
loadTemplateEntry(registerInfo, searchInfo)
}
).execute()
}
private fun loadTemplateEntry(registerInfo: RegisterInfo?,
searchInfo: SearchInfo?) {
IOActionTask(
{
val templates = getTemplates()
val entryTemplate = getEntryTemplate()
val templates = database.getTemplates(isTemplate)
val entryTemplate = entry?.let { database.getTemplate(it) } ?: Template.STANDARD
TemplatesLoad(templates, entryTemplate)
},
{ templatesLoad ->
@@ -124,7 +78,24 @@ class EntryEditViewModel: ViewModel() {
IOActionTask(
{
loadEntryInfo(registerInfo, searchInfo)
var entryInfo: EntryInfo? = null
// Decode the entry / load entry info
entry?.let {
database.decodeEntryWithTemplateConfiguration(it).let { entry ->
// Load entry info
entry.getEntryInfo(database, true).let { tempEntryInfo ->
// Retrieve data from registration
(registerInfo?.searchInfo ?: searchInfo)?.let { tempSearchInfo ->
tempEntryInfo.saveSearchInfo(database, tempSearchInfo)
}
registerInfo?.let { regInfo ->
tempEntryInfo.saveRegisterInfo(database, regInfo)
}
entryInfo = tempEntryInfo
}
}
}
entryInfo
},
{ entryInfo ->
_entryInfo.value = entryInfo
@@ -134,125 +105,46 @@ class EntryEditViewModel: ViewModel() {
).execute()
}
private fun getEntry(entryId: NodeId<UUID>): Entry? {
// Create an Entry copy to modify from the database entry
val tempEntry = mDatabase?.getEntryById(entryId)
// Retrieve the parent
tempEntry?.let { entry ->
// If no parent, add root group as parent
if (entry.parent == null) {
entry.parent = mDatabase?.rootGroup
}
}
return tempEntry
}
private fun getGroup(groupId: NodeId<*>): Group? {
return mDatabase?.getGroupById(groupId)
}
private fun createEntry(parentGroup: Group?): Entry? {
return mDatabase?.createEntry()?.apply {
// Add the default icon from parent if not a folder
val parentIcon = parentGroup?.icon
// Set default icon
if (parentIcon != null) {
if (parentIcon.custom.isUnknown
&& parentIcon.standard.id != IconImageStandard.FOLDER_ID) {
icon = IconImage(parentIcon.standard)
}
if (!parentIcon.custom.isUnknown) {
icon = IconImage(parentIcon.custom)
}
}
// Set default username
username = mDatabase.defaultUsername
// Warning only the entry recognize is parent, parent don't yet recognize the new entry
// Useful to recognize child state (ie: entry is a template)
parent = parentGroup
}
}
private fun isEntryATemplate(): Boolean {
// Define is current entry is a template (in direct template group)
return mDatabase?.entryIsTemplate(mEntry) ?: false
}
private fun getTemplates(): List<Template> {
return mDatabase?.getTemplates(mIsTemplate) ?: listOf()
}
private fun getEntryTemplate(): Template {
return mEntry?.let { mDatabase?.getTemplate(it) } ?: Template.STANDARD
}
private fun loadEntryInfo(registerInfo: RegisterInfo?, searchInfo: SearchInfo?): EntryInfo? {
// Decode the entry
mEntry?.let {
mDatabase?.decodeEntryWithTemplateConfiguration(it)?.let { entry ->
// Load entry info
entry.getEntryInfo(mDatabase, true).let { tempEntryInfo ->
// Retrieve data from registration
(registerInfo?.searchInfo ?: searchInfo)?.let { tempSearchInfo ->
tempEntryInfo.saveSearchInfo(mDatabase, tempSearchInfo)
}
registerInfo?.let { regInfo ->
tempEntryInfo.saveRegisterInfo(mDatabase, regInfo)
}
internalUpdateEntryInfo(tempEntryInfo)
return tempEntryInfo
}
}
}
return null
}
fun changeTemplate(template: Template) {
if (_onTemplateChanged.value != template) {
_onTemplateChanged.value = template
}
}
// TODO Move
fun entryIsTemplate(): Boolean {
return mIsTemplate
fun requestEntryInfoUpdate(database: Database?, entry: Entry?, parent: Group?) {
_requestEntryInfoUpdate.value = EntryUpdate(database, entry, parent)
}
fun requestEntryInfoUpdate() {
_requestEntryInfoUpdate.call()
}
fun saveEntryInfo(entryInfo: EntryInfo) {
fun saveEntryInfo(database: Database?, entry: Entry?, parent: Group?, entryInfo: EntryInfo) {
IOActionTask(
{
internalUpdateEntryInfo(entryInfo)
mEntry?.let { oldEntry ->
removeTempAttachmentsNotCompleted(entryInfo)
entry?.let { oldEntry ->
// Create a clone
var newEntry = Entry(oldEntry)
// Build info
newEntry.setEntryInfo(mDatabase, entryInfo)
newEntry.setEntryInfo(database, entryInfo)
// Encode entry properties for template
_onTemplateChanged.value?.let { template ->
newEntry =
mDatabase?.encodeEntryWithTemplateConfiguration(newEntry, template)
database?.encodeEntryWithTemplateConfiguration(newEntry, template)
?: newEntry
}
// Delete temp attachment if not used
mTempAttachments.forEach { tempAttachmentState ->
val tempAttachment = tempAttachmentState.attachment
mDatabase?.attachmentPool?.let { binaryPool ->
database?.attachmentPool?.let { binaryPool ->
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
mDatabase.removeAttachmentIfNotUsed(tempAttachment)
database.removeAttachmentIfNotUsed(tempAttachment)
}
}
}
// Return entry to save
EntrySave(oldEntry, newEntry, mParent)
EntrySave(oldEntry, newEntry, parent)
}
},
{ entrySave ->
@@ -263,7 +155,7 @@ class EntryEditViewModel: ViewModel() {
).execute()
}
private fun internalUpdateEntryInfo(entryInfo: EntryInfo) {
private fun removeTempAttachmentsNotCompleted(entryInfo: EntryInfo) {
// Do not save entry in upload progression
mTempAttachments.forEach { attachmentState ->
if (attachmentState.streamDirection == StreamDirection.UPLOAD) {
@@ -284,14 +176,6 @@ class EntryEditViewModel: ViewModel() {
}
}
fun requestIconSelection(oldIconImage: IconImage) {
_requestIconSelection.value = oldIconImage
}
fun selectIcon(iconImage: IconImage) {
_onIconSelected.value = iconImage
}
fun requestPasswordSelection(passwordField: Field) {
_requestPasswordSelection.value = passwordField
}
@@ -320,18 +204,6 @@ class EntryEditViewModel: ViewModel() {
_onCustomFieldError.call()
}
fun requestDateTimeSelection(dateInstant: DateInstant) {
_requestDateTimeSelection.value = dateInstant
}
fun selectDate(year: Int, month: Int, day: Int) {
_onDateSelected.value = DataDate(year, month, day)
}
fun selectTime(hours: Int, minutes: Int) {
_onTimeSelected.value = DataTime(hours, minutes)
}
fun setupOtp() {
_requestSetupOtp.call()
}
@@ -353,8 +225,11 @@ class EntryEditViewModel: ViewModel() {
}
fun onAttachmentAction(entryAttachmentState: EntryAttachmentState?) {
if (entryAttachmentState?.downloadState == AttachmentState.START) {
// Add in temp list
// Add in temp list
if (mTempAttachments.contains(entryAttachmentState)) {
mTempAttachments.remove(entryAttachmentState)
}
if (entryAttachmentState != null) {
mTempAttachments.add(entryAttachmentState)
}
_onAttachmentAction.value = entryAttachmentState
@@ -365,6 +240,7 @@ class EntryEditViewModel: ViewModel() {
}
data class TemplatesLoad(val templates: List<Template>, val defaultTemplate: Template)
data class EntryUpdate(val database: Database?, val entry: Entry?, val parent: Group?)
data class EntrySave(val oldEntry: Entry, val newEntry: Entry, val parent: Group?)
data class FieldEdition(val oldField: Field?, val newField: Field?)
data class AttachmentBuild(val attachmentToUploadUri: Uri, val fileName: String)

View File

@@ -1,3 +1,22 @@
/*
* 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.viewmodels
import androidx.lifecycle.LiveData
@@ -6,7 +25,6 @@ import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.app.database.IOActionTask
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.template.Template
@@ -18,22 +36,21 @@ import java.util.*
class EntryViewModel: ViewModel() {
private val mDatabase: Database? = Database.getInstance()
private var mEntryTemplate: Template? = null
private var mEntry: Entry? = null
private var mLastEntryVersion: Entry? = null
private var mHistoryPosition: Int = -1
val template : LiveData<Template> get() = _template
private val _template = MutableLiveData<Template>()
val mainEntryId : LiveData<NodeId<UUID>?> get() = _mainEntryId
private val _mainEntryId = MutableLiveData<NodeId<UUID>?>()
val historyPosition : LiveData<Int> get() = _historyPosition
private val _historyPosition = MutableLiveData<Int>()
val url : LiveData<String?> get() = _url
private val _url = MutableLiveData<String?>()
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
private val _entryInfo = MutableLiveData<EntryInfo>()
val entryIsHistory : LiveData<Boolean> get() = _entryIsHistory
private val _entryIsHistory = MutableLiveData<Boolean>()
val entryHistory : LiveData<List<EntryInfo>> get() = _entryHistory
private val _entryHistory = MutableLiveData<List<EntryInfo>>()
@@ -48,79 +65,66 @@ class EntryViewModel: ViewModel() {
val historySelected : LiveData<EntryHistory> get() = _historySelected
private val _historySelected = SingleLiveEvent<EntryHistory>()
fun loadEntry(entryId: NodeId<UUID>, historyPosition: Int) {
IOActionTask(
{
// Manage current version and history
mLastEntryVersion = mDatabase?.getEntryById(entryId)
fun loadEntry(database: Database?, entryId: NodeId<UUID>?, historyPosition: Int) {
if (entryId != null) {
IOActionTask(
{
database?.getEntryById(entryId)
},
{ mainEntry ->
// Manage current version and history
_mainEntryId.value = mainEntry?.nodeId
_historyPosition.value = historyPosition
mEntry = if (historyPosition > -1) {
mLastEntryVersion?.getHistory()?.get(historyPosition)
} else {
mLastEntryVersion
}
mEntryTemplate = mEntry?.let {
mDatabase?.getTemplate(it)
} ?: Template.STANDARD
mHistoryPosition = historyPosition
// To simplify template field visibility
mEntry?.let { entry ->
// Add mLastEntryVersion to check the parent and define the template state
mDatabase?.decodeEntryWithTemplateConfiguration(entry, mLastEntryVersion)?.let {
// To update current modification time
it.touch(modified = false, touchParents = false)
// Build history info
val entryInfoHistory = it.getHistory().map { entryHistory ->
entryHistory.getEntryInfo(mDatabase)
}
EntryInfoHistory(
mEntryTemplate ?: Template.STANDARD,
it.getEntryInfo(mDatabase),
entryInfoHistory
)
val currentEntry = if (historyPosition > -1) {
mainEntry?.getHistory()?.get(historyPosition)
} else {
mainEntry
}
}
},
{ entryInfoHistory ->
if (entryInfoHistory != null) {
_template.value = entryInfoHistory.template
_entryInfo.value = entryInfoHistory.entryInfo
_entryIsHistory.value = mHistoryPosition != -1
_entryHistory.value = entryInfoHistory.entryHistory
}
}
).execute()
}
_url.value = currentEntry?.url
fun updateEntry() {
mEntry?.nodeId?.let { nodeId ->
loadEntry(nodeId, mHistoryPosition)
IOActionTask(
{
val entryTemplate = currentEntry?.let {
database?.getTemplate(it)
} ?: Template.STANDARD
// To simplify template field visibility
currentEntry?.let { entry ->
// Add mainEntry to check the parent and define the template state
database?.decodeEntryWithTemplateConfiguration(entry, mainEntry)
?.let {
// To update current modification time
it.touch(modified = false, touchParents = false)
// Build history info
val entryInfoHistory = it.getHistory().map { entryHistory ->
entryHistory.getEntryInfo(database)
}
EntryInfoHistory(
entryTemplate,
it.getEntryInfo(database),
entryInfoHistory
)
}
}
},
{ entryInfoHistory ->
if (entryInfoHistory != null) {
_template.value = entryInfoHistory.template
_entryInfo.value = entryInfoHistory.entryInfo
_entryHistory.value = entryInfoHistory.entryHistory
}
}
).execute()
}
).execute()
}
}
// TODO Remove
fun getEntry(): Entry? {
return mEntry
}
// TODO Remove
fun getMainEntry(): Entry? {
return mLastEntryVersion
}
// TODO Remove
fun getEntryHistoryPosition(): Int {
return mHistoryPosition
}
// TODO Remove
fun getEntryIsHistory(): Boolean {
return entryIsHistory.value ?: false
fun updateEntry(database: Database?) {
loadEntry(database, _mainEntryId.value, _historyPosition.value ?: -1)
}
fun onOtpElementUpdated(optElement: OtpElement?) {

View File

@@ -0,0 +1,29 @@
package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.kunzisoft.keepass.model.GroupInfo
class GroupEditViewModel: NodeEditViewModel() {
val groupNamesNotAllowed : LiveData<List<String>?> get() = _groupNamesNotAllowed
private val _groupNamesNotAllowed = MutableLiveData<List<String>?>()
val onGroupCreated : LiveData<GroupInfo> get() = _onGroupCreated
private val _onGroupCreated = SingleLiveEvent<GroupInfo>()
val onGroupUpdated : LiveData<GroupInfo> get() = _onGroupUpdated
private val _onGroupUpdated = SingleLiveEvent<GroupInfo>()
fun setGroupNamesNotAllowed(groupNames: List<String>?) {
this._groupNamesNotAllowed.value = groupNames
}
fun approveGroupCreation(groupInfo: GroupInfo) {
this._onGroupCreated.value = groupInfo
}
fun approveGroupUpdate(groupInfo: GroupInfo) {
this._onGroupUpdated.value = groupInfo
}
}

Some files were not shown because too many files have changed in this diff Show More