diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 766cda86d..50dd998f8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -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 diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index 4a006c620..5cdbf0dd4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -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.isNull()) + return null + return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchInfo.toString(), SearchParameters().apply { searchInTitles = false searchInUserNames = false searchInPasswords = false diff --git a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt index 8ad6e99f4..283d9f78c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt @@ -1,13 +1,17 @@ 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 var webDomain: String? = null + var genericInfo: String? = null + constructor() private constructor(parcel: Parcel) { @@ -15,6 +19,8 @@ class SearchInfo : Parcelable { applicationId = if (readAppId.isNullOrEmpty()) null else readAppId val readDomain = parcel.readString() webDomain = if (readDomain.isNullOrEmpty()) null else readDomain + val readGeneric = parcel.readString() + genericInfo = if (readGeneric.isNullOrEmpty()) null else readGeneric } override fun describeContents(): Int { @@ -24,6 +30,39 @@ class SearchInfo : Parcelable { override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(applicationId ?: "") parcel.writeString(webDomain ?: "") + parcel.writeString(genericInfo ?: "") + } + + override fun getName(resources: Resources): String { + return applicationId ?: webDomain ?: genericInfo ?: "" + } + + fun isNull(): Boolean { + return applicationId == null && webDomain == null && genericInfo == 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 + if (genericInfo != other.genericInfo) return false + + return true + } + + override fun hashCode(): Int { + var result = applicationId?.hashCode() ?: 0 + result = 31 * result + (webDomain?.hashCode() ?: 0) + result = 31 * result + (genericInfo?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return applicationId ?: webDomain ?: genericInfo ?: "" } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt index 96558b1b4..3e560442c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt @@ -20,9 +20,11 @@ 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.AutofillBlacklistPreferenceDialogFragmentCompat class AutofillSettingsFragment : PreferenceFragmentCompat() { @@ -30,4 +32,31 @@ 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_blocklist_key) -> { + dialogFragment = AutofillBlacklistPreferenceDialogFragmentCompat.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" + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt new file mode 100644 index 000000000..21965e957 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt @@ -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 . + * + */ +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 + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt new file mode 100644 index 000000000..454cf7359 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt @@ -0,0 +1,94 @@ +/* + * 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 . + * + */ +package com.kunzisoft.keepass.settings.preferencedialogfragment + +import android.os.Bundle +import android.view.View +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.AutofillBlacklistAdapter + +class AutofillBlacklistPreferenceDialogFragmentCompat + : InputPreferenceDialogFragmentCompat(), + AutofillBlacklistAdapter.ItemDeletedCallback { + + private var persistedItems = HashSet() + + private var filterAdapter: AutofillBlacklistAdapter? = null + + override fun onBindDialogView(view: View) { + super.onBindDialogView(view) + + // TODO persistedItems.add + + val addItemButton = view.findViewById(R.id.add_item_button) + addItemButton?.setOnClickListener { + persistedItems.add(SearchInfo().apply { + genericInfo = inputText + }) + filterAdapter?.replaceItems(persistedItems.toList()) + } + + val recyclerView = view.findViewById(R.id.pref_dialog_list) + recyclerView.layoutManager = LinearLayoutManager(context) + + activity?.let { activity -> + filterAdapter = AutofillBlacklistAdapter(activity) + filterAdapter?.setItemDeletedCallback(this) + recyclerView.adapter = filterAdapter + filterAdapter?.replaceItems(persistedItems.toList()) + } + } + + override fun onItemDeleted(item: SearchInfo) { + persistedItems.remove(item) + filterAdapter?.replaceItems(persistedItems.toList()) + } + + private fun getStringItems(): Set { + val setItems = HashSet() + persistedItems.forEach { + it.getName(resources).let { item -> + setItems.add(item) + } + } + return setItems + } + + override fun onDialogClosed(positiveResult: Boolean) { + if (positiveResult) { + preference.persistStringSet(getStringItems()) + } + } + + companion object { + + fun newInstance(key: String): AutofillBlacklistPreferenceDialogFragmentCompat { + val fragment = AutofillBlacklistPreferenceDialogFragmentCompat() + val bundle = Bundle(1) + bundle.putString(ARG_KEY, key) + fragment.arguments = bundle + + return fragment + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt new file mode 100644 index 000000000..7886ba443 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt @@ -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 . + * + */ +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 AutofillBlacklistAdapter(private val context: Context) + : RecyclerView.Adapter() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + + val items: MutableList = ArrayList() + + private var itemDeletedCallback: ItemDeletedCallback? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlacklistItemViewHolder { + val view = inflater.inflate(R.layout.pref_dialog_list_removable_item, parent, false) + return BlacklistItemViewHolder(view) + } + + override fun onBindViewHolder(holder: BlacklistItemViewHolder, 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) { + 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) { + this.itemDeletedCallback = itemDeletedCallback + } + + interface ItemDeletedCallback { + fun onItemDeleted(item: T) + } + + class BlacklistItemViewHolder(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) + } +} diff --git a/app/src/main/res/layout/pref_dialog_input_list.xml b/app/src/main/res/layout/pref_dialog_input_list.xml new file mode 100644 index 000000000..1b3ca8eee --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_input_list.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pref_dialog_list_removable_item.xml b/app/src/main/res/layout/pref_dialog_list_removable_item.xml new file mode 100644 index 000000000..24f079b79 --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_list_removable_item.xml @@ -0,0 +1,43 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 9c6b93295..6def8bff3 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -132,6 +132,7 @@ false autofill_auto_search_key true + autofill_blocklist_key settings_advanced_unlock_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d69fc5be0..30b9b083d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ Add node Add entry Add group + Add item File information Password checkbox Keyfile checkbox @@ -230,6 +231,7 @@ Do not kill the app… Space Search + Filter Sort Lowest first ↓ Groups before @@ -379,6 +381,7 @@ Audible keypresses Auto search Automatically suggest search results from the web domain or application Id + Blacklist Allow no master key Enable the \"Open\" button if no credentials are selected Delete password diff --git a/app/src/main/res/xml/preferences_autofill.xml b/app/src/main/res/xml/preferences_autofill.xml index ca7d0e860..6dc635efc 100644 --- a/app/src/main/res/xml/preferences_autofill.xml +++ b/app/src/main/res/xml/preferences_autofill.xml @@ -1,6 +1,6 @@