mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
33 Commits
3.0.0_beta
...
3.0.0_beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc3204453e | ||
|
|
5ef8d3b7b9 | ||
|
|
2a9de97a19 | ||
|
|
9cecfed417 | ||
|
|
319715918a | ||
|
|
a3bf6e8b6d | ||
|
|
c4062658ce | ||
|
|
01a5de413e | ||
|
|
e4c22b1f29 | ||
|
|
b10e60126f | ||
|
|
ef1f27f421 | ||
|
|
0ed208675c | ||
|
|
00f7a0a194 | ||
|
|
935d4f4a64 | ||
|
|
dc4d88260d | ||
|
|
18934601da | ||
|
|
4ea811aeda | ||
|
|
f8fdecdc8f | ||
|
|
5467c61137 | ||
|
|
9c72b4cc56 | ||
|
|
9102217bc3 | ||
|
|
0e8fd7b2c4 | ||
|
|
a06ea8fe55 | ||
|
|
31eb0fb48a | ||
|
|
d6a012e85f | ||
|
|
c6e2342ab4 | ||
|
|
b977792168 | ||
|
|
2595cf87d8 | ||
|
|
f4342f1448 | ||
|
|
c71ef24052 | ||
|
|
39b817bc69 | ||
|
|
e3adaba3b3 | ||
|
|
c62064002f |
@@ -4,7 +4,8 @@ KeePassDX(3.0.0)
|
|||||||
* Setting to display OTP Token in list #655
|
* Setting to display OTP Token in list #655
|
||||||
* Fix timeout in dialogs #716
|
* Fix timeout in dialogs #716
|
||||||
* Check URI permissions #626
|
* Check URI permissions #626
|
||||||
* Improvements #1035 #1043 #942 #1021 #1027
|
* Better autofill implementation #943 #946 #984 #1070 (Thx @uduerholz)
|
||||||
|
* Improvements #680 #1035 #1043 #942 #1021 #1027
|
||||||
|
|
||||||
KeePassDX(2.10.5)
|
KeePassDX(2.10.5)
|
||||||
* Increase the saving speed of database #1028
|
* Increase the saving speed of database #1028
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode = 84
|
versionCode = 86
|
||||||
versionName = "3.0.0_beta01"
|
versionName = "3.0.0_beta03"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
|||||||
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
|
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
|
||||||
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
|
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
|
||||||
webScheme = intent.getStringExtra(KEY_SEARCH_SCHEME)
|
webScheme = intent.getStringExtra(KEY_SEARCH_SCHEME)
|
||||||
|
manualSelection = intent.getBooleanExtra(KEY_MANUAL_SELECTION, false)
|
||||||
}
|
}
|
||||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
||||||
searchInfo.webDomain = concreteWebDomain
|
searchInfo.webDomain = concreteWebDomain
|
||||||
@@ -198,15 +199,16 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private const val KEY_MANUAL_SELECTION = "KEY_MANUAL_SELECTION"
|
||||||
private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
|
private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
|
||||||
private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
|
private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
|
||||||
private const val KEY_SEARCH_SCHEME = "KEY_SEARCH_SCHEME"
|
private const val KEY_SEARCH_SCHEME = "KEY_SEARCH_SCHEME"
|
||||||
|
|
||||||
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
|
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
|
||||||
|
|
||||||
fun getAuthIntentSenderForSelection(context: Context,
|
fun getPendingIntentForSelection(context: Context,
|
||||||
searchInfo: SearchInfo? = null,
|
searchInfo: SearchInfo? = null,
|
||||||
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): IntentSender {
|
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): PendingIntent {
|
||||||
return PendingIntent.getActivity(context, 0,
|
return PendingIntent.getActivity(context, 0,
|
||||||
// Doesn't work with Parcelable (don't know why?)
|
// Doesn't work with Parcelable (don't know why?)
|
||||||
Intent(context, AutofillLauncherActivity::class.java).apply {
|
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||||
@@ -214,6 +216,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
|||||||
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
|
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
|
||||||
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
|
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
|
||||||
putExtra(KEY_SEARCH_SCHEME, it.webScheme)
|
putExtra(KEY_SEARCH_SCHEME, it.webScheme)
|
||||||
|
putExtra(KEY_MANUAL_SELECTION, it.manualSelection)
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
inlineSuggestionsRequest?.let {
|
inlineSuggestionsRequest?.let {
|
||||||
@@ -221,17 +224,17 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
PendingIntent.FLAG_CANCEL_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAuthIntentSenderForRegistration(context: Context,
|
fun getPendingIntentForRegistration(context: Context,
|
||||||
registerInfo: RegisterInfo): IntentSender {
|
registerInfo: RegisterInfo): PendingIntent {
|
||||||
return PendingIntent.getActivity(context, 0,
|
return PendingIntent.getActivity(context, 0,
|
||||||
Intent(context, AutofillLauncherActivity::class.java).apply {
|
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||||
EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
|
EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
|
||||||
putExtra(KEY_REGISTER_INFO, registerInfo)
|
putExtra(KEY_REGISTER_INFO, registerInfo)
|
||||||
},
|
},
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
PendingIntent.FLAG_CANCEL_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchForRegistration(context: Context,
|
fun launchForRegistration(context: Context,
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
private var mHistoryPosition: Int = -1
|
private var mHistoryPosition: Int = -1
|
||||||
private var mEntryIsHistory: Boolean = false
|
private var mEntryIsHistory: Boolean = false
|
||||||
private var mUrl: String? = null
|
private var mUrl: String? = null
|
||||||
|
private var mEntryLoaded = false
|
||||||
|
|
||||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||||
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
|
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
|
||||||
@@ -119,11 +120,12 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
|
|
||||||
// Get Entry from UUID
|
// Get Entry from UUID
|
||||||
try {
|
try {
|
||||||
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { entryId ->
|
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { mainEntryId ->
|
||||||
mMainEntryId = entryId
|
|
||||||
intent.removeExtra(KEY_ENTRY)
|
intent.removeExtra(KEY_ENTRY)
|
||||||
mHistoryPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
|
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
|
||||||
intent.removeExtra(KEY_ENTRY_HISTORY_POSITION)
|
intent.removeExtra(KEY_ENTRY_HISTORY_POSITION)
|
||||||
|
|
||||||
|
mEntryViewModel.loadEntry(mDatabase, mainEntryId, historyPosition)
|
||||||
}
|
}
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
Log.e(TAG, "Unable to retrieve the entry key")
|
Log.e(TAG, "Unable to retrieve the entry key")
|
||||||
@@ -138,12 +140,12 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
lockAndExit()
|
lockAndExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
mEntryViewModel.mainEntryId.observe(this) { mainEntryId ->
|
mEntryViewModel.entryInfoHistory.observe(this) { entryInfoHistory ->
|
||||||
this.mMainEntryId = mainEntryId
|
if (entryInfoHistory != null) {
|
||||||
invalidateOptionsMenu()
|
this.mMainEntryId = entryInfoHistory.mainEntryId
|
||||||
}
|
|
||||||
|
|
||||||
mEntryViewModel.historyPosition.observe(this) { historyPosition ->
|
// Manage history position
|
||||||
|
val historyPosition = entryInfoHistory.historyPosition
|
||||||
this.mHistoryPosition = historyPosition
|
this.mHistoryPosition = historyPosition
|
||||||
val entryIsHistory = historyPosition > -1
|
val entryIsHistory = historyPosition > -1
|
||||||
this.mEntryIsHistory = entryIsHistory
|
this.mEntryIsHistory = entryIsHistory
|
||||||
@@ -151,13 +153,12 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
|
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
|
||||||
if (entryIsHistory) {
|
if (entryIsHistory) {
|
||||||
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||||
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
collapsingToolbarLayout?.contentScrim =
|
||||||
|
ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
||||||
taColorAccent.recycle()
|
taColorAccent.recycle()
|
||||||
}
|
}
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
mEntryViewModel.entryInfo.observe(this) { entryInfo ->
|
val entryInfo = entryInfoHistory.entryInfo
|
||||||
// Manage entry copy to start notification if allowed (at the first start)
|
// Manage entry copy to start notification if allowed (at the first start)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
// Manage entry to launch copying notification if allowed
|
// Manage entry to launch copying notification if allowed
|
||||||
@@ -167,24 +168,25 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
|
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign title icon
|
// Assign title icon
|
||||||
mIcon = entryInfo.icon
|
mIcon = entryInfo.icon
|
||||||
titleIconView?.let { iconView ->
|
titleIconView?.let { iconView ->
|
||||||
mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, mIconColor)
|
mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, mIconColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign title text
|
// Assign title text
|
||||||
val entryTitle = if (entryInfo.title.isNotEmpty()) entryInfo.title else entryInfo.id.toString()
|
val entryTitle =
|
||||||
|
if (entryInfo.title.isNotEmpty()) entryInfo.title else entryInfo.id.toString()
|
||||||
collapsingToolbarLayout?.title = entryTitle
|
collapsingToolbarLayout?.title = entryTitle
|
||||||
toolbar?.title = entryTitle
|
toolbar?.title = entryTitle
|
||||||
|
|
||||||
mUrl = entryInfo.url
|
mUrl = entryInfo.url
|
||||||
|
|
||||||
|
loadingView?.hideByFading()
|
||||||
|
mEntryLoaded = true
|
||||||
|
} else {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
// Refresh Menu
|
// Refresh Menu
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
loadingView?.hideByFading()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mEntryViewModel.onOtpElementUpdated.observe(this) { otpElement ->
|
mEntryViewModel.onOtpElementUpdated.observe(this) { otpElement ->
|
||||||
@@ -235,7 +237,7 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
override fun onDatabaseRetrieved(database: Database?) {
|
override fun onDatabaseRetrieved(database: Database?) {
|
||||||
super.onDatabaseRetrieved(database)
|
super.onDatabaseRetrieved(database)
|
||||||
|
|
||||||
mEntryViewModel.loadEntry(mDatabase, mMainEntryId, mHistoryPosition)
|
mEntryViewModel.loadDatabase(database)
|
||||||
|
|
||||||
// Assign title icon
|
// Assign title icon
|
||||||
mIcon?.let { icon ->
|
mIcon?.let { icon ->
|
||||||
@@ -294,7 +296,7 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
||||||
// Reload the current id from database
|
// Reload the current id from database
|
||||||
mEntryViewModel.updateEntry(mDatabase)
|
mEntryViewModel.loadDatabase(mDatabase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,34 +312,43 @@ class EntryActivity : DatabaseLockActivity() {
|
|||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
if (mEntryLoaded) {
|
||||||
val inflater = menuInflater
|
val inflater = menuInflater
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||||
|
|
||||||
inflater.inflate(R.menu.entry, menu)
|
inflater.inflate(R.menu.entry, menu)
|
||||||
inflater.inflate(R.menu.database, menu)
|
inflater.inflate(R.menu.database, menu)
|
||||||
|
|
||||||
if (mUrl?.isEmpty() != false) {
|
|
||||||
menu.findItem(R.id.menu_goto_url)?.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEntryIsHistory && !mDatabaseReadOnly) {
|
if (mEntryIsHistory && !mDatabaseReadOnly) {
|
||||||
inflater.inflate(R.menu.entry_history, menu)
|
inflater.inflate(R.menu.entry_history, menu)
|
||||||
}
|
}
|
||||||
if (mEntryIsHistory || mDatabaseReadOnly) {
|
|
||||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
|
||||||
menu.findItem(R.id.menu_edit)?.isVisible = false
|
|
||||||
}
|
|
||||||
if (mSpecialMode != SpecialMode.DEFAULT) {
|
|
||||||
menu.findItem(R.id.menu_reload_database)?.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show education views
|
// Show education views
|
||||||
Handler(Looper.getMainLooper()).post { performedNextEducation(EntryActivityEducation(this), menu) }
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
performedNextEducation(
|
||||||
|
EntryActivityEducation(
|
||||||
|
this
|
||||||
|
), menu
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
if (mUrl?.isEmpty() != false) {
|
||||||
|
menu?.findItem(R.id.menu_goto_url)?.isVisible = false
|
||||||
|
}
|
||||||
|
if (mEntryIsHistory || mDatabaseReadOnly) {
|
||||||
|
menu?.findItem(R.id.menu_save_database)?.isVisible = false
|
||||||
|
menu?.findItem(R.id.menu_edit)?.isVisible = false
|
||||||
|
}
|
||||||
|
if (mSpecialMode != SpecialMode.DEFAULT) {
|
||||||
|
menu?.findItem(R.id.menu_reload_database)?.isVisible = false
|
||||||
|
}
|
||||||
|
return super.onPrepareOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||||
menu: Menu) {
|
menu: Menu) {
|
||||||
val entryFragment = supportFragmentManager.findFragmentByTag(ENTRY_FRAGMENT_TAG)
|
val entryFragment = supportFragmentManager.findFragmentByTag(ENTRY_FRAGMENT_TAG)
|
||||||
|
|||||||
@@ -48,12 +48,9 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
|||||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||||
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
|
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
|
||||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
|
||||||
import com.kunzisoft.keepass.autofill.AutofillComponent
|
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.element.*
|
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.Node
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.template.*
|
import com.kunzisoft.keepass.database.element.template.*
|
||||||
@@ -95,15 +92,10 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
private var lockView: View? = null
|
private var lockView: View? = null
|
||||||
private var loadingView: ProgressBar? = 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 val mEntryEditViewModel: EntryEditViewModel by viewModels()
|
||||||
private var mParent: Group? = null
|
private var mTemplate: Template? = null
|
||||||
private var mEntry: Entry? = null
|
|
||||||
private var mIsTemplate: Boolean = false
|
private var mIsTemplate: Boolean = false
|
||||||
|
private var mEntryLoaded: Boolean = false
|
||||||
|
|
||||||
private var mAllowCustomFields = false
|
private var mAllowCustomFields = false
|
||||||
private var mAllowOTP = false
|
private var mAllowOTP = false
|
||||||
@@ -138,22 +130,27 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||||
|
|
||||||
mRegisterInfo = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
|
|
||||||
mSearchInfo = EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
|
||||||
|
|
||||||
// Entry is retrieve, it's an entry to update
|
// Entry is retrieve, it's an entry to update
|
||||||
|
var entryId: NodeId<UUID>? = null
|
||||||
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
|
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
|
||||||
//intent.removeExtra(KEY_ENTRY)
|
intent.removeExtra(KEY_ENTRY)
|
||||||
mEntryId = entryToUpdate
|
entryId = entryToUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent is retrieve, it's a new entry to create
|
// Parent is retrieve, it's a new entry to create
|
||||||
|
var parentId: NodeId<*>? = null
|
||||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent ->
|
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent ->
|
||||||
//intent.removeExtra(KEY_PARENT)
|
intent.removeExtra(KEY_PARENT)
|
||||||
mParentId = parent
|
parentId = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieveEntry(mDatabase)
|
mEntryEditViewModel.loadTemplateEntry(
|
||||||
|
mDatabase,
|
||||||
|
entryId,
|
||||||
|
parentId,
|
||||||
|
EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent),
|
||||||
|
EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
||||||
|
)
|
||||||
|
|
||||||
// To retrieve attachment
|
// To retrieve attachment
|
||||||
mExternalFileHelper = ExternalFileHelper(this)
|
mExternalFileHelper = ExternalFileHelper(this)
|
||||||
@@ -166,10 +163,15 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
// Save button
|
// Save button
|
||||||
validateButton?.setOnClickListener { saveEntry() }
|
validateButton?.setOnClickListener { saveEntry() }
|
||||||
|
|
||||||
|
mEntryEditViewModel.onTemplateChanged.observe(this) { template ->
|
||||||
|
this.mTemplate = template
|
||||||
|
}
|
||||||
|
|
||||||
mEntryEditViewModel.templatesEntry.observe(this) { templatesEntry ->
|
mEntryEditViewModel.templatesEntry.observe(this) { templatesEntry ->
|
||||||
|
if (templatesEntry != null) {
|
||||||
// Change template dynamically
|
// Change template dynamically
|
||||||
templatesEntry?.templates?.let { templates ->
|
this.mIsTemplate = templatesEntry.isTemplate
|
||||||
val defaultTemplate = templatesEntry.defaultTemplate
|
templatesEntry.templates.let { templates ->
|
||||||
templateSelectorSpinner?.apply {
|
templateSelectorSpinner?.apply {
|
||||||
// Build template selector
|
// Build template selector
|
||||||
if (templates.isNotEmpty()) {
|
if (templates.isNotEmpty()) {
|
||||||
@@ -178,7 +180,11 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
mIconDrawableFactory,
|
mIconDrawableFactory,
|
||||||
templates
|
templates
|
||||||
)
|
)
|
||||||
setSelection(templates.indexOf(defaultTemplate))
|
val selectedTemplate = if (mTemplate != null)
|
||||||
|
mTemplate
|
||||||
|
else
|
||||||
|
templatesEntry.defaultTemplate
|
||||||
|
setSelection(templates.indexOf(selectedTemplate))
|
||||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(
|
override fun onItemSelected(
|
||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
@@ -198,6 +204,11 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadingView?.hideByFading()
|
loadingView?.hideByFading()
|
||||||
|
mEntryLoaded = true
|
||||||
|
} else {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
// View model listeners
|
// View model listeners
|
||||||
@@ -309,78 +320,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
super.onDatabaseRetrieved(database)
|
super.onDatabaseRetrieved(database)
|
||||||
mAllowCustomFields = database?.allowEntryCustomFields() == true
|
mAllowCustomFields = database?.allowEntryCustomFields() == true
|
||||||
mAllowOTP = database?.allowOTP == true
|
mAllowOTP = database?.allowOTP == true
|
||||||
retrieveEntry(database)
|
mEntryEditViewModel.loadDatabase(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Define if current entry is a template (in direct template group)
|
|
||||||
mIsTemplate = database.entryIsTemplate(mEntry)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mEntryEditViewModel.loadTemplateEntry(
|
|
||||||
database,
|
|
||||||
mEntry,
|
|
||||||
mIsTemplate,
|
|
||||||
mRegisterInfo,
|
|
||||||
mSearchInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
).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(
|
override fun onDatabaseActionFinished(
|
||||||
@@ -571,29 +511,33 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
*/
|
*/
|
||||||
private fun saveEntry() {
|
private fun saveEntry() {
|
||||||
mAttachmentFileBinderManager?.stopUploadAllAttachments()
|
mAttachmentFileBinderManager?.stopUploadAllAttachments()
|
||||||
mEntryEditViewModel.requestEntryInfoUpdate(mDatabase, mEntry, mParent)
|
mEntryEditViewModel.requestEntryInfoUpdate(mDatabase)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
if (mEntryLoaded) {
|
||||||
menuInflater.inflate(R.menu.entry_edit, menu)
|
menuInflater.inflate(R.menu.entry_edit, menu)
|
||||||
|
entryEditActivityEducation?.let {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
performedNextEducation(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
|
||||||
menu?.findItem(R.id.menu_add_field)?.apply {
|
menu?.findItem(R.id.menu_add_field)?.apply {
|
||||||
isEnabled = mAllowCustomFields
|
isEnabled = mAllowCustomFields
|
||||||
isVisible = isEnabled
|
isVisible = isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
menu?.findItem(R.id.menu_add_attachment)?.apply {
|
menu?.findItem(R.id.menu_add_attachment)?.apply {
|
||||||
// Attachment not compatible below KitKat
|
// Attachment not compatible below KitKat
|
||||||
isEnabled = !mIsTemplate
|
isEnabled = !mIsTemplate
|
||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||||
isVisible = isEnabled
|
isVisible = isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
menu?.findItem(R.id.menu_add_otp)?.apply {
|
menu?.findItem(R.id.menu_add_otp)?.apply {
|
||||||
// OTP not compatible below KitKat
|
// OTP not compatible below KitKat
|
||||||
isEnabled = mAllowOTP
|
isEnabled = mAllowOTP
|
||||||
@@ -601,14 +545,10 @@ class EntryEditActivity : DatabaseLockActivity(),
|
|||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||||
isVisible = isEnabled
|
isVisible = isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
entryEditActivityEducation?.let {
|
|
||||||
Handler(Looper.getMainLooper()).post { performedNextEducation(it) }
|
|
||||||
}
|
|
||||||
return super.onPrepareOptionsMenu(menu)
|
return super.onPrepareOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||||
|
|
||||||
val entryEditFragment = supportFragmentManager.findFragmentById(R.id.entry_edit_content)
|
val entryEditFragment = supportFragmentManager.findFragmentById(R.id.entry_edit_content)
|
||||||
as? EntryEditFragment?
|
as? EntryEditFragment?
|
||||||
|
|||||||
@@ -125,13 +125,6 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
||||||
// Remove from app database
|
|
||||||
fileDatabaseHistoryToDelete.databaseUri?.let { databaseUri ->
|
|
||||||
UriUtil.releaseUriPermission(
|
|
||||||
contentResolver,
|
|
||||||
databaseUri
|
|
||||||
)
|
|
||||||
}
|
|
||||||
databaseFilesViewModel.deleteDatabaseFile(fileDatabaseHistoryToDelete)
|
databaseFilesViewModel.deleteDatabaseFile(fileDatabaseHistoryToDelete)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -487,7 +487,9 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
if (groupMetaView != null) {
|
if (groupMetaView != null) {
|
||||||
val meta = group.nodeId.toString()
|
val meta = group.nodeId.toString()
|
||||||
groupMetaView?.text = meta
|
groupMetaView?.text = meta
|
||||||
if (meta.isNotEmpty() && PreferencesUtil.showUUID(this)) {
|
if (meta.isNotEmpty()
|
||||||
|
&& !group.isVirtual
|
||||||
|
&& PreferencesUtil.showUUID(this)) {
|
||||||
groupMetaView?.visibility = View.VISIBLE
|
groupMetaView?.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
groupMetaView?.visibility = View.GONE
|
groupMetaView?.visibility = View.GONE
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
|
|||||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||||
import com.kunzisoft.keepass.database.element.Attachment
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.template.Template
|
||||||
import com.kunzisoft.keepass.model.AttachmentState
|
import com.kunzisoft.keepass.model.AttachmentState
|
||||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
@@ -55,6 +56,7 @@ class EntryEditFragment: DatabaseFragment() {
|
|||||||
private lateinit var attachmentsListView: RecyclerView
|
private lateinit var attachmentsListView: RecyclerView
|
||||||
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
|
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
|
||||||
|
|
||||||
|
private var mTemplate: Template? = null
|
||||||
private var mAllowMultipleAttachments: Boolean = false
|
private var mAllowMultipleAttachments: Boolean = false
|
||||||
|
|
||||||
private var mIconColor: Int = 0
|
private var mIconColor: Int = 0
|
||||||
@@ -115,11 +117,17 @@ class EntryEditFragment: DatabaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mEntryEditViewModel.onTemplateChanged.observe(viewLifecycleOwner) { template ->
|
mEntryEditViewModel.onTemplateChanged.observe(viewLifecycleOwner) { template ->
|
||||||
|
this.mTemplate = template
|
||||||
templateView.setTemplate(template)
|
templateView.setTemplate(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
mEntryEditViewModel.templatesEntry.observe(viewLifecycleOwner) { templateEntry ->
|
mEntryEditViewModel.templatesEntry.observe(viewLifecycleOwner) { templateEntry ->
|
||||||
templateView.setTemplate(templateEntry.defaultTemplate)
|
if (templateEntry != null) {
|
||||||
|
val selectedTemplate = if (mTemplate != null)
|
||||||
|
mTemplate
|
||||||
|
else
|
||||||
|
templateEntry.defaultTemplate
|
||||||
|
templateView.setTemplate(selectedTemplate)
|
||||||
// Load entry info only the first time to keep change locally
|
// Load entry info only the first time to keep change locally
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
assignEntryInfo(templateEntry.entryInfo)
|
assignEntryInfo(templateEntry.entryInfo)
|
||||||
@@ -129,6 +137,7 @@ class EntryEditFragment: DatabaseFragment() {
|
|||||||
// Apply timeout reset
|
// Apply timeout reset
|
||||||
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
|
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
|
mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
|
||||||
mEntryEditViewModel.saveEntryInfo(it.database, it.entry, it.parent, retrieveEntryInfo())
|
mEntryEditViewModel.saveEntryInfo(it.database, it.entry, it.parent, retrieveEntryInfo())
|
||||||
|
|||||||
@@ -91,16 +91,15 @@ class EntryFragment: DatabaseFragment() {
|
|||||||
uuidView = view.findViewById(R.id.entry_UUID)
|
uuidView = view.findViewById(R.id.entry_UUID)
|
||||||
uuidReferenceView = view.findViewById(R.id.entry_UUID_reference)
|
uuidReferenceView = view.findViewById(R.id.entry_UUID_reference)
|
||||||
|
|
||||||
mEntryViewModel.template.observe(viewLifecycleOwner) { template ->
|
mEntryViewModel.entryInfoHistory.observe(viewLifecycleOwner) { entryInfoHistory ->
|
||||||
templateView.setTemplate(template)
|
if (entryInfoHistory != null) {
|
||||||
}
|
templateView.setTemplate(entryInfoHistory.template)
|
||||||
|
assignEntryInfo(entryInfoHistory.entryInfo)
|
||||||
mEntryViewModel.entryInfo.observe(viewLifecycleOwner) { entryInfo ->
|
|
||||||
assignEntryInfo(entryInfo)
|
|
||||||
// Smooth appearing
|
// Smooth appearing
|
||||||
rootView.showByFading()
|
rootView.showByFading()
|
||||||
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
|
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mEntryViewModel.onAttachmentAction.observe(viewLifecycleOwner) { entryAttachmentState ->
|
mEntryViewModel.onAttachmentAction.observe(viewLifecycleOwner) { entryAttachmentState ->
|
||||||
entryAttachmentState?.let {
|
entryAttachmentState?.let {
|
||||||
|
|||||||
@@ -55,9 +55,11 @@ class EntryHistoryFragment: StylishFragment() {
|
|||||||
* History
|
* History
|
||||||
* -------------
|
* -------------
|
||||||
*/
|
*/
|
||||||
private fun assignHistory(history: List<EntryInfo>) {
|
private fun assignHistory(history: List<EntryInfo>?) {
|
||||||
historyAdapter?.clear()
|
historyAdapter?.clear()
|
||||||
|
history?.let {
|
||||||
historyAdapter?.entryHistoryList?.addAll(history)
|
historyAdapter?.entryHistoryList?.addAll(history)
|
||||||
|
}
|
||||||
historyAdapter?.onItemClickListener = { item, position ->
|
historyAdapter?.onItemClickListener = { item, position ->
|
||||||
mEntryViewModel.onHistorySelected(item, position)
|
mEntryViewModel.onHistorySelected(item, position)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,7 +233,6 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
|
|||||||
return mLayoutManager?.findFirstVisibleItemPosition() ?: 0
|
return mLayoutManager?.findFirstVisibleItemPosition() ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
|
||||||
private fun rebuildList() {
|
private fun rebuildList() {
|
||||||
try {
|
try {
|
||||||
// Add elements to the list
|
// Add elements to the list
|
||||||
@@ -418,7 +417,7 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
|
|||||||
mNodesRecyclerView?.scrollToPosition(position)
|
mNodesRecyclerView?.scrollToPosition(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
|
} ?: Log.e(this.javaClass.name, "Entry cannot be retrieved in Activity Result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,9 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
|||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
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.model.MainCredential
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
||||||
|
|
||||||
@@ -28,7 +23,11 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
|||||||
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
||||||
|
|
||||||
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
||||||
if (mDatabase == null || mDatabase != database) {
|
val databaseWasReloaded = database?.wasReloaded == true
|
||||||
|
if (databaseWasReloaded && finishActivityIfReloadRequested()) {
|
||||||
|
finish()
|
||||||
|
} else if (mDatabase == null || mDatabase != database || databaseWasReloaded) {
|
||||||
|
database?.wasReloaded = false
|
||||||
onDatabaseRetrieved(database)
|
onDatabaseRetrieved(database)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,17 +68,8 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
|||||||
mDatabase?.clearAndClose(this)
|
mDatabase?.clearAndClose(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reloadActivity() {
|
|
||||||
super.reloadActivity()
|
|
||||||
mDatabase?.wasReloaded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
if (mDatabase?.wasReloaded == true) {
|
|
||||||
reloadActivity()
|
|
||||||
}
|
|
||||||
mDatabaseTaskProvider?.registerProgressTask()
|
mDatabaseTaskProvider?.registerProgressTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,26 +189,36 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
|
|||||||
).execute()
|
).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteKeyFileByDatabaseUri(databaseUri: Uri) {
|
fun deleteKeyFileByDatabaseUri(databaseUri: Uri,
|
||||||
|
result: (() ->Unit)? = null) {
|
||||||
IOActionTask(
|
IOActionTask(
|
||||||
{
|
{
|
||||||
databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString())
|
databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
result?.invoke()
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAllKeyFiles() {
|
fun deleteAllKeyFiles(result: (() ->Unit)? = null) {
|
||||||
IOActionTask(
|
IOActionTask(
|
||||||
{
|
{
|
||||||
databaseFileHistoryDao.deleteAllKeyFiles()
|
databaseFileHistoryDao.deleteAllKeyFiles()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
result?.invoke()
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAll() {
|
fun deleteAll(result: (() ->Unit)? = null) {
|
||||||
IOActionTask(
|
IOActionTask(
|
||||||
{
|
{
|
||||||
databaseFileHistoryDao.deleteAll()
|
databaseFileHistoryDao.deleteAll()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
result?.invoke()
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.app.PendingIntent
|
|||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentSender
|
||||||
import android.graphics.BlendMode
|
import android.graphics.BlendMode
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -37,11 +38,13 @@ import android.view.autofill.AutofillValue
|
|||||||
import android.view.inputmethod.InlineSuggestionsRequest
|
import android.view.inputmethod.InlineSuggestionsRequest
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import android.widget.inline.InlinePresentationSpec
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.autofill.inline.UiVersions
|
import androidx.autofill.inline.UiVersions
|
||||||
import androidx.autofill.inline.v1.InlineSuggestionUi
|
import androidx.autofill.inline.v1.InlineSuggestionUi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
@@ -51,7 +54,6 @@ import com.kunzisoft.keepass.model.SearchInfo
|
|||||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
|
||||||
@@ -126,12 +128,14 @@ object AutofillHelper {
|
|||||||
if (entryInfo.expires) {
|
if (entryInfo.expires) {
|
||||||
val year = entryInfo.expiryTime.getYearInt()
|
val year = entryInfo.expiryTime.getYearInt()
|
||||||
val month = entryInfo.expiryTime.getMonthInt()
|
val month = entryInfo.expiryTime.getMonthInt()
|
||||||
|
val monthString = month.toString().padStart(2, '0')
|
||||||
val day = entryInfo.expiryTime.getDay()
|
val day = entryInfo.expiryTime.getDay()
|
||||||
|
val dayString = day.toString().padStart(2, '0')
|
||||||
|
|
||||||
struct.creditCardExpirationDateId?.let {
|
struct.creditCardExpirationDateId?.let {
|
||||||
if (struct.isWebView) {
|
if (struct.isWebView) {
|
||||||
// set date string as defined in https://html.spec.whatwg.org
|
// set date string as defined in https://html.spec.whatwg.org
|
||||||
builder.setValue(it, AutofillValue.forText("$year\u002D$month"))
|
builder.setValue(it, AutofillValue.forText("$year\u002D$monthString"))
|
||||||
} else {
|
} else {
|
||||||
builder.setValue(it, AutofillValue.forDate(entryInfo.expiryTime.date.time))
|
builder.setValue(it, AutofillValue.forDate(entryInfo.expiryTime.date.time))
|
||||||
}
|
}
|
||||||
@@ -157,24 +161,24 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
struct.creditCardExpirationMonthId?.let {
|
struct.creditCardExpirationMonthId?.let {
|
||||||
if (struct.isWebView) {
|
if (struct.isWebView) {
|
||||||
builder.setValue(it, AutofillValue.forText(month.toString()))
|
builder.setValue(it, AutofillValue.forText(monthString))
|
||||||
} else {
|
} else {
|
||||||
if (struct.creditCardExpirationMonthOptions != null) {
|
if (struct.creditCardExpirationMonthOptions != null) {
|
||||||
// index starts at 0
|
// index starts at 0
|
||||||
builder.setValue(it, AutofillValue.forList(month - 1))
|
builder.setValue(it, AutofillValue.forList(month - 1))
|
||||||
} else {
|
} else {
|
||||||
builder.setValue(it, AutofillValue.forText(month.toString()))
|
builder.setValue(it, AutofillValue.forText(monthString))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct.creditCardExpirationDayId?.let {
|
struct.creditCardExpirationDayId?.let {
|
||||||
if (struct.isWebView) {
|
if (struct.isWebView) {
|
||||||
builder.setValue(it, AutofillValue.forText(day.toString()))
|
builder.setValue(it, AutofillValue.forText(dayString))
|
||||||
} else {
|
} else {
|
||||||
if (struct.creditCardExpirationDayOptions != null) {
|
if (struct.creditCardExpirationDayOptions != null) {
|
||||||
builder.setValue(it, AutofillValue.forList(day - 1))
|
builder.setValue(it, AutofillValue.forList(day - 1))
|
||||||
} else {
|
} else {
|
||||||
builder.setValue(it, AutofillValue.forText(day.toString()))
|
builder.setValue(it, AutofillValue.forText(dayString))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,6 +274,27 @@ object AutofillHelper {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
private fun buildInlinePresentationForManualSelection(context: Context,
|
||||||
|
inlinePresentationSpec: InlinePresentationSpec,
|
||||||
|
pendingIntent: PendingIntent): InlinePresentation? {
|
||||||
|
// Make sure that the IME spec claims support for v1 UI template.
|
||||||
|
val imeStyle = inlinePresentationSpec.style
|
||||||
|
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
|
||||||
|
return null
|
||||||
|
|
||||||
|
// Build the content for IME UI
|
||||||
|
return InlinePresentation(
|
||||||
|
InlineSuggestionUi.newContentBuilder(pendingIntent).apply {
|
||||||
|
setContentDescription(context.getString(R.string.autofill_sign_in_prompt))
|
||||||
|
setTitle(context.getString(R.string.autofill_select_entry))
|
||||||
|
setStartIcon(Icon.createWithResource(context, R.drawable.ic_arrow_right_green_24dp).apply {
|
||||||
|
setTintBlendMode(BlendMode.DST)
|
||||||
|
})
|
||||||
|
}.build().slice, inlinePresentationSpec, false)
|
||||||
|
}
|
||||||
|
|
||||||
fun buildResponse(context: Context,
|
fun buildResponse(context: Context,
|
||||||
database: Database,
|
database: Database,
|
||||||
entriesInfo: List<EntryInfo>,
|
entriesInfo: List<EntryInfo>,
|
||||||
@@ -293,17 +318,58 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add inline suggestion for new IME and dataset
|
// Add inline suggestion for new IME and dataset
|
||||||
entriesInfo.forEachIndexed { index, entryInfo ->
|
var numberInlineSuggestions = 0
|
||||||
val inlinePresentation = inlineSuggestionsRequest?.let {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
buildInlinePresentationForEntry(context, database, inlineSuggestionsRequest, index, entryInfo)
|
inlineSuggestionsRequest?.let {
|
||||||
|
numberInlineSuggestions = minOf(inlineSuggestionsRequest.maxSuggestionCount, entriesInfo.size)
|
||||||
|
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
||||||
|
if (entriesInfo.size >= inlineSuggestionsRequest.maxSuggestionCount) {
|
||||||
|
--numberInlineSuggestions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
entriesInfo.forEachIndexed { _, entry ->
|
||||||
|
val inlinePresentation = if (numberInlineSuggestions > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
inlineSuggestionsRequest?.let {
|
||||||
|
buildInlinePresentationForEntry(context, database, inlineSuggestionsRequest, numberInlineSuggestions--, entry)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult, inlinePresentation))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
||||||
|
val searchInfo = SearchInfo().apply {
|
||||||
|
applicationId = parseResult.applicationId
|
||||||
|
webDomain = parseResult.webDomain
|
||||||
|
webScheme = parseResult.webScheme
|
||||||
|
manualSelection = true
|
||||||
|
}
|
||||||
|
val manualSelectionView = RemoteViews(context.packageName, R.layout.item_autofill_select_entry)
|
||||||
|
val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context,
|
||||||
|
searchInfo, inlineSuggestionsRequest)
|
||||||
|
|
||||||
|
parseResult.allAutofillIds().let { autofillIds ->
|
||||||
|
autofillIds.forEach { id ->
|
||||||
|
val builder = Dataset.Builder(manualSelectionView)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
inlineSuggestionsRequest?.let {
|
||||||
|
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0]
|
||||||
|
val inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent)
|
||||||
|
inlinePresentation?.let {
|
||||||
|
builder.setInlinePresentation(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.setValue(id, null)
|
||||||
|
builder.setAuthentication(pendingIntent.intentSender)
|
||||||
|
responseBuilder.addDataset(builder.build())
|
||||||
}
|
}
|
||||||
val dataSet = buildDataset(context, database, entryInfo, parseResult, inlinePresentation)
|
|
||||||
dataSet?.let {
|
|
||||||
responseBuilder.addDataset(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,14 +138,14 @@ class KeeAutofillService : AutofillService() {
|
|||||||
items, parseResult, inlineSuggestionsRequest)
|
items, parseResult, inlineSuggestionsRequest)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{ openedDatabase ->
|
||||||
// Show UI if no search result
|
// Show UI if no search result
|
||||||
showUIForEntrySelection(parseResult,
|
showUIForEntrySelection(parseResult, openedDatabase,
|
||||||
searchInfo, inlineSuggestionsRequest, callback)
|
searchInfo, inlineSuggestionsRequest, callback)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Show UI if database not open
|
// Show UI if database not open
|
||||||
showUIForEntrySelection(parseResult,
|
showUIForEntrySelection(parseResult, null,
|
||||||
searchInfo, inlineSuggestionsRequest, callback)
|
searchInfo, inlineSuggestionsRequest, callback)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -153,6 +153,7 @@ class KeeAutofillService : AutofillService() {
|
|||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
||||||
|
database: Database?,
|
||||||
searchInfo: SearchInfo,
|
searchInfo: SearchInfo,
|
||||||
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
||||||
callback: FillCallback) {
|
callback: FillCallback) {
|
||||||
@@ -160,20 +161,52 @@ class KeeAutofillService : AutofillService() {
|
|||||||
if (autofillIds.isNotEmpty()) {
|
if (autofillIds.isNotEmpty()) {
|
||||||
// If the entire Autofill Response is authenticated, AuthActivity is used
|
// If the entire Autofill Response is authenticated, AuthActivity is used
|
||||||
// to generate Response.
|
// to generate Response.
|
||||||
val intentSender = AutofillLauncherActivity.getAuthIntentSenderForSelection(this,
|
val intentSender = AutofillLauncherActivity.getPendingIntentForSelection(this,
|
||||||
searchInfo, inlineSuggestionsRequest)
|
searchInfo, inlineSuggestionsRequest).intentSender
|
||||||
val responseBuilder = FillResponse.Builder()
|
val responseBuilder = FillResponse.Builder()
|
||||||
val remoteViewsUnlock: RemoteViews = if (!parseResult.webDomain.isNullOrEmpty()) {
|
val remoteViewsUnlock: RemoteViews = if (database == null) {
|
||||||
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
|
if (!parseResult.webDomain.isNullOrEmpty()) {
|
||||||
setTextViewText(R.id.autofill_web_domain_text, parseResult.webDomain)
|
RemoteViews(
|
||||||
|
packageName,
|
||||||
|
R.layout.item_autofill_unlock_web_domain
|
||||||
|
).apply {
|
||||||
|
setTextViewText(
|
||||||
|
R.id.autofill_web_domain_text,
|
||||||
|
parseResult.webDomain
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (!parseResult.applicationId.isNullOrEmpty()) {
|
} else if (!parseResult.applicationId.isNullOrEmpty()) {
|
||||||
RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply {
|
RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply {
|
||||||
setTextViewText(R.id.autofill_app_id_text, parseResult.applicationId)
|
setTextViewText(
|
||||||
|
R.id.autofill_app_id_text,
|
||||||
|
parseResult.applicationId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RemoteViews(packageName, R.layout.item_autofill_unlock)
|
RemoteViews(packageName, R.layout.item_autofill_unlock)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!parseResult.webDomain.isNullOrEmpty()) {
|
||||||
|
RemoteViews(
|
||||||
|
packageName,
|
||||||
|
R.layout.item_autofill_select_entry_web_domain
|
||||||
|
).apply {
|
||||||
|
setTextViewText(
|
||||||
|
R.id.autofill_web_domain_text,
|
||||||
|
parseResult.webDomain
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (!parseResult.applicationId.isNullOrEmpty()) {
|
||||||
|
RemoteViews(packageName, R.layout.item_autofill_select_entry_app_id).apply {
|
||||||
|
setTextViewText(
|
||||||
|
R.id.autofill_app_id_text,
|
||||||
|
parseResult.applicationId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RemoteViews(packageName, R.layout.item_autofill_select_entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tell the autofill framework the interest to save credentials
|
// Tell the autofill framework the interest to save credentials
|
||||||
if (askToSaveData) {
|
if (askToSaveData) {
|
||||||
|
|||||||
@@ -143,19 +143,19 @@ class StructureParser(private val structure: AssistStructure) {
|
|||||||
Log.d(TAG, "Autofill password hint")
|
Log.d(TAG, "Autofill password hint")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
it.contains("cc-name", true) -> {
|
it.equals("cc-name", true) -> {
|
||||||
Log.d(TAG, "Autofill credit card name hint")
|
Log.d(TAG, "Autofill credit card name hint")
|
||||||
result?.creditCardHolderId = autofillId
|
result?.creditCardHolderId = autofillId
|
||||||
result?.creditCardHolder = node.autofillValue?.textValue?.toString()
|
result?.creditCardHolder = node.autofillValue?.textValue?.toString()
|
||||||
}
|
}
|
||||||
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, true)
|
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, true)
|
||||||
|| it.contains("cc-number", true) -> {
|
|| it.equals("cc-number", true) -> {
|
||||||
Log.d(TAG, "Autofill credit card number hint")
|
Log.d(TAG, "Autofill credit card number hint")
|
||||||
result?.creditCardNumberId = autofillId
|
result?.creditCardNumberId = autofillId
|
||||||
result?.creditCardNumber = node.autofillValue?.textValue?.toString()
|
result?.creditCardNumber = node.autofillValue?.textValue?.toString()
|
||||||
}
|
}
|
||||||
// expect date string as defined in https://html.spec.whatwg.org, e.g. 2014-12
|
// expect date string as defined in https://html.spec.whatwg.org, e.g. 2014-12
|
||||||
it.contains("cc-exp", true) -> {
|
it.equals("cc-exp", true) -> {
|
||||||
Log.d(TAG, "Autofill credit card expiration date hint")
|
Log.d(TAG, "Autofill credit card expiration date hint")
|
||||||
result?.creditCardExpirationDateId = autofillId
|
result?.creditCardExpirationDateId = autofillId
|
||||||
node.autofillValue?.let { value ->
|
node.autofillValue?.let { value ->
|
||||||
@@ -182,7 +182,7 @@ class StructureParser(private val structure: AssistStructure) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, true)
|
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, true)
|
||||||
|| it.contains("cc-exp-year", true) -> {
|
|| it.equals("cc-exp-year", true) -> {
|
||||||
Log.d(TAG, "Autofill credit card expiration year hint")
|
Log.d(TAG, "Autofill credit card expiration year hint")
|
||||||
result?.creditCardExpirationYearId = autofillId
|
result?.creditCardExpirationYearId = autofillId
|
||||||
if (node.autofillOptions != null) {
|
if (node.autofillOptions != null) {
|
||||||
@@ -204,7 +204,7 @@ class StructureParser(private val structure: AssistStructure) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, true)
|
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, true)
|
||||||
|| it.contains("cc-exp-month", true) -> {
|
|| it.equals("cc-exp-month", true) -> {
|
||||||
Log.d(TAG, "Autofill credit card expiration month hint")
|
Log.d(TAG, "Autofill credit card expiration month hint")
|
||||||
result?.creditCardExpirationMonthId = autofillId
|
result?.creditCardExpirationMonthId = autofillId
|
||||||
if (node.autofillOptions != null) {
|
if (node.autofillOptions != null) {
|
||||||
@@ -227,7 +227,7 @@ class StructureParser(private val structure: AssistStructure) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, true)
|
it.contains(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, true)
|
||||||
|| it.contains("cc-exp-day", true) -> {
|
|| it.equals("cc-exp-day", true) -> {
|
||||||
Log.d(TAG, "Autofill credit card expiration day hint")
|
Log.d(TAG, "Autofill credit card expiration day hint")
|
||||||
result?.creditCardExpirationDayId = autofillId
|
result?.creditCardExpirationDayId = autofillId
|
||||||
if (node.autofillOptions != null) {
|
if (node.autofillOptions != null) {
|
||||||
@@ -399,7 +399,6 @@ class StructureParser(private val structure: AssistStructure) {
|
|||||||
class Result {
|
class Result {
|
||||||
var isWebView: Boolean = false
|
var isWebView: Boolean = false
|
||||||
var applicationId: String? = null
|
var applicationId: String? = null
|
||||||
|
|
||||||
var webDomain: String? = null
|
var webDomain: String? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field == null)
|
if (field == null)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class SearchHelper {
|
|||||||
} else if (TimeoutHelper.checkTime(context)) {
|
} else if (TimeoutHelper.checkTime(context)) {
|
||||||
var searchWithoutUI = false
|
var searchWithoutUI = false
|
||||||
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
|
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
|
||||||
&& searchInfo != null
|
&& searchInfo != null && !searchInfo.manualSelection
|
||||||
&& !searchInfo.containsOnlyNullValues()) {
|
&& !searchInfo.containsOnlyNullValues()) {
|
||||||
// If search provide results
|
// If search provide results
|
||||||
database.createVirtualGroupFromSearchInfo(
|
database.createVirtualGroupFromSearchInfo(
|
||||||
|
|||||||
@@ -186,6 +186,39 @@ class EntryInfo : NodeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is EntryInfo) return false
|
||||||
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
|
if (id != other.id) return false
|
||||||
|
if (username != other.username) return false
|
||||||
|
if (password != other.password) return false
|
||||||
|
if (url != other.url) return false
|
||||||
|
if (notes != other.notes) return false
|
||||||
|
if (customFields != other.customFields) return false
|
||||||
|
if (attachments != other.attachments) return false
|
||||||
|
if (otpModel != other.otpModel) return false
|
||||||
|
if (isTemplate != other.isTemplate) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = super.hashCode()
|
||||||
|
result = 31 * result + id.hashCode()
|
||||||
|
result = 31 * result + username.hashCode()
|
||||||
|
result = 31 * result + password.hashCode()
|
||||||
|
result = 31 * result + url.hashCode()
|
||||||
|
result = 31 * result + notes.hashCode()
|
||||||
|
result = 31 * result + customFields.hashCode()
|
||||||
|
result = 31 * result + attachments.hashCode()
|
||||||
|
result = 31 * result + (otpModel?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + isTemplate.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val WEB_DOMAIN_FIELD_NAME = "URL"
|
const val WEB_DOMAIN_FIELD_NAME = "URL"
|
||||||
|
|||||||
@@ -24,6 +24,22 @@ class GroupInfo : NodeInfo {
|
|||||||
parcel.writeString(notes)
|
parcel.writeString(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is GroupInfo) return false
|
||||||
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
|
if (notes != other.notes) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = super.hashCode()
|
||||||
|
result = 31 * result + (notes?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
companion object CREATOR : Parcelable.Creator<GroupInfo> {
|
companion object CREATOR : Parcelable.Creator<GroupInfo> {
|
||||||
override fun createFromParcel(parcel: Parcel): GroupInfo {
|
override fun createFromParcel(parcel: Parcel): GroupInfo {
|
||||||
return GroupInfo(parcel)
|
return GroupInfo(parcel)
|
||||||
|
|||||||
@@ -36,6 +36,30 @@ open class NodeInfo() : Parcelable {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is NodeInfo) return false
|
||||||
|
|
||||||
|
if (title != other.title) return false
|
||||||
|
if (icon != other.icon) return false
|
||||||
|
if (creationTime != other.creationTime) return false
|
||||||
|
if (lastModificationTime != other.lastModificationTime) return false
|
||||||
|
if (expires != other.expires) return false
|
||||||
|
if (expiryTime != other.expiryTime) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = title.hashCode()
|
||||||
|
result = 31 * result + icon.hashCode()
|
||||||
|
result = 31 * result + creationTime.hashCode()
|
||||||
|
result = 31 * result + lastModificationTime.hashCode()
|
||||||
|
result = 31 * result + expires.hashCode()
|
||||||
|
result = 31 * result + expiryTime.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
companion object CREATOR : Parcelable.Creator<NodeInfo> {
|
companion object CREATOR : Parcelable.Creator<NodeInfo> {
|
||||||
override fun createFromParcel(parcel: Parcel): NodeInfo {
|
override fun createFromParcel(parcel: Parcel): NodeInfo {
|
||||||
return NodeInfo(parcel)
|
return NodeInfo(parcel)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
|
|||||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||||
|
|
||||||
class SearchInfo : ObjectNameResource, Parcelable {
|
class SearchInfo : ObjectNameResource, Parcelable {
|
||||||
|
var manualSelection: Boolean = false
|
||||||
var applicationId: String? = null
|
var applicationId: String? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = when {
|
field = when {
|
||||||
@@ -42,6 +42,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
constructor()
|
constructor()
|
||||||
|
|
||||||
constructor(toCopy: SearchInfo?) {
|
constructor(toCopy: SearchInfo?) {
|
||||||
|
manualSelection = toCopy?.manualSelection ?: manualSelection
|
||||||
applicationId = toCopy?.applicationId
|
applicationId = toCopy?.applicationId
|
||||||
webDomain = toCopy?.webDomain
|
webDomain = toCopy?.webDomain
|
||||||
webScheme = toCopy?.webScheme
|
webScheme = toCopy?.webScheme
|
||||||
@@ -49,6 +50,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private constructor(parcel: Parcel) {
|
private constructor(parcel: Parcel) {
|
||||||
|
manualSelection = parcel.readByte().toInt() != 0
|
||||||
val readAppId = parcel.readString()
|
val readAppId = parcel.readString()
|
||||||
applicationId = if (readAppId.isNullOrEmpty()) null else readAppId
|
applicationId = if (readAppId.isNullOrEmpty()) null else readAppId
|
||||||
val readDomain = parcel.readString()
|
val readDomain = parcel.readString()
|
||||||
@@ -64,6 +66,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeByte((if (manualSelection) 1 else 0).toByte())
|
||||||
parcel.writeString(applicationId ?: "")
|
parcel.writeString(applicationId ?: "")
|
||||||
parcel.writeString(webDomain ?: "")
|
parcel.writeString(webDomain ?: "")
|
||||||
parcel.writeString(webScheme ?: "")
|
parcel.writeString(webScheme ?: "")
|
||||||
@@ -88,10 +91,9 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (other !is SearchInfo) return false
|
||||||
|
|
||||||
other as SearchInfo
|
|
||||||
|
|
||||||
|
if (manualSelection != other.manualSelection) return false
|
||||||
if (applicationId != other.applicationId) return false
|
if (applicationId != other.applicationId) return false
|
||||||
if (webDomain != other.webDomain) return false
|
if (webDomain != other.webDomain) return false
|
||||||
if (webScheme != other.webScheme) return false
|
if (webScheme != other.webScheme) return false
|
||||||
@@ -101,7 +103,8 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = applicationId?.hashCode() ?: 0
|
var result = manualSelection.hashCode()
|
||||||
|
result = 31 * result + (applicationId?.hashCode() ?: 0)
|
||||||
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
||||||
result = 31 * result + (webScheme?.hashCode() ?: 0)
|
result = 31 * result + (webScheme?.hashCode() ?: 0)
|
||||||
result = 31 * result + (otpString?.hashCode() ?: 0)
|
result = 31 * result + (otpString?.hashCode() ?: 0)
|
||||||
|
|||||||
@@ -81,14 +81,18 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
findPreference<Preference>(getString(R.string.remember_database_locations_key))?.setOnPreferenceChangeListener { _, newValue ->
|
findPreference<Preference>(getString(R.string.remember_database_locations_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
if (!(newValue as Boolean)) {
|
if (!(newValue as Boolean)) {
|
||||||
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll()
|
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll {
|
||||||
|
UriUtil.releaseAllUnnecessaryPermissionUris(activity.applicationContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.remember_keyfile_locations_key))?.setOnPreferenceChangeListener { _, newValue ->
|
findPreference<Preference>(getString(R.string.remember_keyfile_locations_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
if (!(newValue as Boolean)) {
|
if (!(newValue as Boolean)) {
|
||||||
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles()
|
FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles {
|
||||||
|
UriUtil.releaseAllUnnecessaryPermissionUris(activity.applicationContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,6 +493,12 @@ object PreferencesUtil {
|
|||||||
context.resources.getBoolean(R.bool.autofill_inline_suggestions_default))
|
context.resources.getBoolean(R.bool.autofill_inline_suggestions_default))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isAutofillManualSelectionEnable(context: Context): Boolean {
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
return prefs.getBoolean(context.getString(R.string.autofill_manual_selection_key),
|
||||||
|
context.resources.getBoolean(R.bool.autofill_manual_selection_default))
|
||||||
|
}
|
||||||
|
|
||||||
fun isAutofillSaveSearchInfoEnable(context: Context): Boolean {
|
fun isAutofillSaveSearchInfoEnable(context: Context): Boolean {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
return prefs.getBoolean(context.getString(R.string.autofill_save_search_info_key),
|
return prefs.getBoolean(context.getString(R.string.autofill_save_search_info_key),
|
||||||
@@ -630,6 +636,7 @@ object PreferencesUtil {
|
|||||||
context.getString(R.string.autofill_close_database_key) -> editor.putBoolean(name, value.toBoolean())
|
context.getString(R.string.autofill_close_database_key) -> editor.putBoolean(name, value.toBoolean())
|
||||||
context.getString(R.string.autofill_auto_search_key) -> editor.putBoolean(name, value.toBoolean())
|
context.getString(R.string.autofill_auto_search_key) -> editor.putBoolean(name, value.toBoolean())
|
||||||
context.getString(R.string.autofill_inline_suggestions_key) -> editor.putBoolean(name, value.toBoolean())
|
context.getString(R.string.autofill_inline_suggestions_key) -> editor.putBoolean(name, value.toBoolean())
|
||||||
|
context.getString(R.string.autofill_manual_selection_key) -> editor.putBoolean(name, value.toBoolean())
|
||||||
context.getString(R.string.autofill_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
|
context.getString(R.string.autofill_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
|
||||||
context.getString(R.string.autofill_ask_to_save_data_key) -> editor.putBoolean(name, value.toBoolean())
|
context.getString(R.string.autofill_ask_to_save_data_key) -> editor.putBoolean(name, value.toBoolean())
|
||||||
context.getString(R.string.autofill_application_id_blocklist_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
|
context.getString(R.string.autofill_application_id_blocklist_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mEnabled = isSwitchActivated()
|
||||||
setSwitchAction({ isChecked ->
|
setSwitchAction({ isChecked ->
|
||||||
mEnabled = isChecked
|
mEnabled = isChecked
|
||||||
}, mDays + mHours + mMinutes + mSeconds > 0)
|
}, mDays + mHours + mMinutes + mSeconds > 0)
|
||||||
|
|||||||
@@ -155,6 +155,10 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isSwitchActivated(): Boolean {
|
||||||
|
return switchElementView?.isChecked == true
|
||||||
|
}
|
||||||
|
|
||||||
fun activateSwitch() {
|
fun activateSwitch() {
|
||||||
if (switchElementView?.isChecked != true)
|
if (switchElementView?.isChecked != true)
|
||||||
switchElementView?.isChecked = true
|
switchElementView?.isChecked = true
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import android.content.IntentFilter
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||||
@@ -142,19 +141,5 @@ fun Context.closeDatabase(database: Database?) {
|
|||||||
database?.clearAndClose(this)
|
database?.clearAndClose(this)
|
||||||
|
|
||||||
// Release not useful URI permission
|
// Release not useful URI permission
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
UriUtil.releaseAllUnnecessaryPermissionUris(applicationContext)
|
||||||
applicationContext?.let { appContext ->
|
|
||||||
val fileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(appContext)
|
|
||||||
fileDatabaseHistoryAction.getDatabaseFileList { databaseFileList ->
|
|
||||||
val listToNotRemove = databaseFileList.map { it.databaseUri }
|
|
||||||
// Remove URI permission for not database files
|
|
||||||
val resolver = appContext.contentResolver
|
|
||||||
resolver.persistedUriPermissions.forEach { uriPermission ->
|
|
||||||
val uri = uriPermission.uri
|
|
||||||
if (!listToNotRemove.contains(uri))
|
|
||||||
UriUtil.releaseUriPermission(resolver, uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ import android.util.Log
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -187,6 +188,32 @@ object UriUtil {
|
|||||||
persistUriPermission(contentResolver, uri, release = true, readOnly = false)
|
persistUriPermission(contentResolver, uri, release = true, readOnly = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun releaseAllUnnecessaryPermissionUris(applicationContext: Context?) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
applicationContext?.let { appContext ->
|
||||||
|
val fileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(appContext)
|
||||||
|
fileDatabaseHistoryAction.getDatabaseFileList { databaseFileList ->
|
||||||
|
val listToNotRemove = mutableListOf<Uri>()
|
||||||
|
databaseFileList.forEach {
|
||||||
|
it.databaseUri?.let { databaseUri ->
|
||||||
|
listToNotRemove.add(databaseUri)
|
||||||
|
}
|
||||||
|
it.keyFileUri?.let { keyFileUri ->
|
||||||
|
listToNotRemove.add(keyFileUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove URI permission for not database files
|
||||||
|
val resolver = appContext.contentResolver
|
||||||
|
resolver.persistedUriPermissions.forEach { uriPermission ->
|
||||||
|
val uri = uriPermission.uri
|
||||||
|
if (!listToNotRemove.contains(uri))
|
||||||
|
releaseUriPermission(resolver, uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getUriFromIntent(intent: Intent, key: String): Uri? {
|
fun getUriFromIntent(intent: Intent, key: String): Uri? {
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
|||||||
field: Field): TextEditFieldView? {
|
field: Field): TextEditFieldView? {
|
||||||
return context?.let {
|
return context?.let {
|
||||||
TextEditFieldView(it).apply {
|
TextEditFieldView(it).apply {
|
||||||
setProtection(field.protectedValue.isProtected, mHideProtectedValue)
|
// hiddenProtectedValue (mHideProtectedValue) don't work with TextInputLayout
|
||||||
|
setProtection(field.protectedValue.isProtected)
|
||||||
setMaxChars(templateAttribute.options.getNumberChars())
|
setMaxChars(templateAttribute.options.getNumberChars())
|
||||||
setMaxLines(templateAttribute.options.getNumberLines())
|
setMaxLines(templateAttribute.options.getNumberLines())
|
||||||
setActionClick(templateAttribute, field, this)
|
setActionClick(templateAttribute, field, this)
|
||||||
|
|||||||
@@ -161,8 +161,7 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean) {
|
fun setProtection(protection: Boolean) {
|
||||||
// hiddenProtectedValue don't work with TextInputLayout
|
|
||||||
if (protection) {
|
if (protection) {
|
||||||
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
|
|||||||
@@ -110,6 +110,21 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
|
|||||||
fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile) {
|
fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile) {
|
||||||
mFileDatabaseHistoryAction?.deleteDatabaseFile(databaseFileToDelete) { databaseFileDeleted ->
|
mFileDatabaseHistoryAction?.deleteDatabaseFile(databaseFileToDelete) { databaseFileDeleted ->
|
||||||
databaseFileDeleted?.let { _ ->
|
databaseFileDeleted?.let { _ ->
|
||||||
|
// Release database and keyfile URIs permissions
|
||||||
|
val contentResolver = getApplication<App>().applicationContext.contentResolver
|
||||||
|
databaseFileDeleted.databaseUri?.let { databaseUri ->
|
||||||
|
UriUtil.releaseUriPermission(
|
||||||
|
contentResolver,
|
||||||
|
databaseUri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
databaseFileDeleted.keyFileUri?.let { keyFileUri ->
|
||||||
|
UriUtil.releaseUriPermission(
|
||||||
|
contentResolver,
|
||||||
|
keyFileUri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Call the feedback
|
||||||
databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
|
databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
|
||||||
databaseFileAction = DatabaseFileAction.DELETE
|
databaseFileAction = DatabaseFileAction.DELETE
|
||||||
databaseFileToActivate = databaseFileDeleted
|
databaseFileToActivate = databaseFileDeleted
|
||||||
|
|||||||
@@ -5,17 +5,28 @@ import androidx.lifecycle.LiveData
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
import com.kunzisoft.keepass.app.database.IOActionTask
|
||||||
import com.kunzisoft.keepass.database.element.*
|
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.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.template.Template
|
import com.kunzisoft.keepass.database.element.template.Template
|
||||||
import com.kunzisoft.keepass.model.*
|
import com.kunzisoft.keepass.model.*
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class EntryEditViewModel: NodeEditViewModel() {
|
class EntryEditViewModel: NodeEditViewModel() {
|
||||||
|
|
||||||
|
private var mEntryId: NodeId<UUID>? = null
|
||||||
|
private var mParentId: NodeId<*>? = null
|
||||||
|
private var mRegisterInfo: RegisterInfo? = null
|
||||||
|
private var mSearchInfo: SearchInfo? = null
|
||||||
|
private var mParent: Group? = null
|
||||||
|
private var mEntry: Entry? = null
|
||||||
|
private var mIsTemplate: Boolean = false
|
||||||
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
|
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
|
||||||
|
|
||||||
val templatesEntry : LiveData<TemplatesEntry> get() = _templatesEntry
|
val templatesEntry : LiveData<TemplatesEntry?> get() = _templatesEntry
|
||||||
private val _templatesEntry = MutableLiveData<TemplatesEntry>()
|
private val _templatesEntry = MutableLiveData<TemplatesEntry?>()
|
||||||
|
|
||||||
val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate
|
val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate
|
||||||
private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>()
|
private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>()
|
||||||
@@ -23,7 +34,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
|||||||
private val _onEntrySaved = SingleLiveEvent<EntrySave>()
|
private val _onEntrySaved = SingleLiveEvent<EntrySave>()
|
||||||
|
|
||||||
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
|
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
|
||||||
private val _onTemplateChanged = SingleLiveEvent<Template>()
|
private val _onTemplateChanged = MutableLiveData<Template>()
|
||||||
|
|
||||||
val requestPasswordSelection : LiveData<Field> get() = _requestPasswordSelection
|
val requestPasswordSelection : LiveData<Field> get() = _requestPasswordSelection
|
||||||
private val _requestPasswordSelection = SingleLiveEvent<Field>()
|
private val _requestPasswordSelection = SingleLiveEvent<Field>()
|
||||||
@@ -53,16 +64,100 @@ class EntryEditViewModel: NodeEditViewModel() {
|
|||||||
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
|
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
|
||||||
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
|
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
|
||||||
|
|
||||||
|
fun loadDatabase(database: Database?) {
|
||||||
|
loadTemplateEntry(database, mEntryId, mParentId, mRegisterInfo, mSearchInfo)
|
||||||
|
}
|
||||||
|
|
||||||
fun loadTemplateEntry(database: Database,
|
fun loadTemplateEntry(database: Database?,
|
||||||
|
entryId: NodeId<UUID>?,
|
||||||
|
parentId: NodeId<*>?,
|
||||||
|
registerInfo: RegisterInfo?,
|
||||||
|
searchInfo: SearchInfo?) {
|
||||||
|
this.mEntryId = entryId
|
||||||
|
this.mParentId = parentId
|
||||||
|
this.mRegisterInfo = registerInfo
|
||||||
|
this.mSearchInfo = searchInfo
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// Define if current entry is a template (in direct template group)
|
||||||
|
mIsTemplate = database.entryIsTemplate(mEntry)
|
||||||
|
decodeTemplateEntry(
|
||||||
|
database,
|
||||||
|
entry,
|
||||||
|
mIsTemplate,
|
||||||
|
registerInfo,
|
||||||
|
searchInfo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ templatesEntry ->
|
||||||
|
mEntryId = null
|
||||||
|
_templatesEntry.value = templatesEntry
|
||||||
|
}
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
decodeTemplateEntry(
|
||||||
|
database,
|
||||||
|
mEntry,
|
||||||
|
mIsTemplate,
|
||||||
|
registerInfo,
|
||||||
|
searchInfo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ templatesEntry ->
|
||||||
|
mParentId = null
|
||||||
|
_templatesEntry.value = templatesEntry
|
||||||
|
}
|
||||||
|
).execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeTemplateEntry(database: Database,
|
||||||
entry: Entry?,
|
entry: Entry?,
|
||||||
isTemplate: Boolean,
|
isTemplate: Boolean,
|
||||||
registerInfo: RegisterInfo?,
|
registerInfo: RegisterInfo?,
|
||||||
searchInfo: SearchInfo?) {
|
searchInfo: SearchInfo?): TemplatesEntry {
|
||||||
IOActionTask(
|
|
||||||
{
|
|
||||||
val templates = database.getTemplates(isTemplate)
|
val templates = database.getTemplates(isTemplate)
|
||||||
val entryTemplate = entry?.let { database.getTemplate(it) } ?: Template.STANDARD
|
val entryTemplate = entry?.let { database.getTemplate(it) }
|
||||||
|
?: Template.STANDARD
|
||||||
var entryInfo: EntryInfo? = null
|
var entryInfo: EntryInfo? = null
|
||||||
// Decode the entry / load entry info
|
// Decode the entry / load entry info
|
||||||
entry?.let {
|
entry?.let {
|
||||||
@@ -80,12 +175,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TemplatesEntry(templates, entryTemplate, entryInfo)
|
return TemplatesEntry(isTemplate, templates, entryTemplate, entryInfo)
|
||||||
},
|
|
||||||
{ templatesEntry ->
|
|
||||||
_templatesEntry.value = templatesEntry
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeTemplate(template: Template) {
|
fun changeTemplate(template: Template) {
|
||||||
@@ -94,8 +184,8 @@ class EntryEditViewModel: NodeEditViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestEntryInfoUpdate(database: Database?, entry: Entry?, parent: Group?) {
|
fun requestEntryInfoUpdate(database: Database?) {
|
||||||
_requestEntryInfoUpdate.value = EntryUpdate(database, entry, parent)
|
_requestEntryInfoUpdate.value = EntryUpdate(database, mEntry, mParent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveEntryInfo(database: Database?, entry: Entry?, parent: Group?, entryInfo: EntryInfo) {
|
fun saveEntryInfo(database: Database?, entry: Entry?, parent: Group?, entryInfo: EntryInfo) {
|
||||||
@@ -222,7 +312,10 @@ class EntryEditViewModel: NodeEditViewModel() {
|
|||||||
_onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition)
|
_onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TemplatesEntry(val templates: List<Template>, val defaultTemplate: Template, val entryInfo: EntryInfo?)
|
data class TemplatesEntry(val isTemplate: Boolean,
|
||||||
|
val templates: List<Template>,
|
||||||
|
val defaultTemplate: Template,
|
||||||
|
val entryInfo: EntryInfo?)
|
||||||
data class EntryUpdate(val database: Database?, val entry: Entry?, val parent: Group?)
|
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 EntrySave(val oldEntry: Entry, val newEntry: Entry, val parent: Group?)
|
||||||
data class FieldEdition(val oldField: Field?, val newField: Field?)
|
data class FieldEdition(val oldField: Field?, val newField: Field?)
|
||||||
|
|||||||
@@ -36,20 +36,14 @@ import java.util.*
|
|||||||
|
|
||||||
class EntryViewModel: ViewModel() {
|
class EntryViewModel: ViewModel() {
|
||||||
|
|
||||||
val template : LiveData<Template> get() = _template
|
private var mMainEntryId: NodeId<UUID>? = null
|
||||||
private val _template = MutableLiveData<Template>()
|
private var mHistoryPosition: Int = -1
|
||||||
|
|
||||||
val mainEntryId : LiveData<NodeId<UUID>?> get() = _mainEntryId
|
val entryInfoHistory : LiveData<EntryInfoHistory?> get() = _entryInfoHistory
|
||||||
private val _mainEntryId = MutableLiveData<NodeId<UUID>?>()
|
private val _entryInfoHistory = MutableLiveData<EntryInfoHistory?>()
|
||||||
|
|
||||||
val historyPosition : LiveData<Int> get() = _historyPosition
|
val entryHistory : LiveData<List<EntryInfo>?> get() = _entryHistory
|
||||||
private val _historyPosition = MutableLiveData<Int>()
|
private val _entryHistory = MutableLiveData<List<EntryInfo>?>()
|
||||||
|
|
||||||
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
|
|
||||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
|
||||||
|
|
||||||
val entryHistory : LiveData<List<EntryInfo>> get() = _entryHistory
|
|
||||||
private val _entryHistory = MutableLiveData<List<EntryInfo>>()
|
|
||||||
|
|
||||||
val onOtpElementUpdated : LiveData<OtpElement?> get() = _onOtpElementUpdated
|
val onOtpElementUpdated : LiveData<OtpElement?> get() = _onOtpElementUpdated
|
||||||
private val _onOtpElementUpdated = SingleLiveEvent<OtpElement?>()
|
private val _onOtpElementUpdated = SingleLiveEvent<OtpElement?>()
|
||||||
@@ -62,11 +56,18 @@ class EntryViewModel: ViewModel() {
|
|||||||
val historySelected : LiveData<EntryHistory> get() = _historySelected
|
val historySelected : LiveData<EntryHistory> get() = _historySelected
|
||||||
private val _historySelected = SingleLiveEvent<EntryHistory>()
|
private val _historySelected = SingleLiveEvent<EntryHistory>()
|
||||||
|
|
||||||
fun loadEntry(database: Database?, entryId: NodeId<UUID>?, historyPosition: Int) {
|
fun loadDatabase(database: Database?) {
|
||||||
if (database != null && entryId != null) {
|
loadEntry(database, mMainEntryId, mHistoryPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadEntry(database: Database?, mainEntryId: NodeId<UUID>?, historyPosition: Int = -1) {
|
||||||
|
this.mMainEntryId = mainEntryId
|
||||||
|
this.mHistoryPosition = historyPosition
|
||||||
|
|
||||||
|
if (database != null && mainEntryId != null) {
|
||||||
IOActionTask(
|
IOActionTask(
|
||||||
{
|
{
|
||||||
val mainEntry = database.getEntryById(entryId)
|
val mainEntry = database.getEntryById(mainEntryId)
|
||||||
val currentEntry = if (historyPosition > -1) {
|
val currentEntry = if (historyPosition > -1) {
|
||||||
mainEntry?.getHistory()?.get(historyPosition)
|
mainEntry?.getHistory()?.get(historyPosition)
|
||||||
} else {
|
} else {
|
||||||
@@ -91,6 +92,7 @@ class EntryViewModel: ViewModel() {
|
|||||||
|
|
||||||
EntryInfoHistory(
|
EntryInfoHistory(
|
||||||
mainEntry!!.nodeId,
|
mainEntry!!.nodeId,
|
||||||
|
historyPosition,
|
||||||
entryTemplate,
|
entryTemplate,
|
||||||
it.getEntryInfo(database),
|
it.getEntryInfo(database),
|
||||||
entryInfoHistory
|
entryInfoHistory
|
||||||
@@ -99,22 +101,13 @@ class EntryViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ entryInfoHistory ->
|
{ entryInfoHistory ->
|
||||||
if (entryInfoHistory != null) {
|
_entryInfoHistory.value = entryInfoHistory
|
||||||
_mainEntryId.value = entryInfoHistory.mainEntryId
|
_entryHistory.value = entryInfoHistory?.entryHistory
|
||||||
_historyPosition.value = historyPosition
|
|
||||||
_template.value = entryInfoHistory.template
|
|
||||||
_entryInfo.value = entryInfoHistory.entryInfo
|
|
||||||
_entryHistory.value = entryInfoHistory.entryHistory
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).execute()
|
).execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateEntry(database: Database?) {
|
|
||||||
loadEntry(database, _mainEntryId.value, _historyPosition.value ?: -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onOtpElementUpdated(optElement: OtpElement?) {
|
fun onOtpElementUpdated(optElement: OtpElement?) {
|
||||||
_onOtpElementUpdated.value = optElement
|
_onOtpElementUpdated.value = optElement
|
||||||
}
|
}
|
||||||
@@ -132,6 +125,7 @@ class EntryViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class EntryInfoHistory(var mainEntryId: NodeId<UUID>,
|
data class EntryInfoHistory(var mainEntryId: NodeId<UUID>,
|
||||||
|
var historyPosition: Int,
|
||||||
val template: Template,
|
val template: Template,
|
||||||
val entryInfo: EntryInfo,
|
val entryInfo: EntryInfo,
|
||||||
val entryHistory: List<EntryInfo>)
|
val entryHistory: List<EntryInfo>)
|
||||||
|
|||||||
9
app/src/main/res/drawable/ic_arrow_right_green_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_arrow_right_green_24dp.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/green"
|
||||||
|
android:pathData="M10,17l5,-5 -5,-5v10z"/>
|
||||||
|
</vector>
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
style="@style/KeepassDXStyle.TextAppearance.Info" />
|
style="@style/KeepassDXStyle.TextAppearance.Info" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
|
|||||||
@@ -23,15 +23,15 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/autofill_entry_icon"
|
android:id="@+id/autofill_entry_icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="24dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="24dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginRight="12dp"
|
android:layout_marginRight="12dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:layout_marginLeft="12dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:contentDescription="@string/content_description_entry_icon"
|
android:contentDescription="@string/content_description_entry_icon"
|
||||||
android:src="@drawable/ic_key_white_24dp" />
|
android:src="@drawable/ic_arrow_right_green_24dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/autofill_entry_text"
|
android:id="@+id/autofill_entry_text"
|
||||||
|
|||||||
42
app/src/main/res/layout/item_autofill_select_entry.xml
Normal file
42
app/src/main/res/layout/item_autofill_select_entry.xml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
* Copyright (C) 2017 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/windowBackground"
|
||||||
|
android:minHeight="36dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:contentDescription="@string/autofill_select_entry"
|
||||||
|
android:src="@drawable/ic_arrow_right_green_24dp"/>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/autofill_select_entry"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
* Copyright (C) 2017 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<include
|
||||||
|
layout="@layout/item_autofill_app_id"/>
|
||||||
|
<include
|
||||||
|
layout="@layout/item_autofill_select_entry"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
* Copyright (C) 2017 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<include
|
||||||
|
layout="@layout/item_autofill_web_domain"/>
|
||||||
|
<include
|
||||||
|
layout="@layout/item_autofill_select_entry"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -203,6 +203,7 @@
|
|||||||
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
|
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
|
||||||
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
|
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
|
||||||
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um Formulare schnell in anderen Apps auszufüllen</string>
|
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um Formulare schnell in anderen Apps auszufüllen</string>
|
||||||
|
<string name="autofill_select_entry">Eintrag auswählen…</string>
|
||||||
<string name="clipboard">Zwischenablage</string>
|
<string name="clipboard">Zwischenablage</string>
|
||||||
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
|
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
|
||||||
<string name="biometric_delete_all_key_summary">Alle Verschlüsselungsschlüssel löschen, die mit der modernen Entsperrerkennung zusammenhängen</string>
|
<string name="biometric_delete_all_key_summary">Alle Verschlüsselungsschlüssel löschen, die mit der modernen Entsperrerkennung zusammenhängen</string>
|
||||||
@@ -448,6 +449,8 @@
|
|||||||
<string name="validate">Validieren</string>
|
<string name="validate">Validieren</string>
|
||||||
<string name="autofill_auto_search_summary">Suchergebnisse automatisch nach Web-Domain oder Anwendungs-ID vorschlagen</string>
|
<string name="autofill_auto_search_summary">Suchergebnisse automatisch nach Web-Domain oder Anwendungs-ID vorschlagen</string>
|
||||||
<string name="autofill_auto_search_title">Automatische Suche</string>
|
<string name="autofill_auto_search_title">Automatische Suche</string>
|
||||||
|
<string name="autofill_manual_selection_title">Manuelle Auswahl</string>
|
||||||
|
<string name="autofill_manual_selection_summary">Manuelle Auswahl des Datenbank-Eintrags ermöglichen</string>
|
||||||
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
|
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
|
||||||
<string name="lock_database_show_button_title">Sperrtaste anzeigen</string>
|
<string name="lock_database_show_button_title">Sperrtaste anzeigen</string>
|
||||||
<string name="autofill_preference_title">Einstellungen für automatisches Ausfüllen</string>
|
<string name="autofill_preference_title">Einstellungen für automatisches Ausfüllen</string>
|
||||||
|
|||||||
@@ -171,6 +171,7 @@
|
|||||||
<string name="autofill_sign_in_prompt">Se connecter avec KeePassDX</string>
|
<string name="autofill_sign_in_prompt">Se connecter avec KeePassDX</string>
|
||||||
<string name="set_autofill_service_title">Définir le service de remplissage automatique par défaut</string>
|
<string name="set_autofill_service_title">Définir le service de remplissage automatique par défaut</string>
|
||||||
<string name="autofill_explanation_summary">Activer le remplissage automatique pour remplir rapidement des formulaires dans d’autres applications</string>
|
<string name="autofill_explanation_summary">Activer le remplissage automatique pour remplir rapidement des formulaires dans d’autres applications</string>
|
||||||
|
<string name="autofill_select_entry">Sélectionner une entrée…</string>
|
||||||
<string name="password_size_title">Taille du mot de passe généré</string>
|
<string name="password_size_title">Taille du mot de passe généré</string>
|
||||||
<string name="password_size_summary">Défini la taille par défaut des mots de passe générés</string>
|
<string name="password_size_summary">Défini la taille par défaut des mots de passe générés</string>
|
||||||
<string name="list_password_generator_options_title">Caractères de mot de passe</string>
|
<string name="list_password_generator_options_title">Caractères de mot de passe</string>
|
||||||
|
|||||||
@@ -159,6 +159,8 @@
|
|||||||
<bool name="autofill_auto_search_default" translatable="false">true</bool>
|
<bool name="autofill_auto_search_default" translatable="false">true</bool>
|
||||||
<string name="autofill_inline_suggestions_key" translatable="false">autofill_inline_suggestions_key</string>
|
<string name="autofill_inline_suggestions_key" translatable="false">autofill_inline_suggestions_key</string>
|
||||||
<bool name="autofill_inline_suggestions_default" translatable="false">false</bool>
|
<bool name="autofill_inline_suggestions_default" translatable="false">false</bool>
|
||||||
|
<string name="autofill_manual_selection_key" translatable="false">autofill_manual_selection_key</string>
|
||||||
|
<bool name="autofill_manual_selection_default" translatable="false">true</bool>
|
||||||
<string name="autofill_save_search_info_key" translatable="false">autofill_save_search_info_key</string>
|
<string name="autofill_save_search_info_key" translatable="false">autofill_save_search_info_key</string>
|
||||||
<bool name="autofill_save_search_info_default" translatable="false">true</bool>
|
<bool name="autofill_save_search_info_default" translatable="false">true</bool>
|
||||||
<string name="autofill_ask_to_save_data_key" translatable="false">autofill_ask_to_save_data_key</string>
|
<string name="autofill_ask_to_save_data_key" translatable="false">autofill_ask_to_save_data_key</string>
|
||||||
|
|||||||
@@ -352,6 +352,7 @@
|
|||||||
<string name="autofill_service_name">KeePassDX form autofilling</string>
|
<string name="autofill_service_name">KeePassDX form autofilling</string>
|
||||||
<string name="autofill_sign_in_prompt">Sign in with KeePassDX</string>
|
<string name="autofill_sign_in_prompt">Sign in with KeePassDX</string>
|
||||||
<string name="autofill_explanation_summary">Enable autofilling to quickly fill out forms in other apps</string>
|
<string name="autofill_explanation_summary">Enable autofilling to quickly fill out forms in other apps</string>
|
||||||
|
<string name="autofill_select_entry">Select entry…</string>
|
||||||
<string name="set_autofill_service_title">Set default autofill service</string>
|
<string name="set_autofill_service_title">Set default autofill service</string>
|
||||||
<string name="autofill_preference_title">Autofill settings</string>
|
<string name="autofill_preference_title">Autofill settings</string>
|
||||||
<string name="password_size_title">Generated password size</string>
|
<string name="password_size_title">Generated password size</string>
|
||||||
@@ -487,6 +488,8 @@
|
|||||||
<string name="autofill_auto_search_summary">Automatically suggest search results from the web domain or application ID</string>
|
<string name="autofill_auto_search_summary">Automatically suggest search results from the web domain or application ID</string>
|
||||||
<string name="autofill_inline_suggestions_title">Inline suggestions</string>
|
<string name="autofill_inline_suggestions_title">Inline suggestions</string>
|
||||||
<string name="autofill_inline_suggestions_summary">Attempt to display autofill suggestions directly from a compatible keyboard</string>
|
<string name="autofill_inline_suggestions_summary">Attempt to display autofill suggestions directly from a compatible keyboard</string>
|
||||||
|
<string name="autofill_manual_selection_title">Manual selection</string>
|
||||||
|
<string name="autofill_manual_selection_summary">Display option to let the user select database entry</string>
|
||||||
<string name="autofill_save_search_info_title">Save search info</string>
|
<string name="autofill_save_search_info_title">Save search info</string>
|
||||||
<string name="autofill_save_search_info_summary">Try to save search information when making a manual entry selection</string>
|
<string name="autofill_save_search_info_summary">Try to save search information when making a manual entry selection</string>
|
||||||
<string name="autofill_ask_to_save_data_title">Ask to save data</string>
|
<string name="autofill_ask_to_save_data_title">Ask to save data</string>
|
||||||
|
|||||||
@@ -35,6 +35,11 @@
|
|||||||
android:title="@string/autofill_inline_suggestions_title"
|
android:title="@string/autofill_inline_suggestions_title"
|
||||||
android:summary="@string/autofill_inline_suggestions_summary"
|
android:summary="@string/autofill_inline_suggestions_summary"
|
||||||
android:defaultValue="@bool/autofill_inline_suggestions_default"/>
|
android:defaultValue="@bool/autofill_inline_suggestions_default"/>
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="@string/autofill_manual_selection_key"
|
||||||
|
android:title="@string/autofill_manual_selection_title"
|
||||||
|
android:summary="@string/autofill_manual_selection_summary"
|
||||||
|
android:defaultValue="@bool/autofill_manual_selection_default"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/save">
|
android:title="@string/save">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
* Add / Manage dynamic templates #191
|
* Add / Manage dynamic templates #191
|
||||||
* Allow to manually select RecycleBin group and Templates group #191
|
* Manually select RecycleBin group and Templates group #191
|
||||||
* Setting to display OTP Token in list #655
|
* Setting to display OTP Token in list #655
|
||||||
* Fix timeout in dialogs #716
|
* Fix timeout in dialogs #716
|
||||||
* Check URI permissions #626
|
* Check URI permissions #626
|
||||||
* Improvements #1035 #1043 #942 #1021 #1027
|
* Better autofill implementation #943 #946 #984 #1070 (Thx @uduerholz)
|
||||||
|
* Improvements #680 #1035 #1043 #942 #1021 #1027
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
* Paramètres pour afficher les jetons OTP dans la liste #655
|
* Paramètres pour afficher les jetons OTP dans la liste #655
|
||||||
* Correction du délai d'expiration dans les dialogues #716
|
* Correction du délai d'expiration dans les dialogues #716
|
||||||
* Vérification des permissions URI #626
|
* Vérification des permissions URI #626
|
||||||
* Améliorations #1035 #1043 #942 #1021 #1027
|
* Meilleure implémentation du remplissage auto #943 #946 #984 #1070 (Thx @uduerholz)
|
||||||
|
* Améliorations #680 #1035 #1043 #942 #1021 #1027
|
||||||
Reference in New Issue
Block a user