Compare commits

...

33 Commits

Author SHA1 Message Date
J-Jamet
cc3204453e Upgrade to 3.0.0_beta03 2021-09-03 15:31:17 +02:00
J-Jamet
5ef8d3b7b9 Update CHANGELOG 2021-09-03 15:25:30 +02:00
J-Jamet
2a9de97a19 Default manual selection to true 2021-09-03 15:14:39 +02:00
J-Jamet
9cecfed417 Add dots 2021-09-03 15:10:18 +02:00
J-Jamet
319715918a Small change to merge views 2021-09-03 15:02:40 +02:00
J-Jamet
a3bf6e8b6d Small change for consistency 2021-09-03 14:41:45 +02:00
J-Jamet
c4062658ce Fix search info parcelable 2021-09-03 14:39:19 +02:00
J-Jamet
01a5de413e Merge branch 'develop' of git://github.com/uduerholz/KeePassDX into uduerholz-develop 2021-09-03 14:18:51 +02:00
J-Jamet
e4c22b1f29 Change remote views when the database is open 2021-09-03 12:44:01 +02:00
J-Jamet
b10e60126f Fix UUID view 2021-09-02 17:39:08 +02:00
J-Jamet
ef1f27f421 Check null view model callback 2021-09-02 17:31:51 +02:00
J-Jamet
0ed208675c Fix reloading from history 2021-09-02 17:18:01 +02:00
J-Jamet
00f7a0a194 Better entry activity view model to fix reloading 2021-09-02 17:05:07 +02:00
J-Jamet
935d4f4a64 Unused throw 2021-09-02 16:13:25 +02:00
J-Jamet
dc4d88260d Fix database reload 2021-09-02 16:13:08 +02:00
J-Jamet
18934601da Fix education 2021-09-02 14:26:08 +02:00
J-Jamet
4ea811aeda Fix menu in template creation 2021-09-02 13:48:48 +02:00
J-Jamet
f8fdecdc8f Fix multiple loading by move variables in entry edit view model 2021-09-02 11:12:19 +02:00
J-Jamet
5467c61137 Add equals in node info 2021-09-02 11:10:51 +02:00
J-Jamet
9c72b4cc56 Fix timeout switch 2021-09-01 18:51:18 +02:00
J-Jamet
9102217bc3 Fix template lost after orientation change #1069 2021-09-01 17:38:29 +02:00
Uli
0e8fd7b2c4 Merge branch 'Kunzisoft:develop' into develop 2021-09-01 14:41:07 +02:00
J-Jamet
a06ea8fe55 Fix warning 2021-08-30 11:13:38 +02:00
J-Jamet
31eb0fb48a Upgrade to 3.0.0_beta02 2021-08-30 11:05:38 +02:00
J-Jamet
d6a012e85f Fix Permissions #1066 2021-08-30 11:05:08 +02:00
Uli
c6e2342ab4 Merge branch 'Kunzisoft:develop' into develop 2021-08-29 17:04:31 +02:00
Ulrich Dürholz
b977792168 Autofill manual selection for all form fields 2021-08-29 11:23:22 +02:00
Uli
2595cf87d8 Merge branch 'Kunzisoft:develop' into develop 2021-08-29 10:50:08 +02:00
Ulrich Dürholz
f4342f1448 Manual selection for inline suggestions 2021-08-29 10:16:15 +02:00
Ulrich Dürholz
c71ef24052 Add icon for manual autofill selection 2021-08-28 12:48:03 +02:00
Ulrich Dürholz
39b817bc69 Let user select entry for autofill 2021-08-27 18:23:32 +02:00
Uli
e3adaba3b3 Merge branch 'Kunzisoft:develop' into develop 2021-08-24 16:39:45 +02:00
Ulrich Dürholz
c62064002f Fix small issue with credit card autofill 2021-07-27 17:36:56 +02:00
45 changed files with 754 additions and 374 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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,

View File

@@ -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)

View File

@@ -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?

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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())

View File

@@ -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 {

View File

@@ -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)
} }

View File

@@ -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")
} }
} }
} }

View File

@@ -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()
} }

View File

@@ -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()
} }

View File

@@ -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)
} }
} }

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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(

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}
}
}
}
} }

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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?)

View File

@@ -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>)

View 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>

View File

@@ -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"

View File

@@ -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"

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 dautres applications</string> <string name="autofill_explanation_summary">Activer le remplissage automatique pour remplir rapidement des formulaires dans dautres 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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

View File

@@ -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