mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'feature/Autofill_Blocklist' #571
This commit is contained in:
@@ -307,9 +307,8 @@ class GroupActivity : LockingActivity(),
|
||||
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
|
||||
if (searchInfo != null) {
|
||||
intent.action = Intent.ACTION_SEARCH
|
||||
val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
|
||||
intent.removeExtra(KEY_SEARCH_INFO)
|
||||
intent.putExtra(SearchManager.QUERY, searchQuery)
|
||||
intent.putExtra(SearchManager.QUERY, searchInfo.toString())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.kunzisoft.keepass.activities.AutofillLauncherActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class KeeAutofillService : AutofillService() {
|
||||
@@ -45,32 +46,53 @@ class KeeAutofillService : AutofillService() {
|
||||
// Check user's settings for authenticating Responses and Datasets.
|
||||
StructureParser(latestStructure).parse()?.let { parseResult ->
|
||||
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.domain
|
||||
// Build search info only if applicationId or webDomain are not blocked
|
||||
var searchAllowed = true
|
||||
parseResult.applicationId?.let {
|
||||
if (PreferencesUtil.applicationIdBlocklist(this).any { appIdBlocked ->
|
||||
it.contains(appIdBlocked)
|
||||
}
|
||||
) {
|
||||
searchAllowed = false
|
||||
}
|
||||
}
|
||||
parseResult.domain?.let {
|
||||
if (PreferencesUtil.webDomainBlocklist(this).any { webDomainBlocked ->
|
||||
it.contains(webDomainBlocked)
|
||||
}
|
||||
) {
|
||||
searchAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
Database.getInstance(),
|
||||
searchInfo,
|
||||
{ items ->
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
AutofillHelper.addHeader(responseBuilder, packageName,
|
||||
parseResult.domain, parseResult.applicationId)
|
||||
items.forEach {
|
||||
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
|
||||
if (searchAllowed) {
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.domain
|
||||
}
|
||||
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
Database.getInstance(),
|
||||
searchInfo,
|
||||
{ items ->
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
AutofillHelper.addHeader(responseBuilder, packageName,
|
||||
parseResult.domain, parseResult.applicationId)
|
||||
items.forEach {
|
||||
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
|
||||
}
|
||||
callback.onSuccess(responseBuilder.build())
|
||||
},
|
||||
{
|
||||
// Show UI if no search result
|
||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||
},
|
||||
{
|
||||
// Show UI if database not open
|
||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||
}
|
||||
callback.onSuccess(responseBuilder.build())
|
||||
},
|
||||
{
|
||||
// Show UI if no search result
|
||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||
},
|
||||
{
|
||||
// Show UI if database not open
|
||||
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -406,12 +406,9 @@ class Database {
|
||||
|
||||
fun createVirtualGroupFromSearch(searchInfo: SearchInfo,
|
||||
max: Int = Integer.MAX_VALUE): Group? {
|
||||
val query = (if (searchInfo.webDomain != null)
|
||||
searchInfo.webDomain
|
||||
else
|
||||
searchInfo.applicationId)
|
||||
?: return null
|
||||
return mSearchHelper?.createVirtualGroupWithSearchResult(this, query, SearchParameters().apply {
|
||||
if (searchInfo.containsOnlyNullValues())
|
||||
return null
|
||||
return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchInfo.toString(), SearchParameters().apply {
|
||||
searchInTitles = false
|
||||
searchInUserNames = false
|
||||
searchInPasswords = false
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||
|
||||
class SearchInfo : Parcelable {
|
||||
class SearchInfo : ObjectNameResource, Parcelable {
|
||||
|
||||
var applicationId: String? = null
|
||||
set(value) {
|
||||
field = when {
|
||||
value == null -> null
|
||||
Regex(APPLICATION_ID_REGEX).matches(value) -> value
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
var webDomain: String? = null
|
||||
set(value) {
|
||||
field = when {
|
||||
value == null -> null
|
||||
Regex(WEB_DOMAIN_REGEX).matches(value) -> value
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
@@ -26,7 +42,40 @@ class SearchInfo : Parcelable {
|
||||
parcel.writeString(webDomain ?: "")
|
||||
}
|
||||
|
||||
override fun getName(resources: Resources): String {
|
||||
return applicationId ?: webDomain ?: ""
|
||||
}
|
||||
|
||||
fun containsOnlyNullValues(): Boolean {
|
||||
return applicationId == null && webDomain == null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SearchInfo
|
||||
|
||||
if (applicationId != other.applicationId) return false
|
||||
if (webDomain != other.webDomain) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = applicationId?.hashCode() ?: 0
|
||||
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return applicationId ?: webDomain ?: ""
|
||||
}
|
||||
|
||||
companion object {
|
||||
// https://gist.github.com/rishabhmhjn/8663966
|
||||
const val APPLICATION_ID_REGEX = "^(?:[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)(?:\\.[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)+\$"
|
||||
const val WEB_DOMAIN_REGEX = "^(?!://)([a-zA-Z0-9-_]+\\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\\.[a-zA-Z]{2,11}?\$"
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<SearchInfo> = object : Parcelable.Creator<SearchInfo> {
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistAppIdPreferenceDialogFragmentCompat
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlocklistWebDomainPreferenceDialogFragmentCompat
|
||||
|
||||
class AutofillSettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
@@ -30,4 +33,34 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
|
||||
// Load the preferences from an XML resource
|
||||
setPreferencesFromResource(R.xml.preferences_autofill, rootKey)
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference?) {
|
||||
var otherDialogFragment = false
|
||||
|
||||
var dialogFragment: DialogFragment? = null
|
||||
|
||||
when (preference?.key) {
|
||||
getString(R.string.autofill_application_id_blocklist_key) -> {
|
||||
dialogFragment = AutofillBlocklistAppIdPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
getString(R.string.autofill_web_domain_blocklist_key) -> {
|
||||
dialogFragment = AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
else -> otherDialogFragment = true
|
||||
}
|
||||
|
||||
if (dialogFragment != null) {
|
||||
dialogFragment.setTargetFragment(this, 0)
|
||||
dialogFragment.show(parentFragmentManager, TAG_AUTOFILL_PREF_FRAGMENT)
|
||||
}
|
||||
// Could not be handled here. Try with the super method.
|
||||
else if (otherDialogFragment) {
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG_AUTOFILL_PREF_FRAGMENT = "TAG_AUTOFILL_PREF_FRAGMENT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,4 +366,16 @@ object PreferencesUtil {
|
||||
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
|
||||
context.resources.getBoolean(R.bool.autofill_auto_search_default))
|
||||
}
|
||||
|
||||
fun applicationIdBlocklist(context: Context): Set<String> {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getStringSet(context.getString(R.string.autofill_application_id_blocklist_key), null)
|
||||
?: emptySet()
|
||||
}
|
||||
|
||||
fun webDomainBlocklist(context: Context): Set<String> {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), null)
|
||||
?: emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.DialogPreference
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import java.util.ArrayList
|
||||
|
||||
open class InputListPreference @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
|
||||
defStyleRes: Int = defStyleAttr)
|
||||
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
override fun getDialogLayoutResource(): Int {
|
||||
return R.layout.pref_dialog_input_list
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
|
||||
class AutofillBlocklistAppIdPreferenceDialogFragmentCompat
|
||||
: AutofillBlocklistPreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun buildSearchInfoFromString(searchInfoString: String): SearchInfo? {
|
||||
val newSearchInfo = searchInfoString
|
||||
// remove chars not allowed in application ID
|
||||
.replace(Regex("[^a-zA-Z0-9_.]+"), "")
|
||||
return SearchInfo().apply { this.applicationId = newSearchInfo }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(key: String): AutofillBlocklistAppIdPreferenceDialogFragmentCompat {
|
||||
val fragment = AutofillBlocklistAppIdPreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.AutofillBlocklistAdapter
|
||||
import java.util.*
|
||||
import kotlin.Comparator
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
abstract class AutofillBlocklistPreferenceDialogFragmentCompat
|
||||
: InputPreferenceDialogFragmentCompat(),
|
||||
AutofillBlocklistAdapter.ItemDeletedCallback<SearchInfo> {
|
||||
|
||||
private var persistedItems = TreeSet<SearchInfo>(
|
||||
Comparator { o1, o2 -> o1.toString().compareTo(o2.toString()) })
|
||||
|
||||
private var filterAdapter: AutofillBlocklistAdapter<SearchInfo>? = null
|
||||
|
||||
abstract fun buildSearchInfoFromString(searchInfoString: String): SearchInfo?
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// To get items for saved instance state
|
||||
savedInstanceState?.getParcelableArray(ITEMS_KEY)?.let {
|
||||
it.forEach { itemSaved ->
|
||||
(itemSaved as SearchInfo?)?.let { item ->
|
||||
persistedItems.add(item)
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
// Or from preference
|
||||
preference.getPersistedStringSet(emptySet()).forEach { searchInfoString ->
|
||||
addSearchInfo(searchInfoString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
setOnInputTextEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE -> {
|
||||
if (inputText.isEmpty()) {
|
||||
onDialogClosed(true)
|
||||
dialog?.dismiss()
|
||||
true
|
||||
} else {
|
||||
addItemFromInputText()
|
||||
false
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
})
|
||||
|
||||
val addItemButton = view.findViewById<View>(R.id.add_item_button)
|
||||
addItemButton?.setOnClickListener {
|
||||
addItemFromInputText()
|
||||
}
|
||||
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.pref_dialog_list)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
activity?.let { activity ->
|
||||
filterAdapter = AutofillBlocklistAdapter(activity)
|
||||
filterAdapter?.setItemDeletedCallback(this)
|
||||
recyclerView.adapter = filterAdapter
|
||||
filterAdapter?.replaceItems(persistedItems.toList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun addSearchInfo(searchInfoString: String): Boolean {
|
||||
val itemToAdd = buildSearchInfoFromString(searchInfoString)
|
||||
return if (itemToAdd != null && !itemToAdd.containsOnlyNullValues()) {
|
||||
persistedItems.add(itemToAdd)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun addItemFromInputText() {
|
||||
if (addSearchInfo(inputText)) {
|
||||
inputText = ""
|
||||
} else {
|
||||
setInputTextError(getString(R.string.error_string_type))
|
||||
}
|
||||
filterAdapter?.replaceItems(persistedItems.toList())
|
||||
}
|
||||
|
||||
override fun onItemDeleted(item: SearchInfo) {
|
||||
persistedItems.remove(item)
|
||||
filterAdapter?.replaceItems(persistedItems.toList())
|
||||
}
|
||||
|
||||
private fun getStringItems(): Set<String> {
|
||||
val setItems = HashSet<String>()
|
||||
persistedItems.forEach {
|
||||
it.getName(resources).let { item ->
|
||||
setItems.add(item)
|
||||
}
|
||||
}
|
||||
return setItems
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putParcelableArray(ITEMS_KEY, persistedItems.toTypedArray())
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
if (positiveResult) {
|
||||
preference.persistStringSet(getStringItems())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ITEMS_KEY = "ITEMS_KEY"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import java.net.URI
|
||||
|
||||
class AutofillBlocklistWebDomainPreferenceDialogFragmentCompat
|
||||
: AutofillBlocklistPreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun buildSearchInfoFromString(searchInfoString: String): SearchInfo? {
|
||||
val newSearchInfo = searchInfoString
|
||||
// remove prefix https://
|
||||
.replace(Regex("^.*://"), "")
|
||||
// Remove suffix /login...
|
||||
.replace(Regex("/.*$"), "")
|
||||
return SearchInfo().apply { webDomain = newSearchInfo }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(key: String): AutofillBlocklistWebDomainPreferenceDialogFragmentCompat {
|
||||
val fragment = AutofillBlocklistWebDomainPreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,12 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceDialogFragmentCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
@@ -34,6 +36,8 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
||||
private var textExplanationView: TextView? = null
|
||||
private var switchElementView: CompoundButton? = null
|
||||
|
||||
private var mOnInputTextEditorActionListener: TextView.OnEditorActionListener? = null
|
||||
|
||||
var inputText: String
|
||||
get() = this.inputTextView?.text?.toString() ?: ""
|
||||
set(inputText) {
|
||||
@@ -43,6 +47,14 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
||||
}
|
||||
}
|
||||
|
||||
fun setInputTextError(error: CharSequence) {
|
||||
this.inputTextView?.error = error
|
||||
}
|
||||
|
||||
fun setOnInputTextEditorActionListener(onEditorActionListener: TextView.OnEditorActionListener) {
|
||||
this.mOnInputTextEditorActionListener = onEditorActionListener
|
||||
}
|
||||
|
||||
var explanationText: String?
|
||||
get() = textExplanationView?.text?.toString() ?: ""
|
||||
set(explanationText) {
|
||||
@@ -63,16 +75,21 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
||||
inputTextView = view.findViewById(R.id.input_text)
|
||||
inputTextView?.apply {
|
||||
imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
setOnEditorActionListener { _, actionId, _ ->
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE -> {
|
||||
onDialogClosed(true)
|
||||
dialog?.dismiss()
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
setOnEditorActionListener { v, actionId, event ->
|
||||
if (mOnInputTextEditorActionListener == null) {
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE -> {
|
||||
onDialogClosed(true)
|
||||
dialog?.dismiss()
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mOnInputTextEditorActionListener?.onEditorAction(v, actionId, event)
|
||||
?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +99,20 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
|
||||
switchElementView?.visibility = View.GONE
|
||||
}
|
||||
|
||||
protected fun hideKeyboard(): Boolean {
|
||||
context?.let {
|
||||
ContextCompat.getSystemService(it, InputMethodManager::class.java)?.let { inputManager ->
|
||||
activity?.currentFocus?.let { focus ->
|
||||
val windowToken = focus.windowToken
|
||||
if (windowToken != null) {
|
||||
return inputManager.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun setInoutText(@StringRes inputTextId: Int) {
|
||||
inputText = getString(inputTextId)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
class AutofillBlocklistAdapter<T : ObjectNameResource>(private val context: Context)
|
||||
: RecyclerView.Adapter<AutofillBlocklistAdapter.BlocklistItemViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
|
||||
val items: MutableList<T> = ArrayList()
|
||||
|
||||
private var itemDeletedCallback: ItemDeletedCallback<T>? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlocklistItemViewHolder {
|
||||
val view = inflater.inflate(R.layout.pref_dialog_list_removable_item, parent, false)
|
||||
return BlocklistItemViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BlocklistItemViewHolder, position: Int) {
|
||||
val item = this.items[position]
|
||||
holder.textItem.text = item.getName(context.resources)
|
||||
holder.deleteButton.setOnClickListener(OnItemDeleteClickListener(item))
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
fun replaceItems(items: List<T>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private inner class OnItemDeleteClickListener(private val itemClicked: T) : View.OnClickListener {
|
||||
|
||||
override fun onClick(view: View) {
|
||||
itemDeletedCallback?.onItemDeleted(itemClicked)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun setItemDeletedCallback(itemDeletedCallback: ItemDeletedCallback<T>) {
|
||||
this.itemDeletedCallback = itemDeletedCallback
|
||||
}
|
||||
|
||||
interface ItemDeletedCallback<T> {
|
||||
fun onItemDeleted(item: T)
|
||||
}
|
||||
|
||||
class BlocklistItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var textItem: TextView = itemView.findViewById(R.id.pref_dialog_list_text)
|
||||
var deleteButton: ImageView = itemView.findViewById(R.id.pref_dialog_list_delete_button)
|
||||
}
|
||||
}
|
||||
88
app/src/main/res/layout/pref_dialog_input_list.xml
Normal file
88
app/src/main/res/layout/pref_dialog_input_list.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file is part of KeePassDX.
|
||||
|
||||
KeePassDX is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KeePassDX is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/edit"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
tools:targetApi="o">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/explanation_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp"
|
||||
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switch_element"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="24dp"
|
||||
android:text="@string/enable"
|
||||
android:background="@drawable/background_button_small"
|
||||
android:textColor="?attr/textColorInverse"
|
||||
android:minHeight="48dp"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/add_item_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:orientation="horizontal">
|
||||
<EditText
|
||||
android:id="@+id/input_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/add_item_button"
|
||||
app:layout_constraintBottom_toBottomOf="@id/add_item_button"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/content_description_add_item"/>
|
||||
<ImageButton
|
||||
android:id="@+id/add_item_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:contentDescription="@string/content_description_add_item"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:src="@drawable/ic_add_white_24dp" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/pref_dialog_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/default_margin"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingRight="24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
43
app/src/main/res/layout/pref_dialog_list_removable_item.xml
Normal file
43
app/src/main/res/layout/pref_dialog_list_removable_item.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file is part of KeePassDX.
|
||||
|
||||
KeePassDX is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KeePassDX is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/pref_dialog_list_container"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/pref_dialog_list_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintTop_toTopOf="@+id/pref_dialog_list_delete_button"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/pref_dialog_list_delete_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/pref_dialog_list_delete_button"/>
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/pref_dialog_list_delete_button"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:src="@drawable/ic_content_delete_white_24dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -132,6 +132,8 @@
|
||||
<bool name="keyboard_key_sound_default" translatable="false">false</bool>
|
||||
<string name="autofill_auto_search_key" translatable="false">autofill_auto_search_key</string>
|
||||
<bool name="autofill_auto_search_default" translatable="false">true</bool>
|
||||
<string name="autofill_application_id_blocklist_key" translatable="false">autofill_application_id_blocklist_key</string>
|
||||
<string name="autofill_web_domain_blocklist_key" translatable="false">autofill_web_domain_blocklist_key</string>
|
||||
|
||||
<!-- Advanced Unlock Settings -->
|
||||
<string name="settings_advanced_unlock_key" translatable="false">settings_advanced_unlock_key</string>
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<string name="content_description_add_node">Add node</string>
|
||||
<string name="content_description_add_entry">Add entry</string>
|
||||
<string name="content_description_add_group">Add group</string>
|
||||
<string name="content_description_add_item">Add item</string>
|
||||
<string name="content_description_file_information">File information</string>
|
||||
<string name="content_description_password_checkbox">Password checkbox</string>
|
||||
<string name="content_description_keyfile_checkbox">Keyfile checkbox</string>
|
||||
@@ -131,6 +132,7 @@
|
||||
<string name="error_otp_counter">Counter must be between %1$d and %2$d.</string>
|
||||
<string name="error_otp_period">Period must be between %1$d and %2$d seconds.</string>
|
||||
<string name="error_otp_digits">Token must contain %1$d to %2$d digits.</string>
|
||||
<string name="error_string_type">This text does not match the requested item.</string>
|
||||
<string name="field_name">Field name</string>
|
||||
<string name="field_value">Field value</string>
|
||||
<string name="file_not_found_content">Could not find file. Try reopening it from your file browser.</string>
|
||||
@@ -230,6 +232,7 @@
|
||||
<string name="do_not_kill_app">Do not kill the app…</string>
|
||||
<string name="space">Space</string>
|
||||
<string name="search_label">Search</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="sort_menu">Sort</string>
|
||||
<string name="sort_ascending">Lowest first ↓</string>
|
||||
<string name="sort_groups_before">Groups before</string>
|
||||
@@ -378,7 +381,11 @@
|
||||
<string name="keyboard_key_vibrate_title">Vibratory keypresses</string>
|
||||
<string name="keyboard_key_sound_title">Audible keypresses</string>
|
||||
<string name="autofill_auto_search_title">Auto search</string>
|
||||
<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_application_id_blocklist_title">Application block list</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Block list that prevents auto filling of apps</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Web domain block list</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Block list that prevents auto filling of web domains</string>
|
||||
<string name="allow_no_password_title">Allow no master key</string>
|
||||
<string name="allow_no_password_summary">Enable the \"Open\" button if no credentials are selected</string>
|
||||
<string name="delete_entered_password_title">Delete password</string>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file is part of KeePassDX.
|
||||
|
||||
@@ -26,4 +26,15 @@
|
||||
android:summary="@string/autofill_auto_search_summary"
|
||||
android:defaultValue="@bool/autofill_auto_search_default"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/filter">
|
||||
<com.kunzisoft.keepass.settings.preference.InputListPreference
|
||||
android:key="@string/autofill_application_id_blocklist_key"
|
||||
android:title="@string/autofill_application_id_blocklist_title"
|
||||
android:summary="@string/autofill_application_id_blocklist_summary"/>
|
||||
<com.kunzisoft.keepass.settings.preference.InputListPreference
|
||||
android:key="@string/autofill_web_domain_blocklist_key"
|
||||
android:title="@string/autofill_web_domain_blocklist_title"
|
||||
android:summary="@string/autofill_web_domain_blocklist_summary"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user