diff --git a/CHANGELOG b/CHANGELOG index 1bb76791b..8dfc53a55 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +KeePassDX(2.7) + * Add blocklists for autofill + * Add autofill compatibility mode (usefull for Browser not compatible) + * Upgrade autofill recognition algorithm + * Setting to search through web subdomains + * Refactoring selection mode + KeePassDX(2.6) * Share a web domain to automatically search for an entry * Default group icon for a new entry diff --git a/app/build.gradle b/app/build.gradle index 5ae090213..6286c504b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId "com.kunzisoft.keepass" minSdkVersion 14 targetSdkVersion 29 - versionCode = 34 - versionName = "2.6" + versionCode = 35 + versionName = "2.7" multiDexEnabled true testApplicationId = "com.kunzisoft.keepass.tests" @@ -69,7 +69,7 @@ android { buildConfigField "String", "BUILD_VERSION", "\"free\"" buildConfigField "boolean", "FULL_VERSION", "false" buildConfigField "boolean", "CLOSED_STORE", "true" - buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}" + buildConfigField "String[]", "STYLES_DISABLED", "{}" buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}" manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ] } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt index ccb249aa9..37a6891ae 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/AutofillLauncherActivity.kt @@ -26,25 +26,43 @@ import android.content.Intent import android.content.IntentSender import android.os.Build import android.os.Bundle +import android.widget.Toast import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity +import com.kunzisoft.keepass.R import com.kunzisoft.keepass.autofill.AutofillHelper +import com.kunzisoft.keepass.autofill.KeeAutofillService 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 AutofillLauncherActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { + + // Build search param + val searchInfo = SearchInfo().apply { + applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID) + webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN) + } + // Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE) val assistStructure = AutofillHelper.retrieveAssistStructure(intent) - if (assistStructure != null) { - // Build search param - val searchInfo = SearchInfo().apply { - applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID) - webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN) - } + + if (assistStructure == null) { + setResult(Activity.RESULT_CANCELED) + finish() + } else if (!KeeAutofillService.searchAllowedFor(searchInfo.applicationId, + PreferencesUtil.applicationIdBlocklist(this)) + || !KeeAutofillService.searchAllowedFor(searchInfo.webDomain, + PreferencesUtil.webDomainBlocklist(this))) { + // If item not allowed, show a toast + Toast.makeText(this.applicationContext, R.string.autofill_block_restart, Toast.LENGTH_LONG).show() + setResult(Activity.RESULT_CANCELED) + finish() + } else { // If database is open SearchHelper.checkAutoSearchInfo(this, Database.getInstance(), @@ -57,17 +75,17 @@ class AutofillLauncherActivity : AppCompatActivity() { { // Show the database UI to select the entry GroupActivity.launchForAutofillResult(this, - assistStructure) + assistStructure, + false, + searchInfo) }, { // If database not open FileDatabaseSelectActivity.launchForAutofillResult(this, - assistStructure, searchInfo) + assistStructure, + searchInfo) } ) - } else { - setResult(Activity.RESULT_CANCELED) - finish() } super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index 8bd935f42..aac11904e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -43,6 +43,7 @@ import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Group 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.education.EntryEditActivityEducation import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService @@ -166,11 +167,16 @@ class EntryEditActivity : LockingActivity(), mNewEntry = mDatabase?.createEntry() } mParent = mDatabase?.getGroupById(it) - // Add the default icon from parent - mParent?.icon?.let { parentIcon -> + // Add the default icon from parent if not a folder + val parentIcon = mParent?.icon + if (parentIcon != null + && parentIcon.iconId != IconImage.UNKNOWN_ID + && parentIcon.iconId != IconImageStandard.FOLDER) { temporarilySaveAndShowSelectedIcon(parentIcon) - } ?: mDatabase?.drawFactory?.let { iconFactory -> + } else { + mDatabase?.drawFactory?.let { iconFactory -> entryEditContentsView?.setDefaultIcon(iconFactory) + } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntrySelectionLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntrySelectionLauncherActivity.kt index 6ea936af1..bbf422613 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntrySelectionLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntrySelectionLauncherActivity.kt @@ -23,7 +23,6 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper @@ -49,22 +48,21 @@ class EntrySelectionLauncherActivity : AppCompatActivity() { if ("text/plain" == intent.type) { // Retrieve web domain intent.getStringExtra(Intent.EXTRA_TEXT)?.let { - sharedWebDomain = Uri.parse(it).authority + sharedWebDomain = Uri.parse(it).host } - } } else -> {} } + // Setting to integrate Magikeyboard + val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this) + // Build search param val searchInfo = SearchInfo().apply { webDomain = sharedWebDomain } - // Setting to integrate Magikeyboard - val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this) - // If database is open SearchHelper.checkAutoSearchInfo(this, Database.getInstance(), @@ -75,29 +73,41 @@ class EntrySelectionLauncherActivity : AppCompatActivity() { if (items.size == 1) { // Automatically populate keyboard val entryPopulate = items[0] - populateKeyboardAndMoveAppToBackground(this, entryPopulate, intent) + populateKeyboardAndMoveAppToBackground(this, + entryPopulate, + intent) } else { // Select the one we want - GroupActivity.launchForEntrySelectionResult(this, searchInfo) + GroupActivity.launchForEntrySelectionResult(this, + true, + searchInfo) } } else { - GroupActivity.launch(this, searchInfo) + GroupActivity.launch(this, + true, + searchInfo) } }, { // Show the database UI to select the entry if (searchShareForMagikeyboard) { - GroupActivity.launchForEntrySelectionResult(this) + GroupActivity.launchForEntrySelectionResult(this, + false, + searchInfo) } else { - GroupActivity.launch(this) + GroupActivity.launch(this, + false, + searchInfo) } }, { // If database not open if (searchShareForMagikeyboard) { - FileDatabaseSelectActivity.launchForEntrySelectionResult(this, searchInfo) + FileDatabaseSelectActivity.launchForEntrySelectionResult(this, + searchInfo) } else { - FileDatabaseSelectActivity.launch(this, searchInfo) + FileDatabaseSelectActivity.launch(this, + searchInfo) } } ) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt index 5abe06cab..7949e372d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -44,7 +44,7 @@ import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO import com.kunzisoft.keepass.activities.helpers.OpenFileHelper -import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.autofill.AutofillHelper @@ -59,13 +59,14 @@ import com.kunzisoft.keepass.view.asError import kotlinx.android.synthetic.main.activity_file_selection.* import java.io.FileNotFoundException -class FileDatabaseSelectActivity : StylishActivity(), +class FileDatabaseSelectActivity : SpecialModeActivity(), AssignMasterKeyDialogFragment.AssignPasswordDialogListener { // Views private var coordinatorLayout: CoordinatorLayout? = null private var fileManagerExplanationButton: View? = null - private var createButtonView: View? = null + private var databaseButtonsContainerView: View? = null + private var createDatabaseButtonView: View? = null private var openDatabaseButtonView: View? = null // Adapter to manage database history list @@ -96,19 +97,13 @@ class FileDatabaseSelectActivity : StylishActivity(), UriUtil.gotoUrl(this, R.string.file_manager_explanation_url) } - // Create button - createButtonView = findViewById(R.id.create_database_button) - if (allowCreateDocumentByStorageAccessFramework(packageManager)) { - // There is an activity which can handle this intent. - createButtonView?.visibility = View.VISIBLE - } - else{ - // No Activity found that can handle this intent. - createButtonView?.visibility = View.GONE - } + databaseButtonsContainerView = findViewById(R.id.database_buttons_container) - createButtonView?.setOnClickListener { createNewFile() } + // Create database button + createDatabaseButtonView = findViewById(R.id.create_database_button) + createDatabaseButtonView?.setOnClickListener { createNewFile() } + // Open database button mOpenFileHelper = OpenFileHelper(this) openDatabaseButtonView = findViewById(R.id.open_keyfile_button) openDatabaseButtonView?.apply { @@ -157,7 +152,7 @@ class FileDatabaseSelectActivity : StylishActivity(), UriUtil.parse(databasePath)?.let { databaseFileUri -> launchPasswordActivityWithPath(databaseFileUri) } ?: run { - Log.i(TAG, "Unable to launch Password Activity") + Log.i(TAG, "No default database to prepare") } } @@ -241,11 +236,13 @@ class FileDatabaseSelectActivity : StylishActivity(), EntrySelectionHelper.doEntrySelectionAction(intent, { GroupActivity.launch(this@FileDatabaseSelectActivity, + false, searchInfo, readOnly) }, { GroupActivity.launchForEntrySelectionResult(this@FileDatabaseSelectActivity, + false, searchInfo, readOnly) // Do not keep history @@ -255,6 +252,7 @@ class FileDatabaseSelectActivity : StylishActivity(), if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, assistStructure, + false, searchInfo, readOnly) } @@ -269,37 +267,52 @@ class FileDatabaseSelectActivity : StylishActivity(), } override fun onResume() { + super.onResume() + + // Show open and create button or special mode + if (mSelectionMode) { + // Disable buttons if in selection mode or request for autofill + databaseButtonsContainerView?.visibility = View.GONE + } else { + if (allowCreateDocumentByStorageAccessFramework(packageManager)) { + // There is an activity which can handle this intent. + createDatabaseButtonView?.visibility = View.VISIBLE + } else{ + // No Activity found that can handle this intent. + createDatabaseButtonView?.visibility = View.GONE + } + databaseButtonsContainerView?.visibility = View.VISIBLE + } + val database = Database.getInstance() if (database.loaded) { launchGroupActivity(database.isReadOnly) - } - - super.onResume() - - // Construct adapter with listeners - if (PreferencesUtil.showRecentFiles(this)) { - mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList -> - databaseFileHistoryList?.let { historyList -> - val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity) - mAdapterDatabaseHistory?.addDatabaseFileHistoryList( - // Show only uri accessible - historyList.filter { - if (hideBrokenLocations) { - FileDatabaseInfo(this@FileDatabaseSelectActivity, - it.databaseUri).exists - } else - true - }) - mAdapterDatabaseHistory?.notifyDataSetChanged() - } - } } else { - mAdapterDatabaseHistory?.clearDatabaseFileHistoryList() - mAdapterDatabaseHistory?.notifyDataSetChanged() - } + // Construct adapter with listeners + if (PreferencesUtil.showRecentFiles(this)) { + mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList -> + databaseFileHistoryList?.let { historyList -> + val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity) + mAdapterDatabaseHistory?.addDatabaseFileHistoryList( + // Show only uri accessible + historyList.filter { + if (hideBrokenLocations) { + FileDatabaseInfo(this@FileDatabaseSelectActivity, + it.databaseUri).exists + } else + true + }) + mAdapterDatabaseHistory?.notifyDataSetChanged() + } + } + } else { + mAdapterDatabaseHistory?.clearDatabaseFileHistoryList() + mAdapterDatabaseHistory?.notifyDataSetChanged() + } - // Register progress task - mProgressDialogThread?.registerProgressTask() + // Register progress task + mProgressDialogThread?.registerProgressTask() + } } override fun onPause() { @@ -378,7 +391,10 @@ class FileDatabaseSelectActivity : StylishActivity(), override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) - MenuUtil.defaultMenuInflater(menuInflater, menu) + + if (!mSelectionMode) { + MenuUtil.defaultMenuInflater(menuInflater, menu) + } Handler().post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) } @@ -387,11 +403,11 @@ class FileDatabaseSelectActivity : StylishActivity(), private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) { // If no recent files - val createDatabaseEducationPerformed = createButtonView != null && createButtonView!!.visibility == View.VISIBLE + val createDatabaseEducationPerformed = createDatabaseButtonView != null && createDatabaseButtonView!!.visibility == View.VISIBLE && mAdapterDatabaseHistory != null && mAdapterDatabaseHistory!!.itemCount > 0 && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation( - createButtonView!!, + createDatabaseButtonView!!, { createNewFile() }, 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..fe54e8e47 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -63,6 +63,7 @@ import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.model.getSearchString import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK @@ -74,10 +75,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.utils.MenuUtil -import com.kunzisoft.keepass.view.AddNodeButtonView -import com.kunzisoft.keepass.view.ToolbarAction -import com.kunzisoft.keepass.view.asError -import com.kunzisoft.keepass.view.showActionError +import com.kunzisoft.keepass.view.* class GroupActivity : LockingActivity(), GroupEditDialogFragment.EditGroupListener, @@ -96,7 +94,6 @@ class GroupActivity : LockingActivity(), private var toolbarAction: ToolbarAction? = null private var iconView: ImageView? = null private var numberChildrenView: TextView? = null - private var modeTitleView: TextView? = null private var addNodeButtonView: AddNodeButtonView? = null private var groupNameView: TextView? = null @@ -106,6 +103,9 @@ class GroupActivity : LockingActivity(), private var mCurrentGroupIsASearch: Boolean = false private var mRequestStartupSearch = true + // To manage history in selection mode + private var mSelectionModeCountBackStack = 0 + // Nodes private var mRootGroup: Group? = null private var mCurrentGroup: Group? = null @@ -135,7 +135,6 @@ class GroupActivity : LockingActivity(), searchTitleView = findViewById(R.id.search_title) groupNameView = findViewById(R.id.group_name) toolbarAction = findViewById(R.id.toolbar_action) - modeTitleView = findViewById(R.id.mode_title_view) lockView = findViewById(R.id.lock_button) lockView?.setOnClickListener { @@ -285,7 +284,7 @@ class GroupActivity : LockingActivity(), intent?.let { intentNotNull -> // To transform KEY_SEARCH_INFO in ACTION_SEARCH - manageSearchInfoIntent(intent) + manageSearchInfoIntent(intentNotNull) Log.d(TAG, "setNewIntent: $intentNotNull") setIntent(intentNotNull) mCurrentGroupIsASearch = if (Intent.ACTION_SEARCH == intentNotNull.action) { @@ -305,11 +304,10 @@ class GroupActivity : LockingActivity(), private fun manageSearchInfoIntent(intent: Intent): Boolean { // To relaunch the activity as ACTION_SEARCH val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO) - if (searchInfo != null) { + val autoSearch = intent.getBooleanExtra(AUTO_SEARCH_KEY, false) + if (searchInfo != null && autoSearch) { 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.getSearchString(this)) return true } return false @@ -354,6 +352,9 @@ class GroupActivity : LockingActivity(), fragmentTransaction.addToBackStack(fragmentTag) fragmentTransaction.commit() + if (mSelectionMode) + mSelectionModeCountBackStack++ + // Update last access time. group?.touch(modified = false, touchParents = false) @@ -460,13 +461,6 @@ class GroupActivity : LockingActivity(), // Assign number of children refreshNumberOfChildren() - // Show selection mode message if needed - if (mSelectionMode) { - modeTitleView?.visibility = View.VISIBLE - } else { - modeTitleView?.visibility = View.GONE - } - // Show button if allowed addNodeButtonView?.apply { @@ -484,6 +478,20 @@ class GroupActivity : LockingActivity(), } } + override fun onCancelSpecialMode() { + // To remove the navigation history and + EntrySelectionHelper.removeEntrySelectionModeFromIntent(intent) + val fragmentManager = supportFragmentManager + if (mSelectionModeCountBackStack > 0) { + for (selectionMode in 0 .. mSelectionModeCountBackStack) { + fragmentManager.popBackStack() + } + } + // Reinit the counter for navigation history + mSelectionModeCountBackStack = 0 + backToTheAppCaller() + } + private fun refreshNumberOfChildren() { numberChildrenView?.apply { if (PreferencesUtil.showNumberEntries(context)) { @@ -702,8 +710,7 @@ class GroupActivity : LockingActivity(), menu.findItem(R.id.menu_save_database)?.isVisible = false } if (!mSelectionMode) { - inflater.inflate(R.menu.default_menu, menu) - MenuUtil.contributionMenuInflater(inflater, menu) + MenuUtil.defaultMenuInflater(inflater, menu) } // Menu for recycle bin @@ -912,19 +919,16 @@ class GroupActivity : LockingActivity(), } override fun startActivity(intent: Intent) { - // Get the intent, verify the action and get the query if (Intent.ACTION_SEARCH == intent.action) { - // manually launch the real search activity - val searchIntent = Intent(applicationContext, GroupActivity::class.java).apply { - // Add bundle of current intent - putExtras(this@GroupActivity.intent) + // manually launch the same search activity + val searchIntent = getIntent().apply { // add query to the Intent Extras action = Intent.ACTION_SEARCH putExtra(SearchManager.QUERY, intent.getStringExtra(SearchManager.QUERY)) } - - super.startActivity(searchIntent) + setIntent(searchIntent) + onNewIntent(searchIntent) } else { super.startActivity(intent) } @@ -971,26 +975,38 @@ class GroupActivity : LockingActivity(), assignGroupViewElements() } + private fun backToTheAppCaller() { + if (mAutofillSelection) { + // To get the app caller, only for autofill + super.onBackPressed() + } else { + // To move the app in background + moveTaskToBack(true) + } + } + override fun onBackPressed() { if (mListNodesFragment?.nodeActionSelectionMode == true) { finishNodeAction() } else { // Normal way when we are not in root - if (mRootGroup != null && mRootGroup != mCurrentGroup) + if (mRootGroup != null && mRootGroup != mCurrentGroup) { super.onBackPressed() - // Else lock if needed + rebuildListNodes() + } + // Else in root, lock if needed else { + intent.removeExtra(AUTO_SEARCH_KEY) + intent.removeExtra(KEY_SEARCH_INFO) if (PreferencesUtil.isLockDatabaseWhenBackButtonOnRootClicked(this)) { lockAndExit() super.onBackPressed() } else { // To restore standard mode EntrySelectionHelper.removeEntrySelectionModeFromIntent(intent) - moveTaskToBack(true) + backToTheAppCaller() } } - - rebuildListNodes() } } @@ -1003,6 +1019,7 @@ class GroupActivity : LockingActivity(), private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG" private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG" private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY" + private const val AUTO_SEARCH_KEY = "AUTO_SEARCH_KEY" private fun buildIntent(context: Context, group: Group?, @@ -1040,12 +1057,14 @@ class GroupActivity : LockingActivity(), * ------------------------- */ fun launch(context: Context, + autoSearch: Boolean = false, searchInfo: SearchInfo? = null, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) { checkTimeAndBuildIntent(context, null, readOnly) { intent -> searchInfo?.let { intent.putExtra(KEY_SEARCH_INFO, it) } + intent.putExtra(AUTO_SEARCH_KEY, autoSearch) context.startActivity(intent) } } @@ -1056,9 +1075,11 @@ class GroupActivity : LockingActivity(), * ------------------------- */ fun launchForEntrySelectionResult(context: Context, + autoSearch: Boolean = false, searchInfo: SearchInfo? = null, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) { checkTimeAndBuildIntent(context, null, readOnly) { intent -> + intent.putExtra(AUTO_SEARCH_KEY, autoSearch) EntrySelectionHelper.startActivityForEntrySelectionResult(context, intent, searchInfo) } } @@ -1071,9 +1092,11 @@ class GroupActivity : LockingActivity(), @RequiresApi(api = Build.VERSION_CODES.O) fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure, + autoSearch: Boolean = false, searchInfo: SearchInfo? = null, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) { checkTimeAndBuildIntent(activity, null, readOnly) { intent -> + intent.putExtra(AUTO_SEARCH_KEY, autoSearch) AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index 184561f98..a995994c1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -46,7 +46,7 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_ import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.lock.LockingActivity -import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.autofill.AutofillHelper @@ -73,11 +73,10 @@ import com.kunzisoft.keepass.view.asError import kotlinx.android.synthetic.main.activity_password.* import java.io.FileNotFoundException -open class PasswordActivity : StylishActivity() { +open class PasswordActivity : SpecialModeActivity() { // Views private var toolbar: Toolbar? = null - private var containerView: View? = null private var filenameView: TextView? = null private var passwordView: EditText? = null private var keyFileSelectionView: KeyFileSelectionView? = null @@ -124,7 +123,6 @@ open class PasswordActivity : StylishActivity() { supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) - containerView = findViewById(R.id.container) confirmButtonView = findViewById(R.id.activity_password_open_button) filenameView = findViewById(R.id.filename) passwordView = findViewById(R.id.password) @@ -265,9 +263,10 @@ open class PasswordActivity : StylishActivity() { EntrySelectionHelper.doEntrySelectionAction(intent, { GroupActivity.launch(this@PasswordActivity, + true, searchInfo, readOnly) - // Remove the search info from intent + // Finish activity if no search info if (searchInfo != null) { finish() } @@ -284,13 +283,16 @@ open class PasswordActivity : StylishActivity() { intent) } else { // Select the one we want - GroupActivity.launchForEntrySelectionResult(this, searchInfo) + GroupActivity.launchForEntrySelectionResult(this, + true, + searchInfo) } }, { - // Here no search info found + // Here no search info found, disable auto search GroupActivity.launchForEntrySelectionResult(this@PasswordActivity, - null, + false, + searchInfo, readOnly) }, { @@ -311,10 +313,11 @@ open class PasswordActivity : StylishActivity() { finish() }, { - // Here no search info found + // Here no search info found, disable auto search GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, - null, + false, + searchInfo, readOnly) }, { @@ -337,6 +340,7 @@ open class PasswordActivity : StylishActivity() { } override fun onResume() { + super.onResume() if (Database.getInstance().loaded) { launchGroupActivity() @@ -349,9 +353,6 @@ open class PasswordActivity : StylishActivity() { clearCredentialsViews() } - // For check shutdown - super.onResume() - mProgressDialogThread?.registerProgressTask() // Don't allow auto open prompt if lock become when UI visible @@ -624,14 +625,15 @@ open class PasswordActivity : StylishActivity() { val inflater = menuInflater // Read menu inflater.inflate(R.menu.open_file, menu) - - if (mForceReadOnly) { + if (mSelectionMode || mForceReadOnly) { menu.removeItem(R.id.menu_open_file_read_mode_key) } else { changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key)) } - MenuUtil.defaultMenuInflater(inflater, menu) + if (!mSelectionMode) { + MenuUtil.defaultMenuInflater(inflater, menu) + } if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // biometric menu @@ -779,10 +781,13 @@ open class PasswordActivity : StylishActivity() { if (!keyFileResult) { // this block if not a key file response when (resultCode) { - LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> { + LockingActivity.RESULT_EXIT_LOCK -> { clearCredentialsViews() Database.getInstance().closeAndClear(applicationContext.filesDir) } + Activity.RESULT_CANCELED -> { + clearCredentialsViews() + } } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt index 48dae1d91..1665f9f85 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt @@ -31,10 +31,10 @@ import android.widget.BaseAdapter import android.widget.GridView import android.widget.ImageView import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.ImageViewCompat import androidx.fragment.app.DialogFragment import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.icons.IconPack import com.kunzisoft.keepass.icons.IconPackChooser @@ -132,7 +132,7 @@ class IconPickerDialogFragment : DialogFragment() { return bundle.getParcelable(KEY_ICON_STANDARD) } - fun launch(activity: StylishActivity) { + fun launch(activity: AppCompatActivity) { // Create an instance of the dialog fragment and show it val dialog = IconPickerDialogFragment() dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment") diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt index 2cbcf53c5..9d047a3ea 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt @@ -24,16 +24,15 @@ import android.os.Bundle import android.util.Log import android.view.View import android.view.ViewGroup -import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper -import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.utils.* -abstract class LockingActivity : StylishActivity() { +abstract class LockingActivity : SpecialModeActivity() { protected var mTimeoutEnable: Boolean = true @@ -49,7 +48,6 @@ abstract class LockingActivity : StylishActivity() { mReadOnlyToSave = value } private var mReadOnlyToSave: Boolean = false - protected var mSelectionMode: Boolean = false protected var mAutoSaveEnable: Boolean = true var mProgressDialogThread: ProgressDialogThread? = null @@ -100,7 +98,6 @@ abstract class LockingActivity : StylishActivity() { // To refresh when back to normal workflow from selection workflow mReadOnlyToSave = ReadOnlyHelper.retrieveReadOnlyFromIntent(intent) - mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent) mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this) invalidateOptionsMenu() diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt new file mode 100644 index 000000000..3d75c0e08 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/activities/selection/SpecialModeActivity.kt @@ -0,0 +1,90 @@ +package com.kunzisoft.keepass.activities.selection + +import android.os.Build +import android.view.View +import android.widget.Toast +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper +import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.autofill.AutofillHelper +import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.settings.PreferencesUtil +import com.kunzisoft.keepass.view.SpecialModeView + +/** + * Activity to manage special mode (ie: selection mode) + */ +abstract class SpecialModeActivity : StylishActivity() { + + protected var mSelectionMode: Boolean = false + + protected var mAutofillSelection: Boolean = false + + private var specialModeView: SpecialModeView? = null + + open fun onCancelSpecialMode() { + onBackPressed() + } + + override fun onResume() { + super.onResume() + + mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mAutofillSelection = AutofillHelper.retrieveAssistStructure(intent) != null + } + + val searchInfo: SearchInfo? = intent.getParcelableExtra(EntrySelectionHelper.KEY_SEARCH_INFO) + + // To show the selection mode + specialModeView = findViewById(R.id.special_mode_view) + specialModeView?.apply { + // Populate title + val typeModeId = if (mAutofillSelection) + R.string.autofill + else + R.string.magic_keyboard_title + title = "${resources.getString(R.string.selection_mode)} (${getString(typeModeId)})" + // Populate subtitle + subtitle = searchInfo?.getName(resources) + + // Show the toolbar or not + visible = mSelectionMode + + // Add back listener + onCancelButtonClickListener = View.OnClickListener { + onCancelSpecialMode() + } + + // Create menu + menu.clear() + if (mAutofillSelection) { + menuInflater.inflate(R.menu.autofill, menu) + setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + R.id.menu_block_autofill -> { + blockAutofill(searchInfo) + } + } + true + } + } + } + } + + fun blockAutofill(searchInfo: SearchInfo?) { + val webDomain = searchInfo?.webDomain + val applicationId = searchInfo?.applicationId + if (webDomain != null) { + PreferencesUtil.addWebDomainToBlocklist(this, + webDomain) + } else if (applicationId != null) { + PreferencesUtil.addApplicationIdToBlocklist(this, + applicationId) + } + onCancelSpecialMode() + Toast.makeText(this.applicationContext, + R.string.autofill_block_restart, + Toast.LENGTH_LONG).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt index ec5a8f57c..579b1625c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/AutofillHelper.kt @@ -119,29 +119,33 @@ object AutofillHelper { * Build the Autofill response for many entry */ fun buildResponse(activity: Activity, entriesInfo: List) { - var setResultOk = false - activity.intent?.extras?.let { extras -> - if (extras.containsKey(ASSIST_STRUCTURE)) { - activity.intent?.getParcelableExtra(ASSIST_STRUCTURE)?.let { structure -> - StructureParser(structure).parse()?.let { result -> - // New Response - val responseBuilder = FillResponse.Builder() - entriesInfo.forEach { - responseBuilder.addDataset(buildDataset(activity, it, result)) + if (entriesInfo.isEmpty()) { + activity.setResult(Activity.RESULT_CANCELED) + } else { + var setResultOk = false + activity.intent?.extras?.let { extras -> + if (extras.containsKey(ASSIST_STRUCTURE)) { + activity.intent?.getParcelableExtra(ASSIST_STRUCTURE)?.let { structure -> + StructureParser(structure).parse()?.let { result -> + // New Response + val responseBuilder = FillResponse.Builder() + entriesInfo.forEach { + responseBuilder.addDataset(buildDataset(activity, it, result)) + } + val mReplyIntent = Intent() + Log.d(activity.javaClass.name, "Successed Autofill auth.") + mReplyIntent.putExtra( + AutofillManager.EXTRA_AUTHENTICATION_RESULT, + responseBuilder.build()) + setResultOk = true + activity.setResult(Activity.RESULT_OK, mReplyIntent) } - val mReplyIntent = Intent() - Log.d(activity.javaClass.name, "Successed Autofill auth.") - mReplyIntent.putExtra( - AutofillManager.EXTRA_AUTHENTICATION_RESULT, - responseBuilder.build()) - setResultOk = true - activity.setResult(Activity.RESULT_OK, mReplyIntent) } } - } - if (!setResultOk) { - Log.w(activity.javaClass.name, "Failed Autofill auth.") - activity.setResult(Activity.RESULT_CANCELED) + if (!setResultOk) { + Log.w(activity.javaClass.name, "Failed Autofill auth.") + activity.setResult(Activity.RESULT_CANCELED) + } } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt index d42e65023..eaef31622 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt @@ -30,10 +30,21 @@ 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() { + var applicationIdBlocklist: Set? = null + var webDomainBlocklist: Set? = null + + override fun onCreate() { + super.onCreate() + + applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this) + webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this) + } + override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback) { @@ -45,32 +56,36 @@ 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 + if (searchAllowedFor(parseResult.applicationId, applicationIdBlocklist) + && searchAllowedFor(parseResult.domain, webDomainBlocklist)) { + 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)) + 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) - } - ) + ) + } } } @@ -116,5 +131,18 @@ class KeeAutofillService : AutofillService() { companion object { private val TAG = KeeAutofillService::class.java.name + + fun searchAllowedFor(element: String?, blockList: Set?): Boolean { + element?.let { elementNotNull -> + if (blockList?.any { appIdBlocked -> + elementNotNull.contains(appIdBlocked) + } == true + ) { + Log.d(TAG, "Autofill not allowed for $elementNotNull") + return false + } + } + return true + } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt index 987b386ff..fc663e4ca 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/StructureParser.kt @@ -163,44 +163,78 @@ internal class StructureParser(private val structure: AssistStructure) { return false } + private fun inputIsVariationType(inputType: Int, vararg type: Int): Boolean { + type.forEach { + if (inputType and InputType.TYPE_MASK_VARIATION == it) + return true + } + return false + } + + private fun showHexInputType(inputType: Int): String { + return "0x${"%08x".format(inputType)}" + } + private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean { val autofillId = node.autofillId val inputType = node.inputType - if (inputType and InputType.TYPE_CLASS_TEXT != 0) { - when { - inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != 0 -> { - result?.usernameId = autofillId - Log.d(TAG, "Autofill username android type: $inputType") + when (inputType and InputType.TYPE_MASK_CLASS) { + InputType.TYPE_CLASS_TEXT -> { + when { + inputIsVariationType(inputType, + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, + InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS) -> { + result?.usernameId = autofillId + Log.d(TAG, "Autofill username android text type: ${showHexInputType(inputType)}") + } + inputIsVariationType(inputType, + InputType.TYPE_TEXT_VARIATION_NORMAL, + InputType.TYPE_TEXT_VARIATION_PERSON_NAME, + InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) -> { + usernameCandidate = autofillId + Log.d(TAG, "Autofill username candidate android text type: ${showHexInputType(inputType)}") + } + inputIsVariationType(inputType, + InputType.TYPE_TEXT_VARIATION_PASSWORD, + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, + InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) -> { + result?.passwordId = autofillId + Log.d(TAG, "Autofill password android text type: ${showHexInputType(inputType)}") + usernameNeeded = false + return true + } + inputIsVariationType(inputType, + InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT, + InputType.TYPE_TEXT_VARIATION_FILTER, + InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE, + InputType.TYPE_TEXT_VARIATION_PHONETIC, + InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS, + InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE, + InputType.TYPE_TEXT_VARIATION_URI) -> { + // Type not used + } + else -> { + Log.d(TAG, "Autofill unknown android text type: ${showHexInputType(inputType)}") + } } - inputType and InputType.TYPE_TEXT_VARIATION_NORMAL != 0 || - inputType and InputType.TYPE_NUMBER_VARIATION_NORMAL != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_PERSON_NAME != 0 -> { - usernameCandidate = autofillId - Log.d(TAG, "Autofill username candidate android type: $inputType") - } - inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != 0 || - inputType and InputType.TYPE_NUMBER_VARIATION_PASSWORD != 0 -> { - result?.passwordId = autofillId - Log.d(TAG, "Autofill password android type: $inputType") - // Username not needed in this case - usernameNeeded = false - return true - } - inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_FILTER != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_PHONETIC != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_URI != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != 0 || - inputType and InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != 0 -> { - // Type not used - } - else -> { - Log.d(TAG, "Autofill unknown android type: $inputType") - usernameCandidate = autofillId + } + InputType.TYPE_CLASS_NUMBER -> { + when { + inputIsVariationType(inputType, + InputType.TYPE_NUMBER_VARIATION_NORMAL) -> { + usernameCandidate = autofillId + Log.d(TAG, "Autofill usernale candidate android number type: ${showHexInputType(inputType)}") + } + inputIsVariationType(inputType, + InputType.TYPE_NUMBER_VARIATION_PASSWORD) -> { + result?.passwordId = autofillId + Log.d(TAG, "Autofill password android number type: ${showHexInputType(inputType)}") + usernameNeeded = false + return true + } + else -> { + Log.d(TAG, "Autofill unknown android number type: ${showHexInputType(inputType)}") + } } } } 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..70f32c332 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 @@ -404,14 +404,9 @@ class Database { return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchQuery, SearchParameters(), max) } - 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 { + fun createVirtualGroupFromSearchInfo(searchInfoString: String, + max: Int = Integer.MAX_VALUE): Group? { + return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchInfoString, SearchParameters().apply { searchInTitles = false searchInUserNames = false searchInPasswords = false diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt index 9e94ab4e2..dc57f168f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt @@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorK import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.model.getSearchString import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.TimeoutHelper @@ -48,9 +49,13 @@ class SearchHelper(private val isOmitBackup: Boolean) { if (database.loaded && TimeoutHelper.checkTime(context)) { var searchWithoutUI = false if (PreferencesUtil.isAutofillAutoSearchEnable(context) - && searchInfo != null) { + && searchInfo != null + && !searchInfo.containsOnlyNullValues()) { // If search provide results - database.createVirtualGroupFromSearch(searchInfo, SearchHelper.MAX_SEARCH_ENTRY)?.let { searchGroup -> + database.createVirtualGroupFromSearchInfo( + searchInfo.getSearchString(context), + MAX_SEARCH_ENTRY + )?.let { searchGroup -> if (searchGroup.getNumberOfChildEntries() > 0) { searchWithoutUI = true onItemsFound.invoke( 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..c9adeef5e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt @@ -1,12 +1,31 @@ package com.kunzisoft.keepass.model +import android.content.Context +import android.content.res.Resources import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.settings.PreferencesUtil +import com.kunzisoft.keepass.utils.ObjectNameResource +import com.kunzisoft.keepass.utils.UriUtil -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 +45,40 @@ class SearchInfo : Parcelable { parcel.writeString(webDomain ?: "") } + override fun getName(resources: Resources): String { + return toString() + } + + 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 webDomain ?: applicationId ?: "" + } + 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 = object : Parcelable.Creator { @@ -39,4 +91,15 @@ class SearchInfo : Parcelable { } } } +} + +fun SearchInfo.getSearchString(context: Context): String { + return run { + if (!PreferencesUtil.searchSubdomains(context)) + UriUtil.getWebDomainWithoutSubDomain(webDomain) + else + webDomain + } + ?: applicationId + ?: "" } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt index 850575bf7..0deb45eec 100644 --- a/app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/notifications/KeyboardEntryNotificationService.kt @@ -151,6 +151,7 @@ class KeyboardEntryNotificationService : LockNotificationService() { fun launchNotificationIfAllowed(context: Context, entry: EntryInfo, toast: Boolean) { + val containsURLToCopy = entry.url.isNotEmpty() val containsUsernameToCopy = entry.username.isNotEmpty() val containsPasswordToCopy = entry.password.isNotEmpty() val containsExtraFieldToCopy = entry.customFields.isNotEmpty() @@ -158,7 +159,7 @@ class KeyboardEntryNotificationService : LockNotificationService() { var startService = false val intent = Intent(context, KeyboardEntryNotificationService::class.java) - if (containsUsernameToCopy || containsPasswordToCopy || containsExtraFieldToCopy) { + if (containsURLToCopy || containsUsernameToCopy || containsPasswordToCopy || containsExtraFieldToCopy) { if (toast) { Toast.makeText(context, context.getString(R.string.keyboard_notification_entry_content_title, entry.title), diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsActivity.kt index 8279bf6ad..29c13c85a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsActivity.kt @@ -23,9 +23,9 @@ import android.os.Bundle import android.view.MenuItem import androidx.appcompat.widget.Toolbar import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.activities.selection.SpecialModeActivity -class AutofillSettingsActivity : StylishActivity() { +class AutofillSettingsActivity : SpecialModeActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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..cba9c231d 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,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" + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsActivity.kt index 98a5ae62a..fc1a04db9 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsActivity.kt @@ -24,9 +24,9 @@ import androidx.appcompat.widget.Toolbar import android.view.MenuItem import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.activities.selection.SpecialModeActivity -class MagikeyboardSettingsActivity : StylishActivity() { +class MagikeyboardSettingsActivity : SpecialModeActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt index a3208bc15..cac7c0338 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt @@ -20,8 +20,10 @@ package com.kunzisoft.keepass.settings import android.content.Context +import android.content.res.Resources import android.net.Uri import androidx.preference.PreferenceManager +import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.R import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.timeout.TimeoutHelper @@ -97,6 +99,12 @@ object PreferencesUtil { context.resources.getBoolean(R.bool.auto_focus_search_default)) } + fun searchSubdomains(context: Context): Boolean { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getBoolean(context.getString(R.string.subdomain_search_key), + context.resources.getBoolean(R.bool.subdomain_search_default)) + } + fun showUsernamesListEntries(context: Context): Boolean { val prefs = PreferenceManager.getDefaultSharedPreferences(context) return prefs.getBoolean(context.getString(R.string.list_entries_show_username_key), @@ -366,4 +374,46 @@ object PreferencesUtil { return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key), context.resources.getBoolean(R.bool.autofill_auto_search_default)) } + + /** + * Retrieve the default Blocklist for application ID, including the current app + */ + fun getDefaultApplicationIdBlocklist(resources: Resources?): Set { + return resources?.getStringArray(R.array.autofill_application_id_blocklist_default) + ?.toMutableSet()?.apply { + add(BuildConfig.APPLICATION_ID) + } ?: emptySet() + } + + fun applicationIdBlocklist(context: Context): Set { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getStringSet(context.getString(R.string.autofill_application_id_blocklist_key), + getDefaultApplicationIdBlocklist(context.resources)) + ?: emptySet() + } + + fun webDomainBlocklist(context: Context): Set { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), + context.resources.getStringArray(R.array.autofill_web_domain_blocklist_default).toMutableSet()) + ?: emptySet() + } + + fun addApplicationIdToBlocklist(context: Context, applicationId: String) { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val setItems: MutableSet = applicationIdBlocklist(context).toMutableSet() + setItems.add(applicationId) + prefs.edit() + .putStringSet(context.getString(R.string.autofill_application_id_blocklist_key), setItems) + .apply() + } + + fun addWebDomainToBlocklist(context: Context, webDomain: String) { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val setItems: MutableSet = webDomainBlocklist(context).toMutableSet() + setItems.add(webDomain) + prefs.edit() + .putStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), setItems) + .apply() + } } 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..708ba3fd1 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt @@ -0,0 +1,36 @@ +/* + * 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 + +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/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt new file mode 100644 index 000000000..ae95ad708 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt @@ -0,0 +1,50 @@ +/* + * 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 com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.settings.PreferencesUtil + +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 } + } + + override fun getDefaultValues(): Set { + return PreferencesUtil.getDefaultApplicationIdBlocklist(this.resources) + } + + companion object { + fun newInstance(key: String): AutofillBlocklistAppIdPreferenceDialogFragmentCompat { + val fragment = AutofillBlocklistAppIdPreferenceDialogFragmentCompat() + 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/AutofillBlocklistPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt new file mode 100644 index 000000000..d85342276 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt @@ -0,0 +1,149 @@ +/* + * 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 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 { + + private var persistedItems = TreeSet( + Comparator { o1, o2 -> o1.toString().compareTo(o2.toString()) }) + + private var filterAdapter: AutofillBlocklistAdapter? = null + + abstract fun buildSearchInfoFromString(searchInfoString: String): SearchInfo? + + abstract fun getDefaultValues(): Set + + 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(getDefaultValues()).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(R.id.add_item_button) + addItemButton?.setOnClickListener { + addItemFromInputText() + } + + val recyclerView = view.findViewById(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 { + val setItems = HashSet() + 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" + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt new file mode 100644 index 000000000..2698ec66a --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt @@ -0,0 +1,55 @@ +/* + * 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 com.kunzisoft.keepass.R +import com.kunzisoft.keepass.model.SearchInfo + +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 } + } + + override fun getDefaultValues(): Set { + return context?.resources + ?.getStringArray(R.array.autofill_web_domain_blocklist_default) + ?.toMutableSet() + ?: emptySet() + } + + companion object { + fun newInstance(key: String): AutofillBlocklistWebDomainPreferenceDialogFragmentCompat { + val fragment = AutofillBlocklistWebDomainPreferenceDialogFragmentCompat() + 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/InputPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt index 1db7c8e49..7932e3d3d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt @@ -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) } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlocklistAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlocklistAdapter.kt new file mode 100644 index 000000000..3445ddb48 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlocklistAdapter.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 AutofillBlocklistAdapter(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): 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) { + 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 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) + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/UriUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/UriUtil.kt index 14b17346c..fa0a24625 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/UriUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/UriUtil.kt @@ -86,6 +86,19 @@ object UriUtil { null } + fun getWebDomainWithoutSubDomain(webDomain: String?): String? { + webDomain?.split(".")?.let { domainArray -> + if (domainArray.isEmpty()) { + return "" + } + if (domainArray.size == 1) { + return domainArray[0]; + } + return domainArray[domainArray.size - 2] + "." + domainArray[domainArray.size - 1] + } + return null + } + fun decode(uri: String?): String { return Uri.decode(uri) ?: "" } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/SpecialModeView.kt b/app/src/main/java/com/kunzisoft/keepass/view/SpecialModeView.kt new file mode 100644 index 000000000..03a840d55 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/view/SpecialModeView.kt @@ -0,0 +1,53 @@ +/* + * 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.view + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.appcompat.widget.Toolbar +import com.kunzisoft.keepass.R + +class SpecialModeView @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyle: Int = androidx.appcompat.R.attr.toolbarStyle) + : Toolbar(context, attrs, defStyle) { + + init { + setNavigationIcon(R.drawable.ic_close_white_24dp) + title = resources.getString(R.string.selection_mode) + } + + var onCancelButtonClickListener: OnClickListener? = null + set(value) { + if (value != null) + setNavigationOnClickListener(value) + } + + var visible: Boolean = false + set(value) { + visibility = if (value) { + View.VISIBLE + } else { + View.GONE + } + field = value + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt index a4d28972e..c62ffe5a4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt @@ -33,7 +33,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.snackbar.Snackbar import com.kunzisoft.keepass.R import com.kunzisoft.keepass.tasks.ActionRunnable -import java.util.* /** * Replace font by monospace, must be called after seText() @@ -93,14 +92,17 @@ fun Toolbar.collapse(animate: Boolean = true) { } fun Toolbar.expand(animate: Boolean = true) { - visibility = View.VISIBLE val actionBarHeight = layoutParams.height + layoutParams.height = 0 val slideAnimator = ValueAnimator .ofInt(0, actionBarHeight) if (animate) slideAnimator.duration = 300L slideAnimator.addUpdateListener { animation -> layoutParams.height = animation.animatedValue as Int + if (layoutParams.height >= 1) { + visibility = View.VISIBLE + } requestLayout() } AnimatorSet().apply { diff --git a/app/src/main/res/drawable-v21/background_button.xml b/app/src/main/res/drawable-v21/background_button.xml index 7971f309f..efe76dbbc 100644 --- a/app/src/main/res/drawable-v21/background_button.xml +++ b/app/src/main/res/drawable-v21/background_button.xml @@ -8,8 +8,8 @@ diff --git a/app/src/main/res/drawable/background_button.xml b/app/src/main/res/drawable/background_button.xml index 84c503d39..addbbc56d 100644 --- a/app/src/main/res/drawable/background_button.xml +++ b/app/src/main/res/drawable/background_button.xml @@ -5,8 +5,8 @@ @@ -17,8 +17,8 @@ diff --git a/app/src/main/res/drawable/ic_block_white_24dp.xml b/app/src/main/res/drawable/ic_block_white_24dp.xml new file mode 100644 index 000000000..9c5704687 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_file_selection.xml b/app/src/main/res/layout/activity_file_selection.xml index 66de7776c..8ded0fa7a 100644 --- a/app/src/main/res/layout/activity_file_selection.xml +++ b/app/src/main/res/layout/activity_file_selection.xml @@ -26,13 +26,22 @@ android:importantForAutofill="noExcludeDescendants" tools:targetApi="o"> + + + - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_group.xml b/app/src/main/res/layout/activity_group.xml index 5cfdf3803..4d292f51e 100644 --- a/app/src/main/res/layout/activity_group.xml +++ b/app/src/main/res/layout/activity_group.xml @@ -17,18 +17,28 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . --> - + + + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@+id/special_mode_view" + app:layout_constraintBottom_toTopOf="@+id/toolbar_action"> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_password.xml b/app/src/main/res/layout/activity_password.xml index 5f64a1808..d505147f6 100644 --- a/app/src/main/res/layout/activity_password.xml +++ b/app/src/main/res/layout/activity_password.xml @@ -21,17 +21,24 @@ 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/container" - android:importantForAutofill="noExcludeDescendants" android:layout_width="match_parent" android:layout_height="match_parent" tools:targetApi="o"> + + @@ -213,7 +221,7 @@ android:paddingLeft="24dp" android:paddingEnd="24dp" android:paddingRight="24dp" - style="@style/KeepassDXStyle.TextAppearance.TinyText" + style="@style/KeepassDXStyle.TextAppearance.Tiny" android:text="@string/warning_database_link_revoked" android:textColor="?attr/textColorInverse" android:background="?attr/colorAccent" diff --git a/app/src/main/res/layout/fragment_set_password.xml b/app/src/main/res/layout/fragment_set_password.xml index 98543a365..816d1d124 100644 --- a/app/src/main/res/layout/fragment_set_password.xml +++ b/app/src/main/res/layout/fragment_set_password.xml @@ -27,7 +27,6 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" - android:importantForAutofill="noExcludeDescendants" tools:targetApi="o"> @@ -81,7 +81,8 @@ android:layout_height="wrap_content" android:inputType="textPassword" android:importantForAccessibility="no" - android:importantForAutofill="no" + android:importantForAutofill="yes" + android:autofillHints="newPassword" android:maxLines="1" android:hint="@string/hint_conf_pass"/> diff --git a/app/src/main/res/layout/item_attachment.xml b/app/src/main/res/layout/item_attachment.xml index 610618663..bae3c40d8 100644 --- a/app/src/main/res/layout/item_attachment.xml +++ b/app/src/main/res/layout/item_attachment.xml @@ -58,7 +58,7 @@ android:id="@+id/item_attachment_compression" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@style/KeepassDXStyle.TextAppearance.TinyText" + style="@style/KeepassDXStyle.TextAppearance.Tiny" android:firstBaselineToTopHeight="0dp" android:includeFontPadding="false" android:paddingStart="8dp" 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..a26dc04c9 --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_input_list.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + \ 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/menu/autofill.xml b/app/src/main/res/menu/autofill.xml new file mode 100644 index 000000000..b70d493c6 --- /dev/null +++ b/app/src/main/res/menu/autofill.xml @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2aa94e051..57980aebf 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -117,13 +117,14 @@ مهلة التطبيق مدة الانتظار قبل إقفال قاعدة البيانات تصفَّح الملفات بتثبيت مدير الملفات OpenIntents - بعض هواتف سامسونغ لا تسمح للتطبيقات باستعمال الحافظة. + بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة. مهلة الحافظة مدة التخزين في الحافظة اختر لنسخ %1$s إلى الحافظة يجلب مفتاح قاعدة البيانات… استخدامها كقاعدة بيانات افتراضية - KeePassDX © %1$d د كونزيسوفت تأتي مع الضمان لا على الإطلاق؛ هذا هو البرمجيات الحرة، وكنت أهلا إعادة توزيعه تحت شروط إصدار الترخيص 3 أو في وقت لاحق. + KeePassDX © %1$d كونزيسوفت <strong>مفتوح المصدر</strong> و <strong>بدون اعلانات</strong>. +\n يوزع كما هو، بدون ضمان, تحت ترخيص <strong>GPLv3</strong> نُفذ إليه تنتهي صلاحيته في ملف المفتاح @@ -276,4 +277,12 @@ أغلق الحقول لا يمكن انشاء قاعدة بيانات بكلمة السر وملف المفتاح الحاليين. إلغاء القفل المتقدم - + مرفقات + السجل + أضف مرفقا + إلغاء + تجاهل التغييرات؟ + تأكيد + الأمان + المفتاح الرئيسي + \ No newline at end of file diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 8ed8d95c3..c6d5615ea 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -17,9 +17,8 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . Catalan translation by Oriol Garrote ---> - - Comentaris: +--> + Comentaris Pàgina inici: KeePassDX és una implementació per a Android de KeePass password manager Accepta diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index d8fd3fed4..0ac6894cc 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -344,7 +344,7 @@ Výběr položky Při prohlížení záznamu ukázat na Magikeyboard pole položek Smazat heslo - Smaže heslo zadané po pokusu o připojení + Smaže heslo zadané po pokusu o připojení k databázi Otevři soubor Potomci uzlu Přidej uzel @@ -476,4 +476,6 @@ Nastavení samovyplnění Přístup k souboru zrušenému správcem souborů Tento štítek již existuje. - + Automaticky vyhledat sdílené informace pro naplnění klávesnice + Prohledat sdílené info + \ No newline at end of file diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 3cd1fb9b2..5530dfa14 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -193,11 +193,11 @@ Bekræft brug af ingen adgangskode til beskyttelse mod oplåsning\? Fortsæt uden krypteringsnøgle\? Biometrisk prompt understøttes, men er ikke konfigureret. - Åbn den biometriske prompt for at låse databasen op + Åbn biometriske forespørgsel for at låse databasen op Krypteret adgangskode er gemt Kan ikke læse den biometriske nøgle. Slet den og gentag den biometriske genkendelsesprocedure. - Kunne ikke genkende fingeraftryk - Problem med fingeraftryk: %1$s + Kunne ikke genkende biometrisk + Biometrisk fejl: %1$s Åbn den biometriske prompt for at gemme legitimationsoplysninger Databasen har endnu ikke en adgangskode. Historik @@ -220,11 +220,11 @@ Skærmlås Lås databasen, når skærmen er slukket Fingeraftryk - Fingeraftryksscanning - Lader dig scanne din biometrisk for at åbne databasen + Biometrisk oplåsning + Giver mulighed for at scanne biometriske for at åbne databasen Slet krypteringsnøgler - Slet alle krypteringsnøgler, der er relateret til fingeraftryk - Slet alle nøgler, der er relateret til fingeraftryksgenkendelse\? + Slet alle krypteringsnøgler, der er relateret til biometrisk genkendelse + Slet alle krypteringsnøgler, der er relateret til biometrisk genkendelse\? Funktionen kunne ikke startes. Android-version %1$s opfylder ikke minimum versionskrav %2$s. Kunne ikke finde den tilsvarende hardware. @@ -269,8 +269,8 @@ \nGrupper (~mapper) organiserer poster i databasen. Søg i poster Indtast titel, brugernavn eller indhold af andre felter for at hente adgangskoder. - Database oplåsning med fingeraftryk - Link adgangskoden til det scannede fingeraftryk for hurtigt at låse databasen op. + Oplåsning af database ved hjælp af biometrisk + Knyt adgangskoden til det scannede biometri for hurtigt at låse databasen op. Rediger posten Rediger post med brugerdefinerede felter. Pool data kan refereres mellem forskellige indtastningsfelter. Opret en stærk adgangskode @@ -343,7 +343,7 @@ Valg af indtastning Vis indtastningsfelter i Magikeyboard, når der vises en post Slet adgangskode - Sletter adgangskoden som er indtastet efter et forbindelsesforsøg + Sletter adgangskoden der er angivet efter et forsøg på at oprette forbindelse til en database Åbn fil Undernode Tilføj knude @@ -374,7 +374,7 @@ Uddrag databasens legitimationsoplysninger med biometriske data Biometrisk Åbn automatisk biometrisk prompt - Spørg automatisk efter biometrisk, hvis databasen er konfigureret til at bruge den + Spørg automatisk efter biometri, hvis databasen er konfigureret til at bruge den Aktiver Deaktiver Hovednøgle @@ -428,7 +428,7 @@ Udfører kommandoen… Slet markerede noder permanent\? Nøglelageret er ikke korrekt initialiseret. - Skriv adgangskoden, før der klikkes på den biometriske knap. + Indtast adgangskoden, og klik derefter på knappen \"Biometrisk\". Papirkurvsgruppe Gem automatisk database Gem databasen efter hver en vigtig handling (i tilstanden \"Modificerbar\") @@ -475,4 +475,6 @@ Indstillinger for automatisk udfyldning Adgang til filen tilbagekaldt af filadministratoren Etiketten findes allerede. - + Søg automatisk efter delte oplysninger for at udfylde tastaturet + Søg i delte oplysninger + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d5a5833e5..35be36b5e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -357,7 +357,7 @@ Eintragsauswahl Eingabefelder beim Betrachten eines Eintrags im Magikeyboard anzeigen Passwort löschen - Löscht das eingegebene Passwort nach einem Verbindungsversuch + Löscht das eingegebene Passwort nach einem Verbindungsversuch zu einer Datenbank Datei öffnen Eintrag hinzufügen Gruppe hinzufügen @@ -486,4 +486,6 @@ Autofill-Einstellungen Zugriff auf die Datei durch den Dateimanager widerrufen Diese Bezeichnung existiert bereits. - + Automatische Suche nach gemeinsam genutzten Informationen zur Belegung der Tastatur + Gemeinsame Infos durchsuchen + \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index ae4af2f91..d58a8eef3 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -17,9 +17,8 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . Basque translation by David García-Abad ---> - - Feedback: +--> + Feedback Hasiera orria Keepass pasahitza kudeatzailearen Androiderako inplementazioa Onartu @@ -134,23 +133,21 @@ Euskarririk gabeko datubase bertsioa. Maiuskulak Bertsioa %1$s - Sartu pasahitz eta / edo gako fitxategi bat zure datubasea desblokeatzeko. - - 5 segundu - 10 segundu - 20 segundu - 30 segundu - minutu 1 - 5 minutu - 15 minutu - 30 minutu - Inoiz ez + 5 segundu + 10 segundu + 20 segundu + 30 segundu + minutu 1 + 5 minutu + 15 minutu + 30 minutu + Inoiz ez - Txikia - Ertaina - Handia + Txikia + Ertaina + Handia \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index e2bd030e5..6629171b2 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . --> - Palaute: - Kotisivu: + Palaute + Kotisivu KeePass-salasanahallintaohjelman Android toteutus Hyväksy Lisää uusi salasanatietue diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 6b3d68538..2e7090823 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -16,10 +16,9 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . ---> - - Comentarios: - Páxina inicial: +--> + Comentarios + Páxina inicial KeePassDX é unha implementación para Android do xestor de contrasinais KeePass. Aceptar Engadir entrada diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 6ef3ffd70..803ed658d 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . ---> - +--> Android implementacija KeePass upravitelja lozinki Prihvati Dodaj unos @@ -273,7 +272,7 @@ Obrada… Zaštićeno od pisanja Zavisno o vašem upravitelju datotekama, KeePassDX možda neće moći zapisivati u vašu pohranu. - Potvrdom ovog dialoga, KeePassDX će riješiti problem (generiranjem novih UUID-ova za duplikate) i nastaviti. + Riješi problem generiranjem novih UUID-ova za duplikate\? Korijen Količina memorije (u bajtovima) koju će koristiti funkcija izvedbe ključa. Ne zatvarajte aplikaciju… @@ -341,4 +340,15 @@ Kreirajte svoju prvu datoteku za upravljanje lozinkama. Otvaranje postojeće baze podataka Otvorite vašu prijašnju bazu podataka iz vašeg upravitelja datotekama kako bi je nastavili koristiti. + Zapamti lokaciju baze podataka + Brzo pretraživanje + Nije moguće kreirati datoteku baze podataka. + Prevelik broj \"transformacijskih rundi\". Postavljeno na 2147483648. + KeePassDX © %1$d Kunzisoft je <strong>open source</strong> te <strong>bez reklama</strong>. +\nDostupan kakav je, pod <strong>GPLv3</strong> licencom, bez ikakvog jamstva. + Dodaj privitak + Odbaci + Odbaci promjene\? + Kontakt + Početna stranica \ No newline at end of file diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index deb042625..03550c673 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -16,13 +16,12 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . ---> - +--> KeePassDX yra KeePass slaptažodžių tvarkyklės realizacija Android platformai Iškarpinė išvalyta. Neteisingas slaptažodis arba rakto failas. - Atsiliepimai: - Pagrindinis puslapis: + Atsiliepimai + Pagrindinis puslapis Priimti Pridėti įrašą Pridėti grupę @@ -107,7 +106,5 @@ Slėpti slaptažodžius pagal nutylėjimą Neteisingas algoritmas. Neteisingas kelias. - Įveskite slaptažodį rba raktą, tam kad atidarytumėte duomenų bazę. - - + \ No newline at end of file diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 9e23a6a59..b6f76af56 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -1,4 +1,4 @@ - + - - Atsauksmes: - Mājaslapa: +--> + Atsauksmes + Mājaslapa KeePassDX ir KeePass paroļu menedžera Android implementācija Pieņemt Jauns ieraksts @@ -131,9 +130,7 @@ Neatbalstīta datu bāzes versija. Lielie burti Versija %1$s - Ievadiet paroli/atslēgas failu, lai atbloķētu savu datu bāzi. - 5 sekundes 10 sekundes @@ -150,9 +147,9 @@ Vidējs Liels -Labot ierakstu + Labot ierakstu Šifrēšana Paplašinātais ASCII Atļaut KeePassDX © %1$d Kunzisoft nāk bez jebkādas garantijas. Šī ir bezmaksas programmatūra un jūs to varat izplatīt apstākļos, kas aprakstīti GPL 3 versijā vai jaunākā. - + \ No newline at end of file diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 3a14b392b..98bded3bb 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -222,7 +222,7 @@ Endre skriften brukt i felter for bedre tegngjengivelse Kopi av passord Tillat kopiering av adgangspassordet og beskyttede felter til utklippstavlen - ADVARSEL: Utklippstavlen deles av alle programmer. Hvis sensitiv data kopieres, kan annen programvare gjenopprette den. + Advarsel: Utklippstavlen deles av alle programmer. Hvis sensitiv data kopieres, kan annen programvare gjenopprette den. Databasenavn Databasebeskrivelse Databaseversjon @@ -255,7 +255,7 @@ Lenk passordet og fingeravtrykket ditt for å låse opp databasen din enkelt. Rediger oppføringen Rediger din oppføring med egendefinerte felter, referanser til pooldata kan legges til mellom felter av forskjellige oppføringer. - Opprett et sterkt passord. + Opprett et sterkt passord Generer et sterkt passord å tilknytte oppføringen din, definer det i henhold til kriteriene i skjemaet, og ikke glem å gjøre passordet sikkert. Legg til egendefinerte felter Du ønsker å registrere et grunnleggende felt som ikke allerede finnes, fyll inn et nytt et du også kan beskytte visuelt. @@ -281,7 +281,7 @@ Ulikt mange passordbehandlingsprogrammer, er dette <strong>reklamefri</strong>, <strong>copyleftbasert fri programvare</strong> og samler ikke inn personlig data på tjenerne sine, selv i sin gratisversjon. Ved kjøp av pro-versjonen, vil du få tilgang til denne <strong>visuelle funksjonen</strong> og du vil spesielt hjelpe <strong> realiseringen av gemenskapsprosjekter.</strong> - Denne <strong>visuelle funksjonen</strong> er tilgjengelig takket være din generøsitet. + Denne <strong>visuelle stilen</strong> er tilgjengelig takket være din generøsitet. For å beholde vår frihet og alltid være aktive, stoler vi på dine <strong>bidrag.</strong> Denne funksjonen er <strong>under utvikling</strong> og krever <strong>bidrag</strong> for å bli tilgjengelig snart. @@ -296,7 +296,7 @@ Rijndael (AES) Twofish ChaCha20 - AES KDF + AES Argon2 Velg en drakt Tilpass programdrakten ved å endre fargene @@ -319,8 +319,8 @@ Utseende Tastaturdrakt Taster - Vibrer ved tastetrykk - Lyd ved tastetrykk + Vibrasjonstastetrykk + Hørbare tastetrykk Valgmodus Ikke drep programmet… Tilbakelås @@ -331,7 +331,7 @@ Oppføringsvalg Vis inndatafelter i Magikeyboard når en oppføring vises Slett passord - Sletter passord innskrevet etter et tilkoblingsforsøk + Sletter passord innskrevet etter et tilkoblingsforsøk til en database Åpne fil Legg til node Legg til oppføring @@ -428,4 +428,9 @@ Vis låseknapp Denne etiketten finnes allerede. Bekreft + Åpne database med biometrisk gjenkjenning + Tilgang til filen nektes av filbehandleren + Innvilg skrivetilgang for å lagre databaseendringer + Skjul ødelagte lenker i listen over nylige databaser + Skjul ødelagte databaselenker \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b089afbf3..6382def4f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -21,7 +21,7 @@ --> Reacties Website - Android-implementatie van de KeePass-wachtwoordbeheerder + Android-implementatie van KeePass-wachtwoordbeheer Accepteren Item toevoegen Groep toevoegen @@ -31,7 +31,7 @@ App App-instellingen Haakjes - Een bestandsbeheerder die de Intent-actie ACTION_CREATE_DOCUMENT en ACTION_OPEN_DOCUMENT accepteert, is nodig om databasebestanden te maken, te openen en op te slaan. + Bestandsbeheer dat de Intent-actie ACTION_CREATE_DOCUMENT en ACTION_OPEN_DOCUMENT accepteert, is nodig om databasebestanden aan te maken, te openen en op te slaan. Klembord gewist Klembordtime-out Tijd van opslag op het klembord @@ -48,7 +48,7 @@ Annuleren Opmerkingen Wachtwoord bevestigen - Gecreëerd op + Aangemaakt Verloopt op Sleutelbestand Bewerkt op @@ -59,7 +59,7 @@ Gebruikersnaam De Arcfour stream-versleuteling wordt niet ondersteund. KeePassDX kan deze URI niet verwerken. - Kan bestand niet creëren: + Bestand is niet aangemaakt: Kan database niet uitlezen. Zorg ervoor dat het pad juist is. Voer een naam in. @@ -81,12 +81,12 @@ Kan referenties niet lezen. Databaseformaat kan niet worden herkend. Lengte - Lengte van itemlijst - Tekstgrootte van itemlijst + Lengte van lijst met items + Tekstgrootte in de lijst Database laden… Kleine letters - Wachtwoord verbergen - Wachtwoorden standaard afschermen (***) + Wachtwoorden verbergen + Wachtwoorden standaard verbergen (***) Over Hoofdsleutel wijzigen Instellingen @@ -124,7 +124,7 @@ Niet-ondersteunde databaseversie. Hoofdletters Versie %1$s - Geef het wachtwoord en/of sleutelbestand op om je database te ontgrendelen. + Voer het wachtwoord en/of sleutelbestand in om je database te ontgrendelen. \n \nMaak na elke aanpassing een kopie van je .kdbx-bestand op een veilige locatie. @@ -160,25 +160,25 @@ Een groep kan niet naar zichzelf worden verplaatst. Veldnaam Veldwaarde - Bestand niet gevonden. Probeer opnieuw te openen via je bestandsbeheerder. + Bestand niet gevonden. Probeer opnieuw te openen via bestandsbeheer. Ongeldig algoritme. Het sleutelbestand is leeg. Gebruikersnamen tonen - Gebruikersnamen tonen in itemlijsten + Gebruikersnamen tonen in lijsten Kopie van %1$s Formulierinvulling Kopiëren Verplaatsen Plakken Annuleren - Opgeslagen vingerafdruk verwijderen + Opgeslagen biometrische gegevens verwijderen Alleen-lezen Lezen en schrijven Beveiliging Alleen-lezen KeePassDX moet worden gemachtigd om je databank te kunnen aanpassen. - Recente bestandgeschiedenis - Recent gebruikte bestandsnamen onthouden + Bestandsgeschiedenis + Toon locaties van recente databases Database-encryptie-algoritme dat voor alle gegevens wordt gebruikt. Om de sleutel voor het algoritme te kunnen genereren, wordt de hoofdsleutel getransformeerd middels een willekeurige afleidingsfunctie. Geheugengebruik @@ -199,13 +199,13 @@ Vermijd wachtwoordtekens buiten het tekstcoderingsformaat in het databasebestand (niet-herkende tekens worden geconverteerd naar dezelfde letter). Doorgaan zonder beveiliging voor wachtwoordontgrendeling\? Doorgaan zonder coderingssleutel\? - Vingerafdruk wordt ondersteund, maar is niet ingesteld. - Open de biometrische prompt om de database te ontgrendelen + Biometrische herkenning is ondersteund, maar niet ingesteld. + Biometrische herkenning gebruiken om de database te ontgrendelen Versleuteld wachtwoord is opgeslagen - Kan de biometrische sleutel niet lezen. Verwijder deze en herhaal de procedure voor biometrische herkenning. - Vingerafdruk niet herkend - Vingerafdrukprobleem: %1$s - Vingerafdruk gebruiken om dit wachtwoord op te slaan + Kan de biometrische gegevens niet lezen. Verwijder deze en herhaal de procedure voor biometrische herkenning. + Biometrie niet herkend + Probleem met biometrie: %1$s + Biometrische herkenning gebruiken om wachtwoorden op te slaan Deze databank heeft nog geen wachtwoord. Geschiedenis Uiterlijk @@ -213,12 +213,12 @@ Auto-aanvullen KeePassDX auto-aanvullendienst Inloggen met KeePassDX - Standaard aanvuldienst instellen + Dienst voor automatisch aanvullen Schakel de dienst in om formulieren in andere apps snel in te vullen Gegenereerde wachtwoordlengte Standaardlengte van gegenereerd wachtwoord instellen Wachtwoordtekens - Standaard wachtwoordtekens instellen + Toegestane wachtwoordtekens instellen Klembord Klembordmeldingen Schakel klembordmeldingen in om velden te kopiëren bij het bekijken van een item @@ -226,11 +226,11 @@ Vergrendelen Schermvergrendeling Database vergrendelen wanneer het scherm is uitgeschakeld - Vingerafdruk - Vingerafdrukherkenning - Laat u uw biometrische gegevens scannen om de database te openen - Sleutels voor versleuteling verwijderen - Alle aan vingerafdrukherkenning gerelateerde sleutels wilt verwijderen + Geavanceerd ontgrendelen + Ontgrendelen met biometrie + Gebruik biometrische herkenning om de database te openen + Coderingssleutels verwijderen + Alle sleutels voor biometrische herkenning verwijderen Alle coderingssleutels voor biometrische herkenning verwijderen\? Kan deze functie niet starten. Je Android-versie, %1$s, voldoet niet aan de minimumvereiste %2$s. @@ -257,15 +257,15 @@ Toetsenbord Magikeyboard Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren - Geen hoofdwachtwoord toestaan + Geen hoofdwachtwoord Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd Alleen-lezen Open de database standaard alleen-lezen - Educatieve hints + Informatieve tips Markeer elementen om te leren hoe de app werkt - Educatieve hints opnieuw instellen - Educatieve informatie opnieuw weergeven - Educatieve hints opnieuw ingesteld + Informatieve tips opnieuw instellen + Informatieve tips opnieuw weergeven + Informatieve tips opnieuw ingesteld Maak je databasebestand aan Maak je eerste wachtwoordbeheerbestand aan. Open een bestaande database @@ -276,8 +276,8 @@ \nGroepen (~mappen) organiseren de items in je database. Doorzoek al je items Doorzoek items op titel, gebruikersnaam of andere velden om wachtwoorden te vinden. - Ontgrendel de database met biometrie - Koppel je wachtwoord en vingerafdruk om de database snel te ontgrendelen. + Database-ontgrendeling met biometrie + Koppel je wachtwoord aan je biometrie om de database snel te ontgrendelen. Item bewerken Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items. Genereer een sterk wachtwoord @@ -286,10 +286,9 @@ Registreer een extra veld, voeg een waarde toe en bescherm dit desgewenst. Ontgrendel je database Database alleen-lezen - Wijzig de opstartmodus van de sessie. -\n -\nIn alleen-lezenmodus kunnen geen onbedoelde wijzigingen worden gemaakt. -\n + Wijzig de opstartmodus van de sessie. +\n +\nIn alleen-lezenmodus kunnen geen onbedoelde wijzigingen worden gemaakt. \nIn schrijfmodus kun je elementen toevoegen, verwijderen of aanpassen. Veld kopiëren toestaan Kopieer een veld en plak de inhoud waar je maar wilt. @@ -304,8 +303,7 @@ In tegenstelling tot veel apps voor wachtwoordbeheer, is deze <strong> reclamevrij</strong>, <strong> vrije software </strong> en verzamelt het geen persoonlijke gegevens op haar servers, ongeacht welke versie je gebruikt. Door de pro-versie te kopen krijg je toegang tot dit <strong>visuele thema</strong> en draag je bij aan het <strong>realiseren van gemeenschapsprojecten.</strong> Dit <strong>visuele thema</strong> is beschikbaar gemaakt dankzij jouw vrijgevigheid. - Om altijd vrij en actief te blijven, zijn we afhankelijk van jouw <strong>bijdrage.</strong> - + Om altijd vrij en actief te blijven, rekenen we op jouw <strong>bijdrage.</strong> Deze functie <strong>wordt momenteel ontwikkeld</strong> en kan alleen beschikbaar komen middels jouw <strong>bijdrage</strong>. Door de <strong>pro</strong>-versie te kopen, Door <strong>bij te dragen</strong>, @@ -322,7 +320,7 @@ Thema gebruikt in de app Icoonverzameling Iconenverzameling gebruikt in de app - Bouw %1$s + Build %1$s Magikeyboard Magikeyboard (KeePassDX) Instellingen Magikeyboard @@ -343,7 +341,7 @@ Geluid bij toetsaanslag Selectiemodus App niet afsluiten… - Druk op \'Terug\' om te vergrendelen + Druk \'Terug\' om te vergrendelen Vergrendel de database wanneer de gebruiker op de knop Terug in het hoofdscherm klikt Wissen bij afsluiten Vergrendel de database wanneer u de melding sluit @@ -351,7 +349,7 @@ Itemselectie Invoervelden in Magikeyboard tonen bij het bekijken van een item Wachtwoord wissen - Wist het ingevoerde wachtwoord na een verbindingspoging + Wis het ingevoerde wachtwoord na een poging met een database te verbinden Bestand openen Onderliggende items Knooppunt toevoegen @@ -369,20 +367,20 @@ UUID Je kan hier geen item plaatsen. Je kan hier geen item kopiëren. - Het aantal items tonen + Toon het aantal items Toon het aantal items in een groep Achtergrond Update Velden sluiten - Kan geen database maken met dit wachtwoord en dit sleutelbestand. + Kan geen database aanmaken met dit wachtwoord en sleutelbestand. Geavanceerd ontgrendelen Biometrische herkenning opslaan Waarschuwing: Je moet nog steeds je hoofdwachtwoord onthouden als je biometrische herkenning gebruikt. - Database met biometrische herkenning openen - Database-referenties uitpakken met biometrische gegevens - Biometrisch - Automatisch biometrische prompt openen - Vraag automatisch om biometrie als een database hiervoor is ingesteld + Database openen met biometrische herkenning + Database-referenties uitpakken met biometrische herkenning + Biometrie + Automatisch om biometrie vragen + Automatisch om biometrie vragen als een database hiervoor is ingesteld Inschakelen Uitschakelen Hoofdsleutel @@ -423,33 +421,33 @@ Vernieuwing afdwingen De hoofdsleutel verplicht wijzigen (dagen) Volgende keer verplicht vernieuwen - Het hoofdwachtwoord de volgende keer vernieuwen (eenmalig) + Het hoofdwachtwoord de volgende keer (eenmalig) vernieuwen Standaard gebruikersnaam Aangepaste databasekleur Compressie Geen gzip - Toetsenbordinstellingen voor apparaat - Sla de database op na elke belangrijke actie (in \"Modificeerbare\" modus) + Toetsenbordinstellingen + Sla de database op na elke belangrijke actie (in \"Schrijf\" modus) Instellingen OTP Locatie van sleutelbestanden opslaan - Locatie van de databases opslaan - Verlopen items zijn verborgen - Verlopen items verbergen + Databaselocatie opslaan + Verlopen items worden verborgen + Verberg verlopen items Klaar! Tik om het bestand te openen. Voltooien… Voortgang: %1$d%% Initialiseren… Download %1$s Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat is aangevraagd voor tweefactorauthenticatie (2FA). - Database automatisch opslaan + Automatisch opslaan Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID Automatisch zoeken Prullenbak Geeft de vergrendelknop weer in de gebruikersinterface Vergrendelknop weergeven - Instellingen voor automatisch invullen - Typ het wachtwoord in en klik vervolgens op de knop \"Biometrisch\". + Instellingen voor automatisch aanvullen + Voer het wachtwoord in en klik vervolgens op de knop \"Biometrie\". De sleutelopslag is niet correct geïnitialiseerd. Geselecteerde knooppunten definitief verwijderen\? Toegang tot het bestand ingetrokken door bestandsbeheer @@ -466,14 +464,16 @@ Prullenbak legen Database opslaan Kan database niet opslaan. - Kan databasebestand niet aanmaken. + Databasebestand is niet aangemaakt. Dit label bestaat al. Bijlagen - Om <strong>onze vrijheid te behouden</strong>, <strong>bugs te verhelpen</strong>, <strong>functies toe te voegen</strong> en <strong>altijd actief te blijven</strong>, kunnen wij niet zonder uw <strong>bijdrage</strong>. + Om <strong>onze vrijheid te behouden</strong>, <strong>bugs te verhelpen</strong>, <strong>functies toe te voegen</strong> en <strong>altijd actief te blijven</strong>, rekenen wij op jouw <strong>bijdrage</strong>. Bijlage toevoegen Weggooien Veranderingen ongedaan maken\? Valideren Bijdragen Contact - + Zoek automatisch naar gedeelde informatie om het toetsenbord te vullen + Gedeelde informatie zoeken + \ No newline at end of file diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index e0d684343..1ac743e35 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -16,12 +16,11 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . ---> - - Tilbakemelding: - Heimeside: +--> + Tilbakemelding + Heimeside KeePassDX er ei android-utgåve av KeePass passordstyrar. - Godta + Godta Legg til post Legg til gruppe Algoritme @@ -122,23 +121,21 @@ Kan ikkje bruka databaseutgåva. Store bokstavar Utgåve %1$s - Skriv inn passordet og/eller nøkkelfil for å låsa opp databasen. - - 5 sekund - 10 sekund - 20 sekund - 30 sekund - 1 minutt - 5 minutt - 15 minutt - 30 minutt - Aldri + 5 sekund + 10 sekund + 20 sekund + 30 sekund + 1 minutt + 5 minutt + 15 minutt + 30 minutt + Aldri - Liten - Middels - Stor + Liten + Middels + Stor - + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f4b1aa0c4..fc5fa1006 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -115,7 +115,7 @@ Porządek naturalny Znaki specjalne Szukaj - Dwie ryby + Algorytm szyfrujący Twofish Podkreślenie Nieobsługiwana wersja bazy danych. Wielkie litery @@ -344,7 +344,7 @@ Wybór wpisu Pokaż pola wprowadzania w Magikeyboard podczas przeglądania wpisu Usuń hasło - Usuwa hasło wprowadzone po próbie połączenia + Usuwa hasło wprowadzone po próbie połączenia z bazą danych Otwórz plik Dodaj wpis Dodaj grupę @@ -474,4 +474,6 @@ Ustawienia autouzupełniania Dostęp do pliku anulowany przez menedżera plików Ta etykieta już istnieje. - + Automatyczne wyszukiwanie udostępnionych informacji w celu zapełnienia klawiatury + Wyszukaj udostępnione informacje + \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d4cac58ae..4f05e0dc6 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -192,7 +192,7 @@ Continuar sem proteção de desbloqueio de senha\? Continuar sem chave de criptografia\? Impressão digital é suportada, mas não está configurada. - Escaneamento de impressão digital + Escaneamento de impressão digital para abrir o bando de dados Senha encriptada armazenada Não pôde ler chave de impressão digital. Por favor, apague-a e repita o procedimento de reconhecimento biométrico. Não pôde reconhecer impressão digital @@ -347,7 +347,7 @@ Seleção de entrada Mostrar campos de entrada no Magikeyboard quando estiver visualizando uma Entrada Deletar senha - Deleta a senha inserida após uma tentativa de conexão + Deleta a senha inserida após uma tentativa de conexão com o banco de dados Abrir arquivo Inserir nó Adicionar entrada @@ -474,4 +474,6 @@ Descartar alterações\? Validação Contribuição + Procurar automaticamente por informação compartilhada para popular o teclado + Procurar informação compartilhada \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2588e006e..e4e22b51a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -344,7 +344,7 @@ Выбор записи Показывать поля ввода в Magikeyboard при просмотре записи Удалять пароль - Забывать введённый пароль + Забывать введённый пароль после попытки входа в базу Открыть файл Дочерний узел Добавить узел @@ -474,4 +474,6 @@ Доступ к файлу отменён файловым менеджером Автопоиск Настройки автозаполнения - + Выполнять автоматический поиск общедоступной информации для подстановки в клавиатуру + Поиск доступной информации + \ No newline at end of file diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 4bf499514..86dc465bb 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -1,4 +1,4 @@ - + - - అభిప్రాయం: - హోమ్ పేజీ: +--> + అభిప్రాయం + హోమ్ పేజీ KeePassDX అనేది KeePass పాస్వర్డ్ మేనేజర్ యొక్క యాండ్రాయిడ్ అమలు. ఆమోదించు "ఎంట్రీని జత చెయ్యండి " @@ -29,4 +28,4 @@ ఎన్క్రిప్షన్ అల్గోరిథం కీ వ్యుత్పత్తి ఫంక్షన్ యాప్ కి అనువర్తించిన గడువు ముగిసింది - + \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 6b4988c7d..20bd633ce 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -346,7 +346,7 @@ Giriş seçimi Bir girişi görüntülerken Magikeyboard\'da giriş alanlarını göster Parolayı sil - Bir bağlantı denemesinden sonra girilen parolayı siler + Veri tabanına bağlantı denemesinden sonra girilen parolayı siler Alt düğüm Parola görünürlüğünü değiştirmeyi tekrarla Arka plan @@ -458,4 +458,6 @@ Otomatik doldurma ayarları Dosyaya erişim dosya yöneticisi tarafından iptal edildi Bu etiket zaten var. - + Klavyeyi doldurmak için paylaşılan bilgileri otomatik olarak ara + Paylaşılan bilgileri ara + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5be3562be..105b9b6b9 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -18,7 +18,7 @@ along with KeePassDX. If not, see . --> Відгук - Домашня сторінка + Домівка KeePassDX є Android-версією менеджера паролів KeePass Прийняти Додати запис @@ -141,7 +141,7 @@ Середній Великий - Допомога проєкту + Допомогти проєкту Контакти Довжина пароля Відкрити файл @@ -149,7 +149,7 @@ Змінити запис Проведіть пальцем, щоб очистити буфер обміну зараз Блокувати базу даних під час закриття сповіщення - Розблокування за допомогою біометричних даних підтримується, але не налаштоване. + Розблокування біометричними даними підтримується, але не налаштоване. Додати групу Оновити Клавіатура @@ -344,7 +344,7 @@ Автозбереження бази даних Типово відкривати базу даних лише для читання Захист від запису - Видаляти пароль, введений після спроби з\'єднання + Видаляти пароль, введений після спроби з\'єднання з базою даних Видалити пароль Увімкнути кнопку \"Відкрити\", якщо не вибрано головний пароль Дозволити без головного ключа @@ -421,7 +421,7 @@ Якщо не вдалось очистити буфер обміну автоматично, видаліть його історію вручну. Увімкнути сповіщення буфера обміну для копіювання полів під час перегляду запису Сповіщення буфера обміну - Копіювати облікові дані за допомогою буфера обміну вашого пристрою + Копіювати облікові дані за допомогою буфера обміну пристрою Буфер обміну Базу даних відкрито Встановіть дозволені символи для створення пароля @@ -445,8 +445,8 @@ Витяг облікових даних бази даних за допомогою біометричного ключа Відкрити базу даних біометричним ключем Зберегти біометричний ключ - Скористатися біометричним ключем для розблокування базу даних - Скористатися біометричним ключем, щоби зберегти дані + Розблокувати базу даних біометричним ключем + Скористатися біометричним ключем щоби зберегти дані Сховище ключів не ініціалізовано належним чином. Збірка %1$s Видалити вибрані вузли остаточно\? @@ -474,4 +474,6 @@ Паралельність Використання пам\'яті Алгоритм шифрування бази даних, застосований для всіх даних. - + Автоматичний пошук загальнодоступної інформації для заповнення клавіатури + Пошук доступної інформації + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 70faa81c0..590aec2ae 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -347,7 +347,7 @@ 条目选择 在查看条目时,在专用键盘中显示输入字段 删除密码 - 密码错误后删除已输入的密码 + 在连接数据库尝试后删除输入的密码 打开文件 子节点 增加节点 @@ -474,4 +474,6 @@ 自动填充设置 访问文件管理器撤消访问权限的文件 该标签已经存在。 - + 自动搜索共享信息以填充键盘 + 搜索分享的信息 + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index a257e69a5..94954965d 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -19,7 +19,8 @@ --> - + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 86fb588bd..56303cb56 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -42,6 +42,9 @@ #1976D2 #1565C0 + #33b5e5 + #2c7a96 + #ef9a9a #f44336 #e53935 diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 9c6b93295..00f7ef8fe 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -67,6 +67,8 @@ true auto_focus_search_key false + subdomain_search_key + false app_timeout_key lock_database_screen_off_key true @@ -132,6 +134,12 @@ false autofill_auto_search_key true + autofill_application_id_blocklist_key + + + autofill_web_domain_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..fc067296e 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 @@ -131,6 +132,7 @@ Counter must be between %1$d and %2$d. Period must be between %1$d and %2$d seconds. Token must contain %1$d to %2$d digits. + This text does not match the requested item. Field name Field value Could not find file. Try reopening it from your file browser. @@ -200,6 +202,8 @@ Omits \"Backup\" and \"Recycle bin\" groups from search results Quick search Request a search when opening a database + Subdomain search + Search web domains with subdomains constraints Creating new database… Working… Protection @@ -230,6 +234,7 @@ Do not kill the app… Space Search + Filter Sort Lowest first ↓ Groups before @@ -378,7 +383,13 @@ Vibratory keypresses Audible keypresses Auto search - Automatically suggest search results from the web domain or application Id + Automatically suggest search results from the web domain or application ID + Application blocklist + Blocklist that prevents auto filling of apps + Web domain blocklist + Blocklist that prevents auto filling of web domains + Block autofill + Restart the app containing the form to activate the blocking. Allow no master key Enable the \"Open\" button if no credentials are selected Delete password diff --git a/app/src/main/res/values/style_black.xml b/app/src/main/res/values/style_black.xml index ac10958c6..a5e1aacc8 100644 --- a/app/src/main/res/values/style_black.xml +++ b/app/src/main/res/values/style_black.xml @@ -30,12 +30,13 @@ @color/green_light @color/background_dark @style/KeepassDXStyle.Toolbar.Black - @style/KeepassDXStyle.Toolbar.Black + @style/KeepassDXStyle.Toolbar.Special.Black + @style/KeepassDXStyle.Toolbar.Black @style/KeepassDXStyle.Black.Dialog @style/KeepassDXStyle.Black.Dialog @style/KeepassDXStyle.ActionMode.Dark - + + + @@ -42,7 +43,14 @@ @color/blue_lighter @color/colorTextInverse - + + diff --git a/app/src/main/res/values/style_dark.xml b/app/src/main/res/values/style_dark.xml index 5cd9d02c6..d2ddd55f4 100644 --- a/app/src/main/res/values/style_dark.xml +++ b/app/src/main/res/values/style_dark.xml @@ -22,14 +22,15 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/style_purple.xml b/app/src/main/res/values/style_purple.xml index 09b2fdc1a..59e47ecc7 100644 --- a/app/src/main/res/values/style_purple.xml +++ b/app/src/main/res/values/style_purple.xml @@ -29,7 +29,8 @@ @color/purple_lighter @color/background_purple @style/KeepassDXStyle.Toolbar.Purple - @style/KeepassDXStyle.Toolbar.Bottom.Purple + @style/KeepassDXStyle.Toolbar.Special.Purple + @style/KeepassDXStyle.Toolbar.Action.Purple @style/KeepassDXStyle.ActionMode.Purple @@ -42,7 +43,13 @@ @color/purple_lighter @color/colorTextInverse - + + diff --git a/app/src/main/res/values/style_red.xml b/app/src/main/res/values/style_red.xml index b42fbe18c..af4675612 100644 --- a/app/src/main/res/values/style_red.xml +++ b/app/src/main/res/values/style_red.xml @@ -29,7 +29,8 @@ @color/red_lighter @color/background_night @style/KeepassDXStyle.Toolbar.Red - @style/KeepassDXStyle.Toolbar.Red + @style/KeepassDXStyle.Toolbar.Special.Red + @style/KeepassDXStyle.Toolbar.Red @style/KeepassDXStyle.ActionMode.Red @@ -42,6 +43,11 @@ @color/red_lighter @color/colorTextInverse + + + + + + + + @@ -257,7 +272,7 @@ ?android:attr/textColorPrimary 20sp - + + + +