diff --git a/CHANGELOG b/CHANGELOG index 8a21086f4..ef7eb2ce2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ KeepassDX (2.5.0.0beta23) * New, more secure database creation workflow * Add alias for history files (WARNING: history is erased) + * New Biometric unlock (Fingerprint with new API) KeepassDX (2.5.0.0beta22) * Rebuild code for actions diff --git a/app/build.gradle b/app/build.gradle index ed0bb674d..e9985a1fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,6 +89,7 @@ dependencies { implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.biometric:biometric:1.0.0-beta01' implementation 'com.google.android.material:material:1.0.0' implementation "androidx.room:room-runtime:$room_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a2b155c43..b19a964e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ android:largeScreens="true" android:anyDensity="true" /> - + @@ -128,6 +128,7 @@ + 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 cab58be26..ab23fcef8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -48,7 +48,7 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter -import com.kunzisoft.keepass.app.database.FileDatabaseHistory +import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable import com.kunzisoft.keepass.database.action.ProgressDialogThread @@ -79,7 +79,7 @@ class FileDatabaseSelectActivity : StylishActivity(), // Adapter to manage database history list private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null - private var mFileDatabaseHistory: FileDatabaseHistory? = null + private var mFileDatabaseHistoryAction: FileDatabaseHistoryAction? = null private var mDatabaseFileUri: Uri? = null @@ -90,7 +90,7 @@ class FileDatabaseSelectActivity : StylishActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mFileDatabaseHistory = FileDatabaseHistory.getInstance(applicationContext) + mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext) setContentView(R.layout.activity_file_selection) fileListContainer = findViewById(R.id.container_file_list) @@ -167,7 +167,7 @@ class FileDatabaseSelectActivity : StylishActivity(), } mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete -> // Remove from app database - mFileDatabaseHistory?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted -> + mFileDatabaseHistoryAction?.deleteFileDatabaseHistory(fileDatabaseHistoryToDelete) { fileHistoryDeleted -> // Remove from adapter fileHistoryDeleted?.let { databaseFileHistoryDeleted -> mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted) @@ -178,7 +178,7 @@ class FileDatabaseSelectActivity : StylishActivity(), true } mAdapterDatabaseHistory?.setOnSaveAliasListener { fileDatabaseHistoryWithNewAlias -> - mFileDatabaseHistory?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias) + mFileDatabaseHistoryAction?.addOrUpdateFileDatabaseHistory(fileDatabaseHistoryWithNewAlias) } fileDatabaseHistoryRecyclerView.adapter = mAdapterDatabaseHistory @@ -295,7 +295,7 @@ class FileDatabaseSelectActivity : StylishActivity(), updateExternalStorageWarning() // Construct adapter with listeners - mFileDatabaseHistory?.getAllFileDatabaseHistories { databaseFileHistoryList -> + mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList -> databaseFileHistoryList?.let { mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it) updateFileListVisibility() @@ -361,7 +361,7 @@ class FileDatabaseSelectActivity : StylishActivity(), runOnUiThread { if (result.isSuccess) { // Add database to recent files - mFileDatabaseHistory?.addOrUpdateDatabaseUri(databaseFileUri, keyFileUri) + mFileDatabaseHistoryAction?.addOrUpdateDatabaseUri(databaseFileUri, keyFileUri) mAdapterDatabaseHistory?.notifyDataSetChanged() updateFileListVisibility() GroupActivity.launch(this@FileDatabaseSelectActivity) 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 070c92b8e..5dd68ee58 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -25,7 +25,6 @@ import android.app.backup.BackupManager import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences -import android.hardware.fingerprint.FingerprintManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -43,7 +42,9 @@ import android.view.MenuItem import android.view.View import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.widget.* +import androidx.biometric.BiometricManager import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment import com.kunzisoft.keepass.utils.ClipDataCompat import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper @@ -52,20 +53,19 @@ 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.utils.FileDatabaseInfo -import com.kunzisoft.keepass.app.database.FileDatabaseHistory +import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.education.PasswordActivityEducation -import com.kunzisoft.keepass.fingerprint.FingerPrintHelper -import com.kunzisoft.keepass.fingerprint.FingerPrintViewsManager +import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager import com.kunzisoft.keepass.magikeyboard.KeyboardHelper import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.UriUtil -import com.kunzisoft.keepass.view.FingerPrintInfoView +import com.kunzisoft.keepass.view.AdvancedUnlockInfoView import com.kunzisoft.keepass.view.asError import kotlinx.android.synthetic.main.activity_password.* import java.io.FileNotFoundException @@ -84,7 +84,7 @@ class PasswordActivity : StylishActivity() { private var checkboxPasswordView: CompoundButton? = null private var checkboxKeyFileView: CompoundButton? = null private var checkboxDefaultDatabaseView: CompoundButton? = null - private var fingerPrintInfoView: FingerPrintInfoView? = null + private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null private var mDatabaseFileUri: Uri? = null @@ -95,7 +95,7 @@ class PasswordActivity : StylishActivity() { private var readOnly: Boolean = false - private var fingerPrintViewsManager: FingerPrintViewsManager? = null + private var advancedUnlockedManager: AdvancedUnlockedManager? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -120,7 +120,7 @@ class PasswordActivity : StylishActivity() { checkboxPasswordView = findViewById(R.id.password_checkbox) checkboxKeyFileView = findViewById(R.id.keyfile_checkox) checkboxDefaultDatabaseView = findViewById(R.id.default_database) - fingerPrintInfoView = findViewById(R.id.fingerprint_info) + advancedUnlockInfoView = findViewById(R.id.fingerprint_info) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) @@ -216,7 +216,7 @@ class PasswordActivity : StylishActivity() { if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) { // Retrieve KeyFile in a thread databaseUri?.let { databaseUriNotNull -> - FileDatabaseHistory.getInstance(applicationContext) + FileDatabaseHistoryAction.getInstance(applicationContext) .getKeyFileUriByDatabaseUri(databaseUriNotNull) { onPostInitUri(databaseUri, it) } @@ -280,11 +280,16 @@ class PasswordActivity : StylishActivity() { // Init FingerPrint elements var fingerPrintInit = false if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isFingerprintEnable(this)) { - if (fingerPrintViewsManager == null) { - fingerPrintViewsManager = FingerPrintViewsManager(this, + if (PreferencesUtil.isBiometricUnlockEnable(this)) { + + advancedUnlockInfoView?.setOnClickListener { + FingerPrintExplanationDialog().show(supportFragmentManager, "fingerPrintExplanationDialog") + } + + if (advancedUnlockedManager == null && databaseFileUri != null) { + advancedUnlockedManager = AdvancedUnlockedManager(this, databaseFileUri, - fingerPrintInfoView, + advancedUnlockInfoView, checkboxPasswordView, enableButtonOnCheckedChangeListener, passwordView) { passwordRetrieve -> @@ -298,12 +303,10 @@ class PasswordActivity : StylishActivity() { } } } - fingerPrintViewsManager?.initFingerprint() - // checks if fingerprint is available, will also start listening for fingerprints when available - fingerPrintViewsManager?.checkFingerprintAvailability() + advancedUnlockedManager?.initBiometric() fingerPrintInit = true } else { - fingerPrintViewsManager?.destroy() + advancedUnlockedManager?.destroy() } } if (!fingerPrintInit) { @@ -361,14 +364,14 @@ class PasswordActivity : StylishActivity() { override fun onPause() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintViewsManager?.stopListening() + advancedUnlockedManager?.pause() } super.onPause() } override fun onDestroy() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintViewsManager?.destroy() + advancedUnlockedManager?.destroy() } super.onDestroy() } @@ -429,9 +432,9 @@ class PasswordActivity : StylishActivity() { runOnUiThread { // Recheck fingerprint if error if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isFingerprintEnable(this@PasswordActivity)) { - // Stay with the same mode - fingerPrintViewsManager?.reInitWithFingerprintMode() + if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) { + // Stay with the same mode and init it + advancedUnlockedManager?.initBiometricMode() } } @@ -485,7 +488,7 @@ class PasswordActivity : StylishActivity() { if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Fingerprint menu - fingerPrintViewsManager?.inflateOptionsMenu(inflater, menu) + advancedUnlockedManager?.inflateOptionsMenu(inflater, menu) } super.onCreateOptionsMenu(menu) @@ -523,12 +526,13 @@ class PasswordActivity : StylishActivity() { if (!readOnlyEducationPerformed) { + val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate() // fingerprintEducationPerformed - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && PreferencesUtil.isFingerprintEnable(applicationContext) - && FingerPrintHelper.isFingerprintSupported(getSystemService(FingerprintManager::class.java)) - && fingerPrintInfoView != null && fingerPrintInfoView?.fingerPrintImageView != null - && passwordActivityEducation.checkAndPerformedFingerprintEducation(fingerPrintInfoView?.fingerPrintImageView!!)) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && PreferencesUtil.isBiometricUnlockEnable(applicationContext) + && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) + && advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null + && passwordActivityEducation.checkAndPerformedFingerprintEducation(advancedUnlockInfoView?.unlockIconImageView!!) } } @@ -553,7 +557,7 @@ class PasswordActivity : StylishActivity() { changeOpenFileReadIcon(item) } R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintViewsManager?.deleteEntryKey() + advancedUnlockedManager?.deleteEntryKey() } else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt index eb3b1dfa1..dfbc8296d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt @@ -87,7 +87,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() { override fun onAttach(activity: Context) { super.onAttach(activity) try { - mListener = activity as AssignPasswordDialogListener? + mListener = activity as AssignPasswordDialogListener } catch (e: ClassCastException) { throw ClassCastException(activity.toString() + " must implement " + AssignPasswordDialogListener::class.java.name) diff --git a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintExplanationDialog.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/FingerPrintExplanationDialog.kt similarity index 75% rename from app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintExplanationDialog.kt rename to app/src/main/java/com/kunzisoft/keepass/activities/dialogs/FingerPrintExplanationDialog.kt index 3971563ae..d78a51162 100644 --- a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintExplanationDialog.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/FingerPrintExplanationDialog.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.fingerprint +package com.kunzisoft.keepass.activities.dialogs import android.app.Dialog import android.content.Intent @@ -28,6 +28,8 @@ import androidx.fragment.app.DialogFragment import androidx.appcompat.app.AlertDialog import android.view.View import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector +import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity @RequiresApi(api = Build.VERSION_CODES.M) class FingerPrintExplanationDialog : DialogFragment() { @@ -41,11 +43,16 @@ class FingerPrintExplanationDialog : DialogFragment() { val rootView = inflater.inflate(R.layout.fragment_fingerprint_explanation, null) - val fingerprintSettingWayTextView = rootView.findViewById(R.id.fingerprint_setting_way_text) - fingerprintSettingWayTextView.setOnClickListener { startActivity(Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS)) } + rootView.findViewById(R.id.fingerprint_setting_link_text).setOnClickListener { + startActivity(Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS)) + } + + rootView.findViewById(R.id.auto_open_biometric_prompt_button).setOnClickListener { + startActivity(Intent(activity, SettingsAdvancedUnlockActivity::class.java)) + } fingerPrintAnimatedVector = FingerPrintAnimatedVector(activity, - rootView.findViewById(R.id.fingerprint_image)) + rootView.findViewById(R.id.biometric_image)) builder.setView(rootView) .setPositiveButton(android.R.string.ok) { _, _ -> } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/KeyboardExplanationDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/KeyboardExplanationDialogFragment.kt index cee192656..c00c80b7e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/KeyboardExplanationDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/KeyboardExplanationDialogFragment.kt @@ -40,9 +40,7 @@ class KeyboardExplanationDialogFragment : DialogFragment() { val rootView = inflater.inflate(R.layout.fragment_keyboard_explanation, null) - rootView.findViewById(R.id.keyboards_activate_setting_path1_text) - .setOnClickListener { launchActivateKeyboardSetting() } - rootView.findViewById(R.id.keyboards_activate_setting_path2_text) + rootView.findViewById(R.id.keyboards_activate_device_setting_button) .setOnClickListener { launchActivateKeyboardSetting() } val containerKeyboardSwitcher = rootView.findViewById(R.id.container_keyboard_switcher) diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/ActionDatabaseAsyncTask.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/ActionDatabaseAsyncTask.kt new file mode 100644 index 000000000..fde90ab21 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/ActionDatabaseAsyncTask.kt @@ -0,0 +1,20 @@ +package com.kunzisoft.keepass.app.database + +import android.os.AsyncTask + +/** + * Private class to invoke each method in a separate thread + */ +class ActionDatabaseAsyncTask( + private val action: () -> T , + private val afterActionDatabaseListener: ((T?) -> Unit)? = null +) : AsyncTask() { + + override fun doInBackground(vararg args: Void?): T? { + return action.invoke() + } + + override fun onPostExecute(result: T?) { + afterActionDatabaseListener?.invoke(result) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/AppDatabase.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/AppDatabase.kt index cdbea44e7..8a41cf981 100644 --- a/app/src/main/java/com/kunzisoft/keepass/app/database/AppDatabase.kt +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/AppDatabase.kt @@ -5,9 +5,11 @@ import androidx.room.Room import androidx.room.RoomDatabase import android.content.Context -@Database(entities = [FileDatabaseHistoryEntity::class], version = 1) +@Database(version = 1, entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class]) abstract class AppDatabase : RoomDatabase() { - abstract fun databaseFileHistoryDao(): FileDatabaseHistoryDao + + abstract fun fileDatabaseHistoryDao(): FileDatabaseHistoryDao + abstract fun cipherDatabaseDao(): CipherDatabaseDao companion object { fun getDatabase(applicationContext: Context): AppDatabase { diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt new file mode 100644 index 000000000..18f20cdec --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt @@ -0,0 +1,73 @@ +package com.kunzisoft.keepass.app.database + +import android.content.Context +import android.net.Uri +import com.kunzisoft.keepass.utils.SingletonHolderParameter + +class CipherDatabaseAction(applicationContext: Context) { + + private val cipherDatabaseDao = + AppDatabase + .getDatabase(applicationContext) + .cipherDatabaseDao() + + fun getCipherDatabase(databaseUri: Uri, + cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) { + ActionDatabaseAsyncTask( + { + cipherDatabaseDao.getByDatabaseUri(databaseUri.toString()) + }, + { + cipherDatabaseResultListener.invoke(it) + } + ).execute() + } + + fun containsCipherDatabase(databaseUri: Uri, + contains: (Boolean) -> Unit) { + getCipherDatabase(databaseUri) { + contains.invoke(it != null) + } + } + + fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity, + cipherDatabaseResultListener: (() -> Unit)? = null) { + ActionDatabaseAsyncTask( + { + val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri) + + // Update values if element not yet in the database + if (cipherDatabaseRetrieve == null) { + cipherDatabaseDao.add(cipherDatabaseEntity) + } else { + cipherDatabaseDao.update(cipherDatabaseEntity) + } + }, + { + cipherDatabaseResultListener?.invoke() + } + ).execute() + } + + fun deleteByDatabaseUri(databaseUri: Uri, + cipherDatabaseResultListener: (() -> Unit)? = null) { + ActionDatabaseAsyncTask( + { + cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString()) + }, + { + cipherDatabaseResultListener?.invoke() + } + ).execute() + } + + fun deleteAll() { + ActionDatabaseAsyncTask( + { + cipherDatabaseDao.deleteAll() + } + ).execute() + } + + companion object : SingletonHolderParameter(::CipherDatabaseAction) +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseDao.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseDao.kt new file mode 100644 index 000000000..9431b7861 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseDao.kt @@ -0,0 +1,22 @@ +package com.kunzisoft.keepass.app.database + +import androidx.room.* + +@Dao +interface CipherDatabaseDao { + + @Query("SELECT * FROM cipher_database WHERE database_uri = :databaseUriString") + fun getByDatabaseUri(databaseUriString: String): CipherDatabaseEntity? + + @Insert + fun add(vararg fileDatabaseHistory: CipherDatabaseEntity) + + @Update + fun update(vararg fileDatabaseHistory: CipherDatabaseEntity) + + @Query("DELETE FROM cipher_database WHERE database_uri = :databaseUriString") + fun deleteByDatabaseUri(databaseUriString: String) + + @Query("DELETE FROM cipher_database") + fun deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt new file mode 100644 index 000000000..140b2bf2c --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseEntity.kt @@ -0,0 +1,33 @@ +package com.kunzisoft.keepass.app.database + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "cipher_database") +data class CipherDatabaseEntity( + @PrimaryKey + @ColumnInfo(name = "database_uri") + val databaseUri: String, + + @ColumnInfo(name = "encrypted_value") + var encryptedValue: String, + + @ColumnInfo(name = "specs_parameters") + var specParameters: String +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CipherDatabaseEntity + + if (databaseUri != other.databaseUri) return false + + return true + } + + override fun hashCode(): Int { + return databaseUri.hashCode() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistory.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt similarity index 77% rename from app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistory.kt rename to app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt index 8fb155d7a..c31711108 100644 --- a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistory.kt +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryAction.kt @@ -19,22 +19,20 @@ */ package com.kunzisoft.keepass.app.database -import androidx.sqlite.db.SimpleSQLiteQuery import android.content.Context import android.net.Uri -import android.os.AsyncTask import com.kunzisoft.keepass.utils.SingletonHolderParameter -class FileDatabaseHistory(applicationContext: Context) { +class FileDatabaseHistoryAction(applicationContext: Context) { private val databaseFileHistoryDao = AppDatabase .getDatabase(applicationContext) - .databaseFileHistoryDao() + .fileDatabaseHistoryDao() fun getFileDatabaseHistory(databaseUri: Uri, fileHistoryResultListener: (fileDatabaseHistoryResult: FileDatabaseHistoryEntity?) -> Unit) { - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString()) }, @@ -46,7 +44,7 @@ class FileDatabaseHistory(applicationContext: Context) { fun getKeyFileUriByDatabaseUri(databaseUri: Uri, keyFileUriResultListener: (Uri?) -> Unit) { - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString()) }, @@ -61,7 +59,7 @@ class FileDatabaseHistory(applicationContext: Context) { } fun getAllFileDatabaseHistories(fileHistoryResultListener: (fileDatabaseHistoryResult: List?) -> Unit) { - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { databaseFileHistoryDao.getAll() }, @@ -81,7 +79,7 @@ class FileDatabaseHistory(applicationContext: Context) { } fun addOrUpdateFileDatabaseHistory(fileDatabaseHistory: FileDatabaseHistoryEntity, unmodifiedAlias: Boolean = false) { - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { val fileDatabaseHistoryRetrieve = databaseFileHistoryDao.getByDatabaseUri(fileDatabaseHistory.databaseUri) @@ -100,7 +98,7 @@ class FileDatabaseHistory(applicationContext: Context) { fun deleteFileDatabaseHistory(fileDatabaseHistory: FileDatabaseHistoryEntity, fileHistoryDeletedResult: (FileDatabaseHistoryEntity?) -> Unit) { - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { databaseFileHistoryDao.delete(fileDatabaseHistory) }, @@ -114,39 +112,20 @@ class FileDatabaseHistory(applicationContext: Context) { } fun deleteAllKeyFiles() { - // TODO replace for unsupported query databaseFileHistoryDao.deleteAllKeyFiles() - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { - databaseFileHistoryDao - .deleteAllKeyFiles(SimpleSQLiteQuery("REPLACE INTO file_database_history(keyfile_uri) VALUES(null)")) + databaseFileHistoryDao.deleteAllKeyFiles() } ).execute() } fun deleteAll() { - ActionFileHistoryAsyncTask( + ActionDatabaseAsyncTask( { databaseFileHistoryDao.deleteAll() } ).execute() } - /** - * Private class to invoke each method in a separate thread - */ - private class ActionFileHistoryAsyncTask( - private val action: () -> T , - private val afterActionFileHistoryListener: ((fileHistoryResult: T?) -> Unit)? = null - ) : AsyncTask() { - - override fun doInBackground(vararg args: Void?): T? { - return action.invoke() - } - - override fun onPostExecute(result: T?) { - afterActionFileHistoryListener?.invoke(result) - } - } - - companion object : SingletonHolderParameter(::FileDatabaseHistory) + companion object : SingletonHolderParameter(::FileDatabaseHistoryAction) } diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryDao.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryDao.kt index aa51ad5f0..62dc2642c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryDao.kt +++ b/app/src/main/java/com/kunzisoft/keepass/app/database/FileDatabaseHistoryDao.kt @@ -1,6 +1,5 @@ package com.kunzisoft.keepass.app.database -import androidx.sqlite.db.SupportSQLiteQuery import androidx.room.* @Dao @@ -20,12 +19,8 @@ interface FileDatabaseHistoryDao { @Delete fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int - /* TODO Replace (Insert not yet supported) @Query("REPLACE INTO file_database_history(keyfile_uri) VALUES(null)") fun deleteAllKeyFiles() - */ - @RawQuery - fun deleteAllKeyFiles(query: SupportSQLiteQuery): Boolean @Query("DELETE FROM file_database_history") fun deleteAll() diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt new file mode 100644 index 000000000..f7c7970b6 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt @@ -0,0 +1,322 @@ +package com.kunzisoft.keepass.biometric + +import android.net.Uri +import android.os.Build +import android.util.Log +import android.view.Menu +import android.view.MenuInflater +import android.widget.CompoundButton +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.fragment.app.FragmentActivity +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.app.database.CipherDatabaseAction +import com.kunzisoft.keepass.app.database.CipherDatabaseEntity +import com.kunzisoft.keepass.settings.PreferencesUtil +import com.kunzisoft.keepass.view.AdvancedUnlockInfoView + +@RequiresApi(api = Build.VERSION_CODES.M) +class AdvancedUnlockedManager(var context: FragmentActivity, + var databaseFileUri: Uri, + var advancedUnlockInfoView: AdvancedUnlockInfoView?, + var checkboxPasswordView: CompoundButton?, + var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null, + var passwordView: TextView?, + var loadDatabase: (password: String?) -> Unit) + : BiometricUnlockDatabaseHelper.BiometricUnlockCallback { + + private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null + private var biometricMode: Mode = Mode.NOT_CONFIGURED + + private var isBiometricPromptAutoOpenEnable = PreferencesUtil.isBiometricPromptAutoOpenEnable(context) + + private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext) + + // fingerprint related code here + fun initBiometric() { + + // Check if fingerprint well init (be called the first time the fingerprint is configured + // and the activity still active) + if (biometricUnlockDatabaseHelper == null || !biometricUnlockDatabaseHelper!!.isFingerprintInitialized) { + + biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context, this) + // callback for fingerprint findings + biometricUnlockDatabaseHelper?.setAuthenticationCallback(biometricCallback) + } + + // Add a check listener to change fingerprint mode + checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked -> + + checkBiometricAvailability() + + // Add old listener to enable the button, only be call here because of onCheckedChange bug + onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked) + } + + checkBiometricAvailability() + } + + @Synchronized + fun checkBiometricAvailability() { + + // fingerprint not supported (by API level or hardware) so keep option hidden + // or manually disable + val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate() + + if (!PreferencesUtil.isBiometricUnlockEnable(context) + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { + + toggleMode(Mode.UNAVAILABLE) + + } else { + + // fingerprint is available but not configured, show icon but in disabled state with some information + if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { + + toggleMode(Mode.NOT_CONFIGURED) + + } else { + if (checkboxPasswordView?.isChecked == true) { + // listen for encryption + toggleMode(Mode.STORE) + } else { + cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { + + // fingerprint available but no stored password found yet for this DB so show info don't listen + toggleMode( if (it) { + // listen for decryption + Mode.OPEN + } else { + // wait for typing + Mode.WAIT_CREDENTIAL + }) + } + } + } + } + } + + private fun toggleMode(newBiometricMode: Mode) { + if (newBiometricMode != biometricMode) { + biometricMode = newBiometricMode + initBiometricMode() + } + } + + private val biometricCallback = object : BiometricPrompt.AuthenticationCallback () { + + override fun onAuthenticationError( + errorCode: Int, + errString: CharSequence) { + Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString") + setAdvancedUnlockedMessageView(errString.toString()) + } + + override fun onAuthenticationFailed() { + Log.e(TAG, "Biometric authentication failed, biometric not recognized") + setAdvancedUnlockedMessageView(R.string.fingerprint_not_recognized) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + when (biometricMode) { + Mode.UNAVAILABLE -> {} + Mode.PAUSE -> {} + Mode.NOT_CONFIGURED -> {} + Mode.WAIT_CREDENTIAL -> {} + Mode.STORE -> { + // newly store the entered password in encrypted way + biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString()) + } + Mode.OPEN -> { + // retrieve the encrypted value from preferences + cipherDatabaseAction.getCipherDatabase(databaseFileUri) { + it?.encryptedValue?.let { value -> + biometricUnlockDatabaseHelper?.decryptData(value) + } + } + } + } + } + } + + private fun initNotAvailable() { + showFingerPrintViews(false) + + advancedUnlockInfoView?.setIconViewClickListener(null) + } + + private fun initPause() { + advancedUnlockInfoView?.setIconViewClickListener(null) + } + + private fun initNotConfigured() { + showFingerPrintViews(true) + setAdvancedUnlockedTitleView(R.string.configure_biometric) + setAdvancedUnlockedMessageView("") + + advancedUnlockInfoView?.setIconViewClickListener(null) + } + + private fun initWaitData() { + showFingerPrintViews(true) + setAdvancedUnlockedTitleView(R.string.no_credentials_stored) + setAdvancedUnlockedMessageView("") + + advancedUnlockInfoView?.setIconViewClickListener(null) + } + + private fun initEncryptData() { + showFingerPrintViews(true) + setAdvancedUnlockedTitleView(R.string.open_biometric_prompt_store_credential) + setAdvancedUnlockedMessageView("") + + biometricUnlockDatabaseHelper?.initEncryptData { biometricPrompt, cryptoObject, promptInfo -> + + cryptoObject?.let { crypto -> + // Set listener to open the biometric dialog and save credential + advancedUnlockInfoView?.setIconViewClickListener { _ -> + context.runOnUiThread { + biometricPrompt?.authenticate(promptInfo, crypto) + } + } + } + + } + } + + private fun initDecryptData() { + showFingerPrintViews(true) + setAdvancedUnlockedTitleView(R.string.open_biometric_prompt_unlock_database) + setAdvancedUnlockedMessageView("") + + if (biometricUnlockDatabaseHelper != null) { + cipherDatabaseAction.getCipherDatabase(databaseFileUri) { + + it?.specParameters?.let { specs -> + biometricUnlockDatabaseHelper?.initDecryptData(specs) { biometricPrompt, cryptoObject, promptInfo -> + + cryptoObject?.let { crypto -> + // Set listener to open the biometric dialog and check credential + advancedUnlockInfoView?.setIconViewClickListener { _ -> + context.runOnUiThread { + biometricPrompt?.authenticate(promptInfo, crypto) + } + } + + // Auto open the biometric prompt + if (isBiometricPromptAutoOpenEnable) { + isBiometricPromptAutoOpenEnable = false + context.runOnUiThread { + biometricPrompt?.authenticate(promptInfo, crypto) + } + } + } + + } + } + } + } + } + + @Synchronized + fun initBiometricMode() { + when (biometricMode) { + Mode.UNAVAILABLE -> initNotAvailable() + Mode.PAUSE -> initPause() + Mode.NOT_CONFIGURED -> initNotConfigured() + Mode.WAIT_CREDENTIAL -> initWaitData() + Mode.STORE -> initEncryptData() + Mode.OPEN -> initDecryptData() + } + // Show fingerprint key deletion + context.invalidateOptionsMenu() + } + + fun pause() { + biometricMode = Mode.PAUSE + initBiometricMode() + } + + fun destroy() { + // Restore the checked listener + checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener) + + biometricMode = Mode.UNAVAILABLE + initBiometricMode() + biometricUnlockDatabaseHelper = null + } + + fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) { + cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { + if ((biometricMode != Mode.UNAVAILABLE + && biometricMode != Mode.NOT_CONFIGURED) && it) + menuInflater.inflate(R.menu.advanced_unlock, menu) + } + } + + fun deleteEntryKey() { + biometricUnlockDatabaseHelper?.deleteEntryKey() + cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri) + biometricMode = Mode.NOT_CONFIGURED + checkBiometricAvailability() + } + + override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) { + cipherDatabaseAction.addOrUpdateCipherDatabase(CipherDatabaseEntity( + databaseFileUri.toString(), + encryptedValue, + ivSpec + )) { + // Only for callback + loadDatabase.invoke(null) + setAdvancedUnlockedMessageView(R.string.encrypted_value_stored) + } + } + + override fun handleDecryptedResult(decryptedValue: String) { + // Load database directly with password retrieve + loadDatabase.invoke(decryptedValue) + } + + override fun onInvalidKeyException(e: Exception) { + setAdvancedUnlockedMessageView(R.string.biometric_invalid_key) + } + + override fun onBiometricException(e: Exception) { + setAdvancedUnlockedMessageView(e.localizedMessage) + } + + private fun showFingerPrintViews(show: Boolean) { + context.runOnUiThread { advancedUnlockInfoView?.hide = !show } + } + + private fun setAdvancedUnlockedTitleView(textId: Int) { + context.runOnUiThread { + advancedUnlockInfoView?.setTitle(textId) + } + } + + private fun setAdvancedUnlockedMessageView(textId: Int) { + context.runOnUiThread { + advancedUnlockInfoView?.setMessage(textId) + } + } + + private fun setAdvancedUnlockedMessageView(text: CharSequence) { + context.runOnUiThread { + advancedUnlockInfoView?.message = text + } + } + + enum class Mode { + UNAVAILABLE, PAUSE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN + } + + companion object { + + private val TAG = AdvancedUnlockedManager::class.java.name + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt new file mode 100644 index 000000000..c90b83f9b --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt @@ -0,0 +1,306 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.biometric + +import android.app.KeyguardManager +import android.content.Context +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyPermanentlyInvalidatedException +import android.security.keystore.KeyProperties +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.fragment.app.FragmentActivity +import com.kunzisoft.keepass.R +import java.security.KeyStore +import java.security.UnrecoverableKeyException +import java.util.concurrent.Executors +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.IvParameterSpec + +@RequiresApi(api = Build.VERSION_CODES.M) +class BiometricUnlockDatabaseHelper(private val context: FragmentActivity, + private val biometricUnlockCallback: BiometricUnlockCallback?) { + + private var biometricPrompt: BiometricPrompt? = null + + private var keyStore: KeyStore? = null + private var keyGenerator: KeyGenerator? = null + private var cipher: Cipher? = null + private var keyguardManager: KeyguardManager? = null + private var cryptoObject: BiometricPrompt.CryptoObject? = null + + private var isBiometricInit = false + private var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null + + private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder() + .setTitle(context.getString(R.string.biometric_prompt_store_credential_title)) + .setDescription(context.getString(R.string.biometric_prompt_store_credential_message)) + //.setDeviceCredentialAllowed(true) TODO device credential + .setNegativeButtonText(context.getString(android.R.string.cancel)) + .build() + + private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder() + .setTitle(context.getString(R.string.biometric_prompt_extract_credential_title)) + .setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) + //.setDeviceCredentialAllowed(true) + .setNegativeButtonText(context.getString(android.R.string.cancel)) + .build() + + val isFingerprintInitialized: Boolean + get() { + if (!isBiometricInit && biometricUnlockCallback != null) { + biometricUnlockCallback.onBiometricException(Exception("FingerPrint not initialized")) + } + return isBiometricInit + } + + init { + if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) { + // really not much to do when no fingerprint support found + isBiometricInit = false + } else { + this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + + try { + this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE) + this.keyGenerator = KeyGenerator.getInstance(BIOMETRIC_KEY_ALGORITHM, BIOMETRIC_KEYSTORE) + this.cipher = Cipher.getInstance( + BIOMETRIC_KEY_ALGORITHM + "/" + + BIOMETRIC_BLOCKS_MODES + "/" + + BIOMETRIC_ENCRYPTION_PADDING) + this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!) + isBiometricInit = true + } catch (e: Exception) { + Log.e(TAG, "Unable to initialize the keystore", e) + isBiometricInit = false + biometricUnlockCallback?.onBiometricException(e) + } + } + } + + private fun getSecretKey(): SecretKey? { + if (!isFingerprintInitialized) { + return null + } + try { + // Create new key if needed + keyStore?.let { keyStore -> + keyStore.load(null) + + try { + if (!keyStore.containsAlias(BIOMETRIC_KEYSTORE_KEY)) { + // Set the alias of the entry in Android KeyStore where the key will appear + // and the constrains (purposes) in the constructor of the Builder + keyGenerator?.init( + KeyGenParameterSpec.Builder( + BIOMETRIC_KEYSTORE_KEY, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) + // Require the user to authenticate with a fingerprint to authorize every use + // of the key + .setUserAuthenticationRequired(true) + .build()) + keyGenerator?.generateKey() + } + } catch (e: Exception) { + Log.e(TAG, "Unable to create a key in keystore", e) + biometricUnlockCallback?.onBiometricException(e) + } + + return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey? + } + } catch (e: Exception) { + Log.e(TAG, "Unable to retrieve the key in keystore", e) + biometricUnlockCallback?.onBiometricException(e) + } + return null + } + + fun initEncryptData(actionIfCypherInit + : (biometricPrompt: BiometricPrompt?, + cryptoObject: BiometricPrompt.CryptoObject?, + promptInfo: BiometricPrompt.PromptInfo)->Unit) { + if (!isFingerprintInitialized) { + return + } + try { + cipher?.init(Cipher.ENCRYPT_MODE, getSecretKey()) + + initBiometricPrompt() + actionIfCypherInit.invoke(biometricPrompt, cryptoObject, promptInfoStoreCredential) + + } catch (unrecoverableKeyException: UnrecoverableKeyException) { + Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException) + deleteEntryKey() + } catch (invalidKeyException: KeyPermanentlyInvalidatedException) { + Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException) + biometricUnlockCallback?.onInvalidKeyException(invalidKeyException) + } catch (e: Exception) { + Log.e(TAG, "Unable to initialize encrypt data", e) + biometricUnlockCallback?.onBiometricException(e) + } + + } + + fun encryptData(value: String) { + if (!isFingerprintInitialized) { + return + } + try { + val encrypted = cipher?.doFinal(value.toByteArray()) + val encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP) + + // passes updated iv spec on to callback so this can be stored for decryption + cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec -> + val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP) + biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue) + } + + } catch (e: Exception) { + Log.e(TAG, "Unable to encrypt data", e) + biometricUnlockCallback?.onBiometricException(e) + } + + } + + fun initDecryptData(ivSpecValue: String, actionIfCypherInit + : (biometricPrompt: BiometricPrompt?, + cryptoObject: BiometricPrompt.CryptoObject?, + promptInfo: BiometricPrompt.PromptInfo)->Unit) { + if (!isFingerprintInitialized) { + return + } + try { + // important to restore spec here that was used for decryption + val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP) + val spec = IvParameterSpec(iv) + cipher?.init(Cipher.DECRYPT_MODE, getSecretKey(), spec) + + initBiometricPrompt() + actionIfCypherInit.invoke(biometricPrompt, cryptoObject, promptInfoExtractCredential) + + } catch (unrecoverableKeyException: UnrecoverableKeyException) { + Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException) + deleteEntryKey() + } catch (invalidKeyException: KeyPermanentlyInvalidatedException) { + Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException) + biometricUnlockCallback?.onInvalidKeyException(invalidKeyException) + } catch (e: Exception) { + Log.e(TAG, "Unable to initialize decrypt data", e) + biometricUnlockCallback?.onBiometricException(e) + } + + } + + fun decryptData(encryptedValue: String) { + if (!isFingerprintInitialized) { + return + } + try { + // actual decryption here + val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP) + cipher?.doFinal(encrypted)?.let { decrypted -> + biometricUnlockCallback?.handleDecryptedResult(String(decrypted)) + } + } catch (badPaddingException: BadPaddingException) { + Log.e(TAG, "Unable to decrypt data", badPaddingException) + biometricUnlockCallback?.onInvalidKeyException(badPaddingException) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt data", e) + biometricUnlockCallback?.onBiometricException(e) + } + + } + + fun deleteEntryKey() { + try { + keyStore?.load(null) + keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY) + } catch (e: Exception) { + Log.e(TAG, "Unable to delete entry key in keystore", e) + biometricUnlockCallback?.onBiometricException(e) + } + } + + fun setAuthenticationCallback(authenticationCallback: BiometricPrompt.AuthenticationCallback) { + this.authenticationCallback = authenticationCallback + } + + @Synchronized + fun initBiometricPrompt() { + if (biometricPrompt == null) { + authenticationCallback?.let { + biometricPrompt = BiometricPrompt(context, Executors.newSingleThreadExecutor(), it) + } + } + } + + interface BiometricUnlockErrorCallback { + fun onInvalidKeyException(e: Exception) + fun onBiometricException(e: Exception) + } + + interface BiometricUnlockCallback : BiometricUnlockErrorCallback { + fun handleEncryptedResult(encryptedValue: String, ivSpec: String) + fun handleDecryptedResult(decryptedValue: String) + } + + companion object { + + private val TAG = BiometricUnlockDatabaseHelper::class.java.name + + private const val BIOMETRIC_KEYSTORE = "AndroidKeyStore" + private const val BIOMETRIC_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key" + private const val BIOMETRIC_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES + private const val BIOMETRIC_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC + private const val BIOMETRIC_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 + + /** + * Remove entry key in keystore + */ + fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity, + biometricUnlockCallback: BiometricUnlockErrorCallback) { + val fingerPrintHelper = BiometricUnlockDatabaseHelper(context, object : BiometricUnlockCallback { + + override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {} + + override fun handleDecryptedResult(decryptedValue: String) {} + + override fun onInvalidKeyException(e: Exception) { + biometricUnlockCallback.onInvalidKeyException(e) + } + + override fun onBiometricException(e: Exception) { + biometricUnlockCallback.onBiometricException(e) + } + }) + fingerPrintHelper.deleteEntryKey() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintAnimatedVector.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt similarity index 79% rename from app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintAnimatedVector.kt rename to app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt index b29741992..835d57998 100644 --- a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintAnimatedVector.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.fingerprint +package com.kunzisoft.keepass.biometric import android.content.Context import android.graphics.drawable.Animatable2 @@ -39,19 +39,23 @@ class FingerPrintAnimatedVector(context: Context, imageView: ImageView) { imageView.setImageDrawable(scanFingerprint) } + private var animationCallback = object : Animatable2.AnimationCallback() { + override fun onAnimationEnd(drawable: Drawable) { + if (!scanFingerprint.isRunning) + scanFingerprint.start() + } + } + fun startScan() { - scanFingerprint.registerAnimationCallback(object : Animatable2.AnimationCallback() { - override fun onAnimationEnd(drawable: Drawable) { - if (!scanFingerprint.isRunning) - scanFingerprint.start() - } - }) + scanFingerprint.registerAnimationCallback(animationCallback) if (!scanFingerprint.isRunning) scanFingerprint.start() } fun stopScan() { + scanFingerprint.unregisterAnimationCallback(animationCallback) + if (scanFingerprint.isRunning) scanFingerprint.stop() } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt index 5fdfbdfc9..1b31b3274 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt @@ -27,7 +27,7 @@ import android.util.Log import com.kunzisoft.keepass.R import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.exception.* -import com.kunzisoft.keepass.app.database.FileDatabaseHistory +import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import java.io.FileNotFoundException @@ -120,7 +120,7 @@ class LoadDatabaseRunnable(private val mWeakContext: WeakReference, keyFileUri = null } mWeakContext.get()?.let { - FileDatabaseHistory.getInstance(it).addOrUpdateDatabaseUri(uri, keyFileUri) + FileDatabaseHistoryAction.getInstance(it).addOrUpdateDatabaseUri(uri, keyFileUri) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintHelper.kt b/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintHelper.kt deleted file mode 100644 index 4bfe9b371..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintHelper.kt +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2019 Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX 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. - * - * KeePass DX 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 KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.fingerprint - -import android.annotation.SuppressLint -import android.app.KeyguardManager -import android.content.Context -import android.hardware.fingerprint.FingerprintManager -import android.os.Build -import android.os.CancellationSignal -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyPermanentlyInvalidatedException -import android.security.keystore.KeyProperties -import androidx.annotation.RequiresApi -import android.util.Base64 -import android.util.Log - -import java.io.IOException -import java.security.KeyStore -import java.security.KeyStoreException -import java.security.NoSuchAlgorithmException -import java.security.UnrecoverableKeyException -import java.security.cert.CertificateException - -import javax.crypto.BadPaddingException -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.SecretKey -import javax.crypto.spec.IvParameterSpec - -@RequiresApi(api = Build.VERSION_CODES.M) -class FingerPrintHelper(context: Context, private val fingerPrintCallback: FingerPrintCallback?) { - - private val fingerprintManager: FingerprintManager? = - context.getSystemService(FingerprintManager::class.java) - private var keyStore: KeyStore? = null - private var keyGenerator: KeyGenerator? = null - private var cipher: Cipher? = null - private var keyguardManager: KeyguardManager? = null - private var cryptoObject: FingerprintManager.CryptoObject? = null - - private var initOk = false - private var cancellationSignal: CancellationSignal? = null - private var authenticationCallback: FingerprintManager.AuthenticationCallback? = null - - val isFingerprintInitialized: Boolean - get() = isFingerprintInitialized(true) - - fun setAuthenticationCallback(authenticationCallback: FingerprintManager.AuthenticationCallback) { - this.authenticationCallback = authenticationCallback - } - - @Synchronized - fun startListening() { - // starts listening for fingerprints with the initialised crypto object - cancellationSignal = CancellationSignal() - fingerprintManager?.authenticate( - cryptoObject, - cancellationSignal, - 0 /* flags */, - authenticationCallback!!, null) - } - - @Synchronized - fun stopListening() { - if (!isFingerprintInitialized(false)) { - return - } - if (cancellationSignal != null) { - cancellationSignal?.cancel() - cancellationSignal = null - } - } - - init { - - if (!isFingerprintSupported(fingerprintManager)) { - // really not much to do when no fingerprint support found - initOk = false - } else { - this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - - if (hasEnrolledFingerprints()) { - try { - this.keyStore = KeyStore.getInstance("AndroidKeyStore") - this.keyGenerator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, - "AndroidKeyStore") - this.cipher = Cipher.getInstance( - KeyProperties.KEY_ALGORITHM_AES + "/" - + KeyProperties.BLOCK_MODE_CBC + "/" - + KeyProperties.ENCRYPTION_PADDING_PKCS7) - this.cryptoObject = FingerprintManager.CryptoObject(cipher!!) - initOk = true - } catch (e: Exception) { - Log.e(TAG, "Unable to initialize the keystore", e) - initOk = false - fingerPrintCallback?.onFingerPrintException(e) - } - - } - } - } - - private fun isFingerprintInitialized(throwException: Boolean): Boolean { - val isFingerprintInit = hasEnrolledFingerprints() && initOk - if (!isFingerprintInit && fingerPrintCallback != null) { - if (throwException) - fingerPrintCallback.onFingerPrintException(Exception("FingerPrint not initialized")) - } - return isFingerprintInit - } - - fun initEncryptData() { - if (!isFingerprintInitialized) { - return - } - try { - stopListening() - - createNewKeyIfNeeded(false) // no need to keep deleting existing keys - keyStore?.load(null) - val key = keyStore?.getKey(FINGERPRINT_KEYSTORE_KEY, null) as SecretKey - cipher?.init(Cipher.ENCRYPT_MODE, key) - - startListening() - } catch (unrecoverableKeyException: UnrecoverableKeyException) { - Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException) - deleteEntryKey() - } catch (invalidKeyException: KeyPermanentlyInvalidatedException) { - Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException) - fingerPrintCallback?.onInvalidKeyException(invalidKeyException) - } catch (e: Exception) { - Log.e(TAG, "Unable to initialize encrypt data", e) - fingerPrintCallback?.onFingerPrintException(e) - } - - } - - fun encryptData(value: String) { - if (!isFingerprintInitialized) { - return - } - try { - // actual do encryption here - val encrypted = cipher?.doFinal(value.toByteArray()) - val encryptedValue = Base64.encodeToString(encrypted, Base64.NO_WRAP) - - // passes updated iv spec on to callback so this can be stored for decryption - cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec -> - val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP) - fingerPrintCallback?.handleEncryptedResult(encryptedValue, ivSpecValue) - } - - } catch (e: Exception) { - Log.e(TAG, "Unable to encrypt data", e) - fingerPrintCallback?.onFingerPrintException(e) - } - - } - - fun initDecryptData(ivSpecValue: String) { - if (!isFingerprintInitialized) { - return - } - try { - stopListening() - - createNewKeyIfNeeded(false) - keyStore?.load(null) - val key = keyStore?.getKey(FINGERPRINT_KEYSTORE_KEY, null) as SecretKey - - // important to restore spec here that was used for decryption - val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP) - val spec = IvParameterSpec(iv) - cipher?.init(Cipher.DECRYPT_MODE, key, spec) - - startListening() - } catch (unrecoverableKeyException: UnrecoverableKeyException) { - Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException) - deleteEntryKey() - } catch (invalidKeyException: KeyPermanentlyInvalidatedException) { - Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException) - fingerPrintCallback?.onInvalidKeyException(invalidKeyException) - } catch (e: Exception) { - Log.e(TAG, "Unable to initialize decrypt data", e) - fingerPrintCallback?.onFingerPrintException(e) - } - - } - - fun decryptData(encryptedValue: String) { - if (!isFingerprintInitialized) { - return - } - try { - // actual decryption here - val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP) - cipher?.doFinal(encrypted)?.let { decrypted -> - //final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */); - fingerPrintCallback?.handleDecryptedResult(String(decrypted)) - } - } catch (badPaddingException: BadPaddingException) { - Log.e(TAG, "Unable to decrypt data", badPaddingException) - fingerPrintCallback?.onInvalidKeyException(badPaddingException) - } catch (e: Exception) { - Log.e(TAG, "Unable to decrypt data", e) - fingerPrintCallback?.onFingerPrintException(e) - } - - } - - @SuppressLint("NewApi") - private fun createNewKeyIfNeeded(allowDeleteExisting: Boolean) { - if (!isFingerprintInitialized) { - return - } - try { - keyStore?.load(null) - if (allowDeleteExisting && keyStore != null && keyStore!!.containsAlias(FINGERPRINT_KEYSTORE_KEY)) { - keyStore?.deleteEntry(FINGERPRINT_KEYSTORE_KEY) - } - - // Create new key if needed - if (keyStore != null && !keyStore!!.containsAlias(FINGERPRINT_KEYSTORE_KEY)) { - // Set the alias of the entry in Android KeyStore where the key will appear - // and the constrains (purposes) in the constructor of the Builder - keyGenerator?.init( - KeyGenParameterSpec.Builder( - FINGERPRINT_KEYSTORE_KEY, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_CBC) - // Require the user to authenticate with a fingerprint to authorize every use - // of the key - .setUserAuthenticationRequired(true) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) - .build()) - keyGenerator?.generateKey() - } - } catch (e: Exception) { - Log.e(TAG, "Unable to create a key in keystore", e) - fingerPrintCallback?.onFingerPrintException(e) - } - - } - - fun deleteEntryKey() { - try { - keyStore?.load(null) - keyStore?.deleteEntry(FINGERPRINT_KEYSTORE_KEY) - } catch (e: KeyStoreException) { - Log.e(TAG, "Unable to delete entry key in keystore", e) - fingerPrintCallback?.onFingerPrintException(e) - } catch (e: CertificateException) { - Log.e(TAG, "Unable to delete entry key in keystore", e) - fingerPrintCallback?.onFingerPrintException(e) - } catch (e: NoSuchAlgorithmException) { - Log.e(TAG, "Unable to delete entry key in keystore", e) - fingerPrintCallback?.onFingerPrintException(e) - } catch (e: IOException) { - Log.e(TAG, "Unable to delete entry key in keystore", e) - fingerPrintCallback?.onFingerPrintException(e) - } catch (e: NullPointerException) { - Log.e(TAG, "Unable to delete entry key in keystore", e) - fingerPrintCallback?.onFingerPrintException(e) - } - - } - - @SuppressLint("NewApi") - fun hasEnrolledFingerprints(): Boolean { - // fingerprint hardware supported and api level OK - return (isFingerprintSupported(fingerprintManager) - // fingerprints enrolled - && fingerprintManager != null && fingerprintManager.hasEnrolledFingerprints() - // and lockscreen configured - && keyguardManager != null && keyguardManager!!.isKeyguardSecure) - } - - interface FingerPrintErrorCallback { - fun onInvalidKeyException(e: Exception) - fun onFingerPrintException(e: Exception) - } - - interface FingerPrintCallback : FingerPrintErrorCallback { - fun handleEncryptedResult(value: String, ivSpec: String) - fun handleDecryptedResult(value: String) - } - - enum class Mode { - NOT_CONFIGURED_MODE, WAITING_PASSWORD_MODE, STORE_MODE, OPEN_MODE - } - - companion object { - - private val TAG = FingerPrintHelper::class.java.name - - private const val FINGERPRINT_KEYSTORE_KEY = "com.kunzisoft.keepass.fingerprint.key" - - fun isFingerprintSupported(fingerprintManager: FingerprintManager?): Boolean { - return fingerprintManager != null && fingerprintManager.isHardwareDetected - } - - /** - * Remove entry key in keystore - */ - fun deleteEntryKeyInKeystoreForFingerprints(context: Context, - fingerPrintCallback: FingerPrintErrorCallback) { - val fingerPrintHelper = FingerPrintHelper( context, object : FingerPrintCallback { - - override fun handleEncryptedResult(value: String, ivSpec: String) {} - - override fun handleDecryptedResult(value: String) {} - - override fun onInvalidKeyException(e: Exception) { - fingerPrintCallback.onInvalidKeyException(e) - } - - override fun onFingerPrintException(e: Exception) { - fingerPrintCallback.onFingerPrintException(e) - } - }) - fingerPrintHelper.deleteEntryKey() - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintViewsManager.kt b/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintViewsManager.kt deleted file mode 100644 index a2f8c90c0..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/fingerprint/FingerPrintViewsManager.kt +++ /dev/null @@ -1,343 +0,0 @@ -package com.kunzisoft.keepass.fingerprint - -import android.content.Context -import android.content.SharedPreferences -import android.hardware.fingerprint.FingerprintManager -import android.net.Uri -import android.os.Build -import android.os.Handler -import androidx.annotation.RequiresApi -import androidx.appcompat.app.AppCompatActivity -import android.util.Log -import android.view.Menu -import android.view.MenuInflater -import android.widget.CompoundButton -import android.widget.TextView -import android.widget.Toast -import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.settings.PreferencesUtil -import com.kunzisoft.keepass.view.FingerPrintInfoView - -@RequiresApi(api = Build.VERSION_CODES.M) -class FingerPrintViewsManager(var context: AppCompatActivity, - var databaseFileUri: Uri?, - var fingerPrintInfoView: FingerPrintInfoView?, - var checkboxPasswordView: CompoundButton?, - var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null, - var passwordView: TextView?, - var loadDatabase: (password: String?) -> Unit) - : FingerPrintHelper.FingerPrintCallback { - - private var fingerPrintHelper: FingerPrintHelper? = null - private var fingerprintMustBeConfigured = true - private var fingerPrintMode: FingerPrintHelper.Mode = FingerPrintHelper.Mode.NOT_CONFIGURED_MODE - - private var checkboxListenerHandler = Handler() - private var checkboxListenerRunnable: Runnable? = null - - // makes it possible to store passwords per database - private val preferenceKeyValue: String - get() = PREF_KEY_VALUE_PREFIX + (databaseFileUri?.path ?: "") - - private val preferenceKeyIvSpec: String - get() = PREF_KEY_IV_PREFIX + (databaseFileUri?.path ?: "") - - private var prefsNoBackup: SharedPreferences? = null - - init { - prefsNoBackup = getNoBackupSharedPreferences(context) - } - - // fingerprint related code here - fun initFingerprint() { - - // Check if fingerprint well init (be called the first time the fingerprint is configured - // and the activity still active) - if (fingerPrintHelper == null || !fingerPrintHelper!!.isFingerprintInitialized) { - - fingerPrintMode = FingerPrintHelper.Mode.NOT_CONFIGURED_MODE - - showFingerPrintViews(true) - // Start the animation - fingerPrintInfoView?.startFingerPrintAnimation() - - // Add a check listener to change fingerprint mode - checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked -> - - // New runnable to each change - checkboxListenerHandler.removeCallbacks(checkboxListenerRunnable) - checkboxListenerRunnable = Runnable { - if (!fingerprintMustBeConfigured) { - // encrypt or decrypt mode based on how much input or not - if (checked) { - toggleFingerprintMode(FingerPrintHelper.Mode.STORE_MODE) - } else { - if (prefsNoBackup?.contains(preferenceKeyValue) == true) { - toggleFingerprintMode(FingerPrintHelper.Mode.OPEN_MODE) - } else { - // This happens when no fingerprints are registered. - toggleFingerprintMode(FingerPrintHelper.Mode.WAITING_PASSWORD_MODE) - } - } - } - } - checkboxListenerHandler.post(checkboxListenerRunnable) - - // Add old listener to enable the button, only be call here because of onCheckedChange bug - onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked) - } - - fingerPrintHelper = FingerPrintHelper(context, this) - // callback for fingerprint findings - fingerPrintHelper?.setAuthenticationCallback(authenticationCallback) - } - } - - private val authenticationCallback = object : FingerprintManager.AuthenticationCallback() { - override fun onAuthenticationError( - errorCode: Int, - errString: CharSequence) { - when (errorCode) { - 5 -> Log.i(TAG, "Fingerprint authentication error. Code : $errorCode Error : $errString") - else -> { - Log.e(TAG, "Fingerprint authentication error. Code : $errorCode Error : $errString") - setFingerPrintView(errString.toString(), true) - } - } - } - - override fun onAuthenticationHelp( - helpCode: Int, - helpString: CharSequence) { - Log.w(TAG, "Fingerprint authentication help. Code : $helpCode Help : $helpString") - showError(helpString) - setFingerPrintView(helpString.toString(), true) - fingerPrintInfoView?.text = helpString.toString() - } - - override fun onAuthenticationFailed() { - Log.e(TAG, "Fingerprint authentication failed, fingerprint not recognized") - showError(R.string.fingerprint_not_recognized) - } - - override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) { - when (fingerPrintMode) { - FingerPrintHelper.Mode.STORE_MODE -> { - // newly store the entered password in encrypted way - fingerPrintHelper?.encryptData(passwordView?.text.toString()) - } - FingerPrintHelper.Mode.OPEN_MODE -> { - // retrieve the encrypted value from preferences - prefsNoBackup?.getString(preferenceKeyValue, null)?.let { - fingerPrintHelper?.decryptData(it) - } - } - FingerPrintHelper.Mode.NOT_CONFIGURED_MODE -> {} - FingerPrintHelper.Mode.WAITING_PASSWORD_MODE -> {} - } - } - } - - private fun initEncryptData() { - setFingerPrintView(R.string.store_with_fingerprint) - fingerPrintMode = FingerPrintHelper.Mode.STORE_MODE - fingerPrintHelper?.initEncryptData() - } - - private fun initDecryptData() { - setFingerPrintView(R.string.scanning_fingerprint) - fingerPrintMode = FingerPrintHelper.Mode.OPEN_MODE - if (fingerPrintHelper != null) { - prefsNoBackup?.getString(preferenceKeyIvSpec, null)?.let { - fingerPrintHelper?.initDecryptData(it) - } - } - } - - private fun initWaitData() { - setFingerPrintView(R.string.no_password_stored, true) - fingerPrintMode = FingerPrintHelper.Mode.WAITING_PASSWORD_MODE - } - - @Synchronized - private fun toggleFingerprintMode(newMode: FingerPrintHelper.Mode) { - when (newMode) { - FingerPrintHelper.Mode.WAITING_PASSWORD_MODE -> setFingerPrintView(R.string.no_password_stored, true) - FingerPrintHelper.Mode.STORE_MODE -> setFingerPrintView(R.string.store_with_fingerprint) - FingerPrintHelper.Mode.OPEN_MODE -> setFingerPrintView(R.string.scanning_fingerprint) - else -> {} - } - if (newMode != fingerPrintMode) { - fingerPrintMode = newMode - reInitWithFingerprintMode() - } - } - - @Synchronized - fun reInitWithFingerprintMode() { - when (fingerPrintMode) { - FingerPrintHelper.Mode.STORE_MODE -> initEncryptData() - FingerPrintHelper.Mode.WAITING_PASSWORD_MODE -> initWaitData() - FingerPrintHelper.Mode.OPEN_MODE -> initDecryptData() - else -> {} - } - // Show fingerprint key deletion - context.invalidateOptionsMenu() - } - - fun stopListening() { - // stop listening when we go in background - fingerPrintInfoView?.stopFingerPrintAnimation() - fingerPrintMode = FingerPrintHelper.Mode.NOT_CONFIGURED_MODE - fingerPrintHelper?.stopListening() - } - - fun destroy() { - // Restore the checked listener - checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener) - - stopListening() - fingerPrintHelper = null - - showFingerPrintViews(false) - } - - fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) { - if (!fingerprintMustBeConfigured && prefsNoBackup?.contains(preferenceKeyValue) == true) - menuInflater.inflate(R.menu.fingerprint, menu) - } - - private fun showFingerPrintViews(show: Boolean) { - context.runOnUiThread { fingerPrintInfoView?.hide = !show } - } - - private fun setFingerPrintView(textId: Int, lock: Boolean = false) { - context.runOnUiThread { - fingerPrintInfoView?.setText(textId, lock) - } - } - - private fun setFingerPrintView(text: CharSequence, lock: Boolean = false) { - context.runOnUiThread { - fingerPrintInfoView?.setText(text, lock) - } - } - - @Synchronized - fun checkFingerprintAvailability() { - // fingerprint not supported (by API level or hardware) so keep option hidden - // or manually disable - if (!PreferencesUtil.isFingerprintEnable(context) - || !FingerPrintHelper.isFingerprintSupported(context.getSystemService(FingerprintManager::class.java))) { - showFingerPrintViews(false) - } else { - // all is set here so we can confirm to user and start listening for fingerprints - // show explanations - fingerPrintInfoView?.setOnClickListener { _ -> - FingerPrintExplanationDialog().show(context.supportFragmentManager, "fingerprintDialog") - } - showFingerPrintViews(true) - - // fingerprint is available but not configured, show icon but in disabled state with some information - if (fingerPrintHelper?.hasEnrolledFingerprints() != true) { - // This happens when no fingerprints are registered. Listening won't start - setFingerPrintView(R.string.configure_fingerprint, true) - } else { - fingerprintMustBeConfigured = false - - // fingerprint available but no stored password found yet for this DB so show info don't listen - if (prefsNoBackup?.contains(preferenceKeyValue) != true) { - if (checkboxPasswordView?.isChecked == true) { - // listen for encryption - initEncryptData() - } else { - // wait for typing - initWaitData() - } - } else { - // listen for decryption - initDecryptData() - } - }// finally fingerprint available and configured so we can use it - } - - // Show fingerprint key deletion - context.invalidateOptionsMenu() - } - - private fun removePrefsNoBackupKey() { - prefsNoBackup?.edit() - ?.remove(preferenceKeyValue) - ?.remove(preferenceKeyIvSpec) - ?.apply() - } - - override fun handleEncryptedResult( - value: String, - ivSpec: String) { - prefsNoBackup?.edit() - ?.putString(preferenceKeyValue, value) - ?.putString(preferenceKeyIvSpec, ivSpec) - ?.apply() - loadDatabase.invoke(null) - setFingerPrintView(R.string.encrypted_value_stored) - } - - override fun handleDecryptedResult(value: String) { - // Load database directly with password retrieve - loadDatabase.invoke(value) - } - - override fun onInvalidKeyException(e: Exception) { - showError(context.getString(R.string.fingerprint_invalid_key)) - deleteEntryKey() - } - - override fun onFingerPrintException(e: Exception) { - // Don't show error here; - // showError(getString(R.string.fingerprint_error, e.getMessage())); - // Can be uninit in Activity and init in fragment - setFingerPrintView(e.localizedMessage, true) - } - - fun deleteEntryKey() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintHelper?.deleteEntryKey() - removePrefsNoBackupKey() - fingerPrintMode = FingerPrintHelper.Mode.NOT_CONFIGURED_MODE - checkFingerprintAvailability() - } - } - - private fun showError(messageId: Int) { - showError(context.getString(messageId)) - } - - private fun showError(message: CharSequence) { - context.runOnUiThread { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } // TODO Error - } - - companion object { - - private val TAG = FingerPrintViewsManager::class.java.name - - private const val PREF_KEY_VALUE_PREFIX = "valueFor_" // key is a combination of db file name and this prefix - private const val PREF_KEY_IV_PREFIX = "ivFor_" // key is a combination of db file name and this prefix - - private const val NO_BACKUP_PREFERENCE_FILE_NAME = "nobackup" - - fun getNoBackupSharedPreferences(context: Context): SharedPreferences { - return context.getSharedPreferences( - NO_BACKUP_PREFERENCE_FILE_NAME, - Context.MODE_PRIVATE) - } - - fun deleteAllValuesFromNoBackupPreferences(context: Context) { - val prefsNoBackup = getNoBackupSharedPreferences(context) - val sharedPreferencesEditor = prefsNoBackup.edit() - sharedPreferencesEditor.clear() - sharedPreferencesEditor.apply() - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MagikIMESettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MagikIMESettingsFragment.kt index e7be39e55..522711bae 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/MagikIMESettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/MagikIMESettingsFragment.kt @@ -28,6 +28,6 @@ class MagikIMESettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { // Load the preferences from an XML resource - setPreferencesFromResource(R.xml.keyboard_preferences, rootKey) + setPreferencesFromResource(R.xml.preferences_keyboard, rootKey) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt index f27a02243..244e5872f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/MainPreferenceFragment.kt @@ -31,7 +31,7 @@ class MainPreferenceFragment : PreferenceFragmentCompat() { private var mCallback: Callback? = null - override fun onAttach(context: Context?) { + override fun onAttach(context: Context) { super.onAttach(context) if (context is Callback) { @@ -45,7 +45,7 @@ class MainPreferenceFragment : PreferenceFragmentCompat() { setPreferencesFromResource(R.xml.preferences, rootKey) // add listeners for non-default actions - findPreference(getString(R.string.app_key)).apply { + findPreference(getString(R.string.settings_app_key)).apply { onPreferenceClickListener = Preference.OnPreferenceClickListener { mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.APPLICATION) false @@ -59,6 +59,13 @@ class MainPreferenceFragment : PreferenceFragmentCompat() { } } + findPreference(getString(R.string.settings_advanced_unlock_key)).apply { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.ADVANCED_UNLOCK) + false + } + } + findPreference(getString(R.string.settings_appearance_key)).apply { onPreferenceClickListener = Preference.OnPreferenceClickListener { mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.APPEARANCE) @@ -66,7 +73,7 @@ class MainPreferenceFragment : PreferenceFragmentCompat() { } } - findPreference(getString(R.string.database_main_menu_key)).apply { + findPreference(getString(R.string.settings_database_key)).apply { onPreferenceClickListener = Preference.OnPreferenceClickListener { mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE) false @@ -76,9 +83,11 @@ class MainPreferenceFragment : PreferenceFragmentCompat() { } } - findPreference(getString(R.string.database_change_master_key_key)).apply { + findPreference(getString(R.string.settings_database_change_credentials_key)).apply { onPreferenceClickListener = Preference.OnPreferenceClickListener { - AssignMasterKeyDialogFragment().show(fragmentManager, "passwordDialog") + fragmentManager?.let { fragmentManager -> + AssignMasterKeyDialogFragment().show(fragmentManager, "passwordDialog") + } false } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt index 873e30413..55a81739e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt @@ -22,7 +22,6 @@ package com.kunzisoft.keepass.settings import android.content.ActivityNotFoundException import android.content.Intent import android.content.res.Resources -import android.hardware.fingerprint.FingerprintManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -37,6 +36,7 @@ import androidx.preference.PreferenceFragmentCompat import android.util.Log import android.view.autofill.AutofillManager import android.widget.Toast +import androidx.biometric.BiometricManager import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.dialogs.KeyboardExplanationDialogFragment @@ -45,11 +45,11 @@ import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.stylish.Stylish -import com.kunzisoft.keepass.app.database.FileDatabaseHistory +import com.kunzisoft.keepass.app.database.CipherDatabaseAction +import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.education.Education -import com.kunzisoft.keepass.fingerprint.FingerPrintHelper -import com.kunzisoft.keepass.fingerprint.FingerPrintViewsManager +import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.settings.preferencedialogfragment.* @@ -65,7 +65,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen private var parallelismPref: Preference? = null enum class Screen { - APPLICATION, FORM_FILLING, DATABASE, APPEARANCE + APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, DATABASE, APPEARANCE } override fun onResume() { @@ -100,6 +100,9 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen Screen.FORM_FILLING -> { onCreateFormFillingPreference(rootKey) } + Screen.ADVANCED_UNLOCK -> { + onCreateAdvancesUnlockPreferences(rootKey) + } Screen.APPEARANCE -> { onCreateAppearancePreferences(rootKey) } @@ -110,7 +113,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen } private fun onCreateApplicationPreferences(rootKey: String?) { - setPreferencesFromResource(R.xml.application_preferences, rootKey) + setPreferencesFromResource(R.xml.preferences_application, rootKey) activity?.let { activity -> allowCopyPassword() @@ -118,7 +121,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen val keyFile = findPreference(getString(R.string.keyfile_key)) keyFile.setOnPreferenceChangeListener { _, newValue -> if (!(newValue as Boolean)) { - FileDatabaseHistory.getInstance(activity.applicationContext).deleteAllKeyFiles() + FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles() } true } @@ -126,67 +129,15 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen val recentHistory = findPreference(getString(R.string.recentfile_key)) recentHistory.setOnPreferenceChangeListener { _, newValue -> if (!(newValue as Boolean)) { - FileDatabaseHistory.getInstance(activity.applicationContext).deleteAll() + FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll() } true } - - val fingerprintEnablePreference = findPreference(getString(R.string.fingerprint_enable_key)) as SwitchPreference - // < M solve verifyError exception - var fingerprintSupported = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - fingerprintSupported = FingerPrintHelper.isFingerprintSupported( - activity.getSystemService(FingerprintManager::class.java)) - if (!fingerprintSupported) { - // False if under Marshmallow - fingerprintEnablePreference.isChecked = false - fingerprintEnablePreference.setOnPreferenceClickListener { preference -> - fragmentManager?.let { fragmentManager -> - (preference as SwitchPreference).isChecked = false - UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M) - .show(fragmentManager, "unavailableFeatureDialog") - } - false - } - } - - val deleteKeysFingerprints = findPreference(getString(R.string.fingerprint_delete_all_key)) - if (!fingerprintSupported) { - deleteKeysFingerprints.isEnabled = false - } else { - deleteKeysFingerprints.setOnPreferenceClickListener { - context?.let { context -> - AlertDialog.Builder(context) - .setMessage(resources.getString(R.string.fingerprint_delete_all_warning)) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(resources.getString(android.R.string.yes) - ) { _, _ -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - FingerPrintHelper.deleteEntryKeyInKeystoreForFingerprints( - context, - object : FingerPrintHelper.FingerPrintErrorCallback { - override fun onInvalidKeyException(e: Exception) {} - - override fun onFingerPrintException(e: Exception) { - Toast.makeText(context, - getString(R.string.fingerprint_error, e.localizedMessage), - Toast.LENGTH_SHORT).show() - } - }) - } - FingerPrintViewsManager.deleteAllValuesFromNoBackupPreferences(context) - } - .setNegativeButton(resources.getString(android.R.string.no)) - { _, _ -> }.show() - } - false - } - } } } private fun onCreateFormFillingPreference(rootKey: String?) { - setPreferencesFromResource(R.xml.form_filling_preferences, rootKey) + setPreferencesFromResource(R.xml.preferences_form_filling, rootKey) activity?.let { activity -> val autoFillEnablePreference = findPreference(getString(R.string.settings_autofill_enable_key)) as SwitchPreference @@ -264,8 +215,68 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen allowCopyPassword() } + private fun onCreateAdvancesUnlockPreferences(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey) + + activity?.let { activity -> + val biometricUnlockEnablePreference = findPreference(getString(R.string.biometric_unlock_enable_key)) as SwitchPreference + // < M solve verifyError exception + var biometricUnlockSupported = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val biometricCanAuthenticate = BiometricManager.from(activity).canAuthenticate() + biometricUnlockSupported = biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS + } + if (!biometricUnlockSupported) { + // False if under Marshmallow + biometricUnlockEnablePreference.isChecked = false + biometricUnlockEnablePreference.setOnPreferenceClickListener { preference -> + fragmentManager?.let { fragmentManager -> + (preference as SwitchPreference).isChecked = false + UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M) + .show(fragmentManager, "unavailableFeatureDialog") + } + false + } + } + + val deleteKeysFingerprints = findPreference(getString(R.string.biometric_delete_all_key_key)) + if (!biometricUnlockSupported) { + deleteKeysFingerprints.isEnabled = false + } else { + deleteKeysFingerprints.setOnPreferenceClickListener { + context?.let { context -> + AlertDialog.Builder(context) + .setMessage(resources.getString(R.string.biometric_delete_all_key_warning)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(resources.getString(android.R.string.yes) + ) { _, _ -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric( + activity, + object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback { + override fun onInvalidKeyException(e: Exception) {} + + override fun onBiometricException(e: Exception) { + Toast.makeText(context, + getString(R.string.biometric_scanning_error, e.localizedMessage), + Toast.LENGTH_SHORT).show() + } + }) + } + CipherDatabaseAction.getInstance(context.applicationContext).deleteAll() + } + .setNegativeButton(resources.getString(android.R.string.no)) + { _, _ -> }.show() + } + false + } + } + } + } + private fun onCreateAppearancePreferences(rootKey: String?) { - setPreferencesFromResource(R.xml.appearance_preferences, rootKey) + setPreferencesFromResource(R.xml.preferences_appearance, rootKey) activity?.let { activity -> val stylePreference = findPreference(getString(R.string.setting_style_key)) @@ -326,7 +337,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen } private fun onCreateDatabasePreference(rootKey: String?) { - setPreferencesFromResource(R.xml.database_preferences, rootKey) + setPreferencesFromResource(R.xml.preferences_database, rootKey) if (database.loaded) { @@ -523,8 +534,9 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen return when (key) { Screen.APPLICATION -> resources.getString(R.string.menu_app_settings) Screen.FORM_FILLING -> resources.getString(R.string.menu_form_filling_settings) - Screen.DATABASE -> resources.getString(R.string.menu_db_settings) - Screen.APPEARANCE -> resources.getString(R.string.appearance) + Screen.ADVANCED_UNLOCK -> resources.getString(R.string.menu_advanced_unlock_settings) + Screen.DATABASE -> resources.getString(R.string.menu_database_settings) + Screen.APPEARANCE -> resources.getString(R.string.menu_appearance_settings) } } } 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 1a6755d22..7465d7775 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt @@ -128,10 +128,16 @@ object PreferencesUtil { context.resources.getBoolean(R.bool.lock_database_back_root_default)) } - fun isFingerprintEnable(context: Context): Boolean { + fun isBiometricUnlockEnable(context: Context): Boolean { val prefs = PreferenceManager.getDefaultSharedPreferences(context) - return prefs.getBoolean(context.getString(R.string.fingerprint_enable_key), - context.resources.getBoolean(R.bool.fingerprint_enable_default)) + return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key), + context.resources.getBoolean(R.bool.biometric_unlock_enable_default)) + } + + fun isBiometricPromptAutoOpenEnable(context: Context): Boolean { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getBoolean(context.getString(R.string.biometric_auto_open_prompt_key), + context.resources.getBoolean(R.bool.biometric_auto_open_prompt_default)) } fun isFullFilePathEnable(context: Context): Boolean { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsAdvancedUnlockActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsAdvancedUnlockActivity.kt new file mode 100644 index 000000000..13177058d --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsAdvancedUnlockActivity.kt @@ -0,0 +1,17 @@ +package com.kunzisoft.keepass.settings + +import android.os.Bundle +import androidx.fragment.app.Fragment + + +class SettingsAdvancedUnlockActivity : SettingsActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + timeoutEnable = false + } + + override fun retrieveMainFragment(): Fragment { + return NestedSettingsFragment.newInstance(NestedSettingsFragment.Screen.ADVANCED_UNLOCK) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/tasks/ProgressTaskDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/tasks/ProgressTaskDialogFragment.kt index 14e1cfdab..32eee39f5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/tasks/ProgressTaskDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/tasks/ProgressTaskDialogFragment.kt @@ -77,7 +77,7 @@ open class ProgressTaskDialogFragment : DialogFragment(), ProgressTaskUpdater { return super.onCreateDialog(savedInstanceState) } - override fun onDismiss(dialog: DialogInterface?) { + override fun onDismiss(dialog: DialogInterface) { activity?.unlockScreenOrientation() super.onDismiss(dialog) } diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/FileDatabaseInfo.kt b/app/src/main/java/com/kunzisoft/keepass/utils/FileDatabaseInfo.kt index 4f8d47527..87c5ce95e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/FileDatabaseInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/FileDatabaseInfo.kt @@ -2,7 +2,7 @@ package com.kunzisoft.keepass.utils import android.content.Context import android.net.Uri -import com.kunzisoft.keepass.app.database.FileDatabaseHistory +import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.settings.PreferencesUtil class FileDatabaseInfo : FileInfo { @@ -21,7 +21,7 @@ class FileDatabaseInfo : FileInfo { fun retrieveDatabaseTitle(titleCallback: (String)->Unit) { - FileDatabaseHistory.getInstance(context.applicationContext).getFileDatabaseHistory(fileUri) { + FileDatabaseHistoryAction.getInstance(context.applicationContext).getFileDatabaseHistory(fileUri) { fileDatabaseHistoryEntity -> titleCallback.invoke(retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: "")) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt b/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt new file mode 100644 index 000000000..df550bb4e --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt @@ -0,0 +1,99 @@ +package com.kunzisoft.keepass.view + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.StringRes +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector + +class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0) + : LinearLayout(context, attrs, defStyle) { + + private val unlockContainerView: View + private var unlockAnimatedVector: FingerPrintAnimatedVector? = null + private var unlockTitleTextView: TextView? = null + private var unlockMessageTextView: TextView? = null + var unlockIconImageView: ImageView? = null + + init { + + val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + inflater.inflate(R.layout.view_advanced_unlock, this) + + unlockContainerView = findViewById(R.id.fingerprint_container) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + unlockTitleTextView = findViewById(R.id.biometric_title) + unlockMessageTextView = findViewById(R.id.biometric_message) + unlockIconImageView = findViewById(R.id.biometric_image) + // Init the fingerprint animation + unlockAnimatedVector = FingerPrintAnimatedVector(context, unlockIconImageView!!) + } + } + + fun startIconViewAnimation() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + unlockAnimatedVector?.startScan() + } + } + + fun stopIconViewAnimation() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + unlockAnimatedVector?.stopScan() + } + } + + fun setIconViewClickListener(listener: ((view: View)->Unit)?) { + if (listener == null) + stopIconViewAnimation() + else + startIconViewAnimation() + unlockContainerView.alpha = if (listener == null) 0.8f else 1f + unlockIconImageView?.setOnClickListener(listener) + } + + var title: CharSequence + get() { + return unlockTitleTextView?.text?.toString() ?: "" + } + set(value) { + unlockTitleTextView?.text = value + } + + fun setTitle(@StringRes textId: Int) { + title = context.getString(textId) + } + + var message: CharSequence? + get() { + return unlockMessageTextView?.text?.toString() ?: "" + } + set(value) { + if (value == null || value.isEmpty()) + unlockMessageTextView?.visibility = GONE + else + unlockMessageTextView?.visibility = VISIBLE + unlockMessageTextView?.text = value ?: "" + } + + fun setMessage(@StringRes textId: Int) { + message = context.getString(textId) + } + + var hide: Boolean + get() { + return visibility != VISIBLE + } + set(value) { + visibility = if (value) View.GONE else View.VISIBLE + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/view/FingerPrintInfoView.kt b/app/src/main/java/com/kunzisoft/keepass/view/FingerPrintInfoView.kt deleted file mode 100644 index 87cc3a2f5..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/view/FingerPrintInfoView.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.kunzisoft.keepass.view - -import android.content.Context -import android.os.Build -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector - -class FingerPrintInfoView @JvmOverloads constructor(context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0) - : LinearLayout(context, attrs, defStyle) { - - private val fingerPrintContainerView: View - private var fingerPrintAnimatedVector: FingerPrintAnimatedVector? = null - private var fingerPrintTextView: TextView? = null - var fingerPrintImageView: ImageView? = null - - init { - - val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - inflater.inflate(R.layout.fingerprint_show, this) - - fingerPrintContainerView = findViewById(R.id.fingerprint_container) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintTextView = findViewById(R.id.fingerprint_label) - fingerPrintImageView = findViewById(R.id.fingerprint_image) - // Init the fingerprint animation - fingerPrintAnimatedVector = FingerPrintAnimatedVector(context, fingerPrintImageView!!) - } - } - - fun startFingerPrintAnimation() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintAnimatedVector?.startScan() - } - } - - fun stopFingerPrintAnimation() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerPrintAnimatedVector?.stopScan() - } - } - - var text: CharSequence - get() { - return fingerPrintTextView?.text?.toString() ?: "" - } - set(value) { - fingerPrintTextView?.text = value - } - - fun setText(textId: Int, lock: Boolean = false) { - setText(context.getString(textId), lock) - } - - fun setText(text: CharSequence, lock: Boolean = false) { - fingerPrintContainerView.alpha = if (lock) 0.8f else 1f - fingerPrintTextView?.text = text - } - - var hide: Boolean - get() { - return visibility != VISIBLE - } - set(value) { - visibility = if (value) View.GONE else View.VISIBLE - } - -} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_fp_40px.png b/app/src/main/res/drawable-hdpi/ic_fp_40px.png deleted file mode 100644 index 48ebd8ad7..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_fp_40px.png and /dev/null differ diff --git a/app/src/main/res/drawable-v23/lock_open.xml b/app/src/main/res/drawable-v23/lock_open.xml deleted file mode 100644 index d65cefb28..000000000 --- a/app/src/main/res/drawable-v23/lock_open.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/ic_fp_40px.png b/app/src/main/res/drawable-xhdpi/ic_fp_40px.png deleted file mode 100644 index e1c9590bb..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_fp_40px.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fp_40px.png b/app/src/main/res/drawable-xxhdpi/ic_fp_40px.png deleted file mode 100644 index f7e87240e..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_fp_40px.png and /dev/null differ diff --git a/app/src/main/res/drawable/background_image.xml b/app/src/main/res/drawable/background_image.xml new file mode 100644 index 000000000..e098308d1 --- /dev/null +++ b/app/src/main/res/drawable/background_image.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle.xml b/app/src/main/res/drawable/circle.xml deleted file mode 100644 index bea663f9d..000000000 --- a/app/src/main/res/drawable/circle.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fingerprint_remove_white_24dp.xml b/app/src/main/res/drawable/ic_fingerprint_remove_white_24dp.xml new file mode 100644 index 000000000..7eaf955e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_fingerprint_remove_white_24dp.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_remove_circle_white_24dp.xml b/app/src/main/res/drawable/ic_remove_circle_white_24dp.xml deleted file mode 100644 index 2e7ac1e2c..000000000 --- a/app/src/main/res/drawable/ic_remove_circle_white_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/prefs_fingerprint_24dp.xml b/app/src/main/res/drawable/prefs_fingerprint_24dp.xml new file mode 100644 index 000000000..00fe2264f --- /dev/null +++ b/app/src/main/res/drawable/prefs_fingerprint_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-v23/fingerprint_show.xml b/app/src/main/res/layout-v23/fingerprint_show.xml deleted file mode 100644 index 2c82ff720..000000000 --- a/app/src/main/res/layout-v23/fingerprint_show.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout-v23/fragment_fingerprint_explanation.xml b/app/src/main/res/layout-v23/fragment_fingerprint_explanation.xml index 6b575e9ec..5bf5c6e21 100644 --- a/app/src/main/res/layout-v23/fragment_fingerprint_explanation.xml +++ b/app/src/main/res/layout-v23/fragment_fingerprint_explanation.xml @@ -15,11 +15,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Title" - android:text="@string/fingerprint_quick_unlock_title"/> + android:text="@string/fingerprint_advanced_unlock_title"/> - + android:layout_margin="8dp" + style="@style/KeepassDXStyle.FabMenu" + android:text="@string/fingerprint_setting_link_text"/> @@ -67,13 +68,12 @@ android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.TinyText" android:padding="5dp" - android:text="@string/fingerprint_type_password_text"/> + android:text="@string/fingerprint_type_credentials_text"/> - + android:text="@string/fingerprint_open_biometric_prompt"/> + + + + + - + android:orientation="horizontal" + android:layout_marginTop="12dp"> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v23/view_advanced_unlock.xml b/app/src/main/res/layout-v23/view_advanced_unlock.xml new file mode 100644 index 000000000..3093bba00 --- /dev/null +++ b/app/src/main/res/layout-v23/view_advanced_unlock.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_password.xml b/app/src/main/res/layout/activity_password.xml index 1bbf4802c..aaefd9481 100644 --- a/app/src/main/res/layout/activity_password.xml +++ b/app/src/main/res/layout/activity_password.xml @@ -58,6 +58,7 @@ android:layout_height="wrap_content" android:minHeight="48dp" android:layout_gravity="end" + android:gravity="center_vertical|end" style="@style/KeepassDXStyle.TextAppearance.Small" android:textColor="?android:attr/textColorHintInverse" android:paddingHorizontal="8dp" @@ -66,7 +67,7 @@ android:layout_marginRight="12dp" android:layout_marginEnd="12dp" android:text="@string/default_checkbox"/> - - - + android:layout_margin="8dp" + style="@style/KeepassDXStyle.FabMenu" + android:text="@string/magic_keyboard_activate_device_keyboard_setting"/> diff --git a/app/src/main/res/layout/fingerprint_show.xml b/app/src/main/res/layout/view_advanced_unlock.xml similarity index 100% rename from app/src/main/res/layout/fingerprint_show.xml rename to app/src/main/res/layout/view_advanced_unlock.xml diff --git a/app/src/main/res/menu/fingerprint.xml b/app/src/main/res/menu/advanced_unlock.xml similarity index 89% rename from app/src/main/res/menu/fingerprint.xml rename to app/src/main/res/menu/advanced_unlock.xml index 4c37f1fe0..3a71f9213 100644 --- a/app/src/main/res/menu/fingerprint.xml +++ b/app/src/main/res/menu/advanced_unlock.xml @@ -20,8 +20,8 @@ \ 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 e10d893fe..cda2506e4 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -70,17 +70,17 @@ حروف صغيرة إخفاء كلمات المرور بشكل افتراضي عن التطبيق - تغيير المفتاح الرئيسي + تغيير المفتاح الرئيسي الإعدادات إعدادات التطبيق - إعدادات قاعدة البيانات + إعدادات قاعدة البيانات حذف التبرع تعديل قفل قاعدة البيانات فتح البحث - إزالة بصمة المفتاح + إزالة بصمة المفتاح ناقص أبداً لا توجد نتائج للبحث @@ -184,8 +184,8 @@ اربط بطاقة الذاكرة لإنشاء او تحميل قاعدة بيانات. بناء %1$s تم حفظ كلمة السر المشفرة - قاعدة البيانات لا تمتلك كلمة سر. - مظهر + قاعدة البيانات لا تمتلك كلمة سر. + مظهر عام ملأ تلقائي سجل باستخدام KeePass DX @@ -199,9 +199,9 @@ اذا فشل الحذف التلقائي من الحافظة ,احذف تأريخه يدويا قفل الشاشة اقفل قاعدة البيانات عند انغلاق الشاشة - ادخل كلمة سر قاعدة البيانات + ادخل كلمة سر قاعدة البيانات استخدام - حذف مفاتيح التشفير + حذف مفاتيح التشفير لا يمكن بدأ هذه الميزة . نسخة الاندرويد %1$s لا تحقق ادنى متطلبات السنخة %2$s. اسم الملف @@ -209,21 +209,21 @@ بايت مسار الملف عرض المسار الكامل للملف - فحص البصمة مدعوم لكنه ليس معد. - فحص البصمة - لا يمكن قراءة مفتاح البصمة. + فحص البصمة مدعوم لكنه ليس معد. + فحص البصمة + لا يمكن قراءة مفتاح البصمة. \nاستعد كلمة السر. لم يتعرّف على البصمة - استخدم البصمة لحفظ كلمة السر + استخدم البصمة لحفظ كلمة السر تأريخ مكن اشعارات الحافظة لنسخ الحقول - كيف أعد فحص البصمة للفتح السريع \? + كيف أعد فحص البصمة للفتح السريع \? "احفظ البصمات في " افحص البصمة لتخزين كلمة سر قاعدة البيانات بأمان. افحص البصمة لفتح قاعدة البيانات عند تعطيل كلمة السر. - البصمة - فحص البصمة - يسمح بفحص البصمة لفتح قاعدة البيانات + البصمة + فحص البصمة + يسمح بفحص البصمة لفتح قاعدة البيانات غير خط الحقول لتوضيح المحارف افتح الملفات بالتحديد \n @@ -270,13 +270,12 @@ قاعده بيانات طبيعية الوصول إقفال - \"الإعدادات\" ← \"الأمان\" ← \"البصمة\" + \"الإعدادات\" ← \"الأمان\" ← \"البصمة\" تعيين مفتاح رئيسي استخدم سلة المحذوفات Magikeyboard إعدادات Magikeyboard - \"الإعدادات\" ← \"اللغة والإدخال\" ← \"لوحة المفاتيح الحالية\" ثم اختر واحدا. - أو (\"الإعدادات\" ← \"اللغة والإدخال\" ← \"لوحة المفاتيح الافتراضية\" ثم اختر واحدا.) + \"الإعدادات\" ← \"اللغة والإدخال\" ← \"لوحة المفاتيح الحالية\" ثم اختر واحدا. Magikeyboard Magikeyboard (KeePass DX) %1$s متوفر على Magikeyboard diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index f50d7c318..6fe43faae 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -95,9 +95,9 @@ Emmascara contrasenya Amaga les contrasenyes per defecte Sobre - Canvia Clau Mestra + Canvia Clau Mestra Paràmetres - Paràmetres de la base de dades + Paràmetres de la base de dades Esborra Donar Editar diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 54efc654a..cd3f5cf0a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -106,9 +106,9 @@ Skrýt hesla Ve výchozím stavu zobrazovat (***) místo hesla O aplikaci - Změnit hlavní klíč + Změnit hlavní klíč Nastavení - Nastavení databáze + Nastavení databáze Smazat Podpořit vývoj darem Upravit @@ -193,7 +193,7 @@ Přesunout Vložit Storno - Odebrat otisk prstu + Odebrat otisk prstu Pouze pro čtení Čtení a zápis Pouze pro čtení @@ -216,16 +216,16 @@ Nepoužívejte v hesle pro .kdb soubory znaky nepodporované znakovou sadou Latin-1 (nepoužívejte znaky s diakritikou). Opravdu chcete ponechat databázi nechráněnou (bez hesla)? Opravdu nechcete používat šifrovací klíč? - Otisk prstu je zařízením podporován, ale není nastavený. - Snímání otisku prstu + Otisk prstu je zařízením podporován, ale není nastavený. + Snímání otisku prstu Šifrované heslo uloženo - Problém s neplatným otiskem prstu. Obnovte své heslo. + Problém s neplatným otiskem prstu. Obnovte své heslo. Otisk prstu není rozpoznán - Problém s otiskem prstu: %1$s - Použít pro uložení tohoto hesla otisk prstu - Tato databáze zatím není chráněna heslem. + Problém s otiskem prstu: %1$s + Použít pro uložení tohoto hesla otisk prstu + Tato databáze zatím není chráněna heslem. Historie - Vzhled + Vzhled Obecné Automatické vyplnění KeePass DX automatické vyplňování formulářů @@ -243,19 +243,19 @@ Zamknout Zamknout obrazovku Při zhasnutí obrazovky uzamknout databázi - Jak nastavit rychlé odemykání otiskem prstu? + Jak nastavit rychlé odemykání otiskem prstu? Uložte svůj otisk prstu pro své zařízení v - „Nastavení“ → „Zabezpečení“ → „Otisk prstu“ - Zadejte heslo pro zamčení databáze + „Nastavení“ → „Zabezpečení“ → „Otisk prstu“ + Zadejte heslo pro zamčení databáze Přiložte prst na snímač otisku prstu a zabezpečte tak heslo k databázi otiskem. Pro otevření databáze s vypnutým heslem přiložte prst na snímač otisku prstu. Použítí - Otisk - Snímání otisku prstu - Otevírat databázi otiskem prstu - Smazat šifrovací klíče - Smazat všechny šifrovací klíče související s rozpoznáváním otisku prstu - Opravdu chcete smazat všechny klíče přiřazené k otiskům prstů\? + Otisk + Snímání otisku prstu + Otevírat databázi otiskem prstu + Smazat šifrovací klíče + Smazat všechny šifrovací klíče související s rozpoznáváním otisku prstu + Opravdu chcete smazat všechny klíče přiřazené k otiskům prstů\? Toto funkci se nedaří spustit. Verze %1$s vámi používaného systému Android je starší, než minimální požadovaná %2$s. Hardware nebyl rozpoznán. @@ -289,8 +289,7 @@ Nastavení Magikeyboard Nastavit klávesnici pro bezpečné vyplňování formulářů. Zapnout \"Magikeyboard\" v nastavení zařízení. - „Nastavení“ → „Jazyk a vstup“ → „Stávající klávesnice“ a zvolte některou. - nebo („Nastavení“ → „Jazyk a vstup“ → „Virtuální klávesnice“ a zvolte nějakou.) + „Nastavení“ → „Jazyk a vstup“ → „Stávající klávesnice“ a zvolte některou. Když potřebujete vyplnit formulář, zvolte Magikeyboard. Na Magikeyboard můžete přepnout ze své obvyklé klávesnice stisknutím jazykového tlačítka na své klávesnici, dlouhým podržením mezerníku, a pokud nejsou k dispozici tak pomocí: Vyberte položku klávesou. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index facc0d14a..28e75c954 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -103,9 +103,9 @@ Skjul adgangskoder Masker adgangskoder (***) som standard Om - Skift hovednøgle + Skift hovednøgle Indstillinger - Database indstillinger + Database indstillinger Slet Donér Rediger @@ -189,7 +189,7 @@ Flyt Indsæt Annuller - Slet gemt fingeraftryk + Slet gemt fingeraftryk Skrivebeskyttet Modificerbar Skrivebeskyttet @@ -212,16 +212,16 @@ Undgå adgangskodetegn uden for tekstkodningsformatet i databasefilen (ukendte tegn konverteres til samme bogstav). Bekræft brug af ingen adgangskode til beskyttelse mod oplåsning\? Bekræft ingen brug af en krypteringsnøgle? - Fingeraftryksscanning understøttes, men er ikke sat op. - Fingeraftryks-scanning + Fingeraftryksscanning understøttes, men er ikke sat op. + Fingeraftryks-scanning Krypteret adgangskode er gemt - Kunne ikke læse fingeraftryksnøgle. Gendan adgangskode. + Kunne ikke læse fingeraftryksnøgle. Gendan adgangskode. Kunne ikke genkende fingeraftryk - Problem med fingeraftryk: %1$s - Brug fingeraftryk til at gemme adgangskoden - Databasen har endnu ikke en adgangskode. + Problem med fingeraftryk: %1$s + Brug fingeraftryk til at gemme adgangskoden + Databasen har endnu ikke en adgangskode. Historik - Udseende + Udseende Generelt Autoudfyld KeePass DX formularudfyldning @@ -239,19 +239,19 @@ Lås Skærmlås Lås databasen, når skærmen er slukket - Hvordan konfigureres fingeraftryksscanning til hurtig oplåsning\? + Hvordan konfigureres fingeraftryksscanning til hurtig oplåsning\? Gem scannede fingeraftryk for enhed i - \"Indstillinger\" → \"Sikkerhed\" → \"Fingeraftryk\" - Indtast adgangskoden til at låse databasen + \"Indstillinger\" → \"Sikkerhed\" → \"Fingeraftryk\" + Indtast adgangskoden til at låse databasen Scan fingeraftryk for at gemme dataseadgangskode sikkert. Scan fingeraftryk til at åbne databasen, når adgangskoden er slået fra. Brug - Fingeraftryk - Fingeraftryksscanning - Scan fingeraftryk for at åbne databasen - Slet krypteringsnøgler - Slet alle krypteringsnøgler, der er relateret til fingeraftryk - Bekræft sletning af alle nøgler, der er relateret til fingeraftryksgenkendelse\? + Fingeraftryk + Fingeraftryksscanning + Scan fingeraftryk for at åbne databasen + Slet krypteringsnøgler + Slet alle krypteringsnøgler, der er relateret til fingeraftryk + Bekræft sletning af alle nøgler, der er relateret til fingeraftryksgenkendelse\? Funktionen kunne ikke startes. Android-version %1$s opfylder ikke minimum versionskrav %2$s. Kunne ikke finde den tilsvarende hardware. @@ -286,8 +286,7 @@ \n \nOpsæt tastaturet til autofyld af formularerne sikkert. Aktiver Magikeyboard\" i enhedens indstillinger. - \"Indstillinger\" → \"Sprog & input\" → \"Aktuelt tastatur\" og vælg et. - eller (\"Indstillinger\" → \"Sprog & input\" → \"Virtuelt tastatur\" og vælg et.) + \"Indstillinger\" → \"Sprog & input\" → \"Aktuelt tastatur\" og vælg et. Vælg Magikeyboard, når der er brug for at udfylde en formular. Skift tastatur med et langt tryk på mellemrumstasten på tastaturet eller -hvis det ikke er tilgængelig - med: Vælg post med nøglen. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 033fb7f5b..e16046f35 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -107,9 +107,9 @@ Passwort verstecken Passwörter standardmäßig mit (***) maskieren Über - Hauptschlüssel ändern + Hauptschlüssel ändern Einstellungen - Datenbank-Einstellungen + Datenbank-Einstellungen Löschen Spenden Ändern @@ -179,7 +179,7 @@ Soll das Entsperren ohne Passwort wirklich möglich sein\? Soll wirklich kein Verschlüsselungsschlüssel verwendet werden\? - Aussehen + Aussehen Generierte Passwortlänge Legt die Standardlänge des generierten Passworts fest Zwischenablagenbenachrichtigungen @@ -192,28 +192,28 @@ Pfad Dateiname Dieses Feature konnte nicht gestartet werden. - Ermöglicht die Datenbanköffnung mit dem Fingerabdruck - Fingerabdruck - Fingerabdruckscanner + Ermöglicht die Datenbanköffnung mit dem Fingerabdruck + Fingerabdruck + Fingerabdruckscanner Fingerabdruck scannen, um die Datenbank zu öffnen, wenn die Passworteingabe ausgeschaltet ist. Fingerabdruck scannen, um das Datenbank-Passwort sicher zu speichern. - Datenbank-Passwort eingeben + Datenbank-Passwort eingeben Sperre Erlaubte Zeichen für Passwortgenerator festlegen Passwortzeichen - Fingerabdruck-Scannen wird unterstützt, ist jedoch nicht konfiguriert. - Fingerabdruck wird gescannt + Fingerabdruck-Scannen wird unterstützt, ist jedoch nicht konfiguriert. + Fingerabdruck wird gescannt Verschlüsseltes Passwort wurde gespeichert - Fingerabdruckschlüssel nicht lesbar. Bitte das Passwort wiederherstellen. - Problem mit dem Fingerabdruck: %1$s + Fingerabdruckschlüssel nicht lesbar. Bitte das Passwort wiederherstellen. + Problem mit dem Fingerabdruck: %1$s Verlauf - Wie richte ich den Fingerabdruckscanner für schnelles Entsperren ein? + Wie richte ich den Fingerabdruckscanner für schnelles Entsperren ein? Eingelesenen Fingerabdruck für das Gerät speichern in - „Einstellungen“ → „Sicherheit“ → „Fingerabdruck“ + „Einstellungen“ → „Sicherheit“ → „Fingerabdruck“ Verwendung Allgemein - Fingerabdruck verwenden, um dieses Passwort zu speichern - Diese Datenbank hat noch kein Passwort. + Fingerabdruck verwenden, um dieses Passwort zu speichern + Diese Datenbank hat noch kein Passwort. App-Design, das in der App genutzt wird Verschlüsselung Schlüsselableitungsfunktion @@ -223,7 +223,7 @@ Autofill-Dienst kann nicht aktiviert werden. Kopie von %1$s Formularausfüllung - Gespeicherten Fingerabdruck löschen + Gespeicherten Fingerabdruck löschen Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet. Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird. Speichernutzung @@ -246,9 +246,9 @@ Standard Autofill-Dienst auswählen Autofill aktivieren, um automatisch Eingabefelder in anderen Apps auszufüllen Zwischenablage - Verschlüsselungsschlüssel löschen - Alle Verschlüsselungsschlüssel für Fingerabdruckerkennung löschen - Sollen wirklich alle mit der Fingerabdruckerkennung verknüpften Schlüssel gelöscht werden\? + Verschlüsselungsschlüssel löschen + Alle Verschlüsselungsschlüssel für Fingerabdruckerkennung löschen + Sollen wirklich alle mit der Fingerabdruckerkennung verknüpften Schlüssel gelöscht werden\? Die Android-Version, %1$s, erfüllt nicht die Mindestanforderung für Version %2$s. Keine entsprechende Hardware. Bytes @@ -336,8 +336,7 @@ Magikeyboard-Einstellungen Tastatur zum sicheren Ausfüllen von Formularen einrichten. Das „Magikeyboard“ in den Geräteeinstellungen aktivieren. - „Einstellungen“ → „Sprache & Eingabe“ → „Aktuelle Tastatur“ und auswählen. - oder („Einstellungen“ → „Sprache & Eingabe“ → „Bildschirmtastatur“ und auswählen.) + „Einstellungen“ → „Sprache & Eingabe“ → „Aktuelle Tastatur“ und auswählen. Das Magikeyboard auswählen, wenn ein Formular ausgefüllt werden soll. Tastaturen durch langes Drücken auf die Leertaste wechseln oder, wenn das nicht zur Verfügung steht, mit: Den Eintrag mit dem Schlüssel auswählen. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 362b059b4..5619c6224 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -103,9 +103,9 @@ Απόκρυψη κωδικού Απόκρυψη κωδικών από προεπιλογή Σχετικά με - Αλλαγή Κύριου Κλειδιού + Αλλαγή Κύριου Κλειδιού Ρυθμίσεις - Ρυθμίσεις βάσης δεδομένων + Ρυθμίσεις βάσης δεδομένων Διαγραφή Δωρεά Επεξεργασία diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0c7fa2bbb..5443f9644 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -93,9 +93,9 @@ Spanish translation by José I. Paños. Updated by David García-Abad (23-09-201 Ocultar contraseña Ocultar contraseñas por defecto Acerca de - Cambiar Contraseña Maestra + Cambiar Contraseña Maestra Configuración - Configuración de Base de datos + Configuración de Base de datos Eliminar Donar Editar @@ -172,7 +172,7 @@ Spanish translation by José I. Paños. Updated by David García-Abad (23-09-201 El archivo clave está vacío. Copia de %1$s Llenado de formulario - Quite la clave de huella dactilar + Quite la clave de huella dactilar Protección Solo lectura KeePass DX no tiene permiso de escritura en la ubicación de la base de datos, la base de datos se abrirá como solo lectura. @@ -198,16 +198,16 @@ Spanish translation by José I. Paños. Updated by David García-Abad (23-09-201 El formato .kdb solo admite el juego de caracteres Latin1. Su contraseña puede contener caracteres fuera de este juego de caracteres. Todos los caracteres no-Latin1 se convierten al mismo carácter, lo que reduce la seguridad de su contraseña. Se recomienda cambiar su contraseña. ¿De verdad quieres usar una cadena vacía como contraseña? ¿Estás seguro que no quieres usar clave de cifrado? - Huella digital compatible pero no configurada para el dispositivo - Monitoreando huellas digitales + Huella digital compatible pero no configurada para el dispositivo + Monitoreando huellas digitales Contraseña encriptada almacenada - Problema clave de huella digital no válida. Restaure su contraseña. + Problema clave de huella digital no válida. Restaure su contraseña. Huella digital no reconocida - Problema de huella digital: %1$s - Usa la huella digital para almacenar esta contraseña - Aún sin contraseña almacenada para esta base de datos + Problema de huella digital: %1$s + Usa la huella digital para almacenar esta contraseña + Aún sin contraseña almacenada para esta base de datos Historial - Apariencia + Apariencia General Autocompletar Servicio de Autocompletar KeePass DX @@ -224,19 +224,19 @@ Spanish translation by José I. Paños. Updated by David García-Abad (23-09-201 Bloquear Bloqueo de pantalla Bloquear la base de datos cuando la pantalla esté apagada - ¿Cómo configurar la huella digital para un desbloqueo rápido? + ¿Cómo configurar la huella digital para un desbloqueo rápido? Establezca su huella digital personal para su dispositivo en - Configuración -> Seguridad -> Huella digital - Escriba su contraseña en Keepass DX + Configuración -> Seguridad -> Huella digital + Escriba su contraseña en Keepass DX Escanee su huella digital para almacenar su contraseña maestra de forma segura Escanee su huella digital cuando la casilla de verificación de contraseña esté desmarcada para abrir la base de datos Uso - Huella digital - Monitoreando Huella digital - Habilitar la apertura de base de datos por huella digital - Eliminar claves de cifrado - Eliminar todas las claves de cifrado relacionadas con el reconocimiento de huellas digitales - ¿Está seguro de que desea borrar todas las claves relacionadas con las huellas digitales? + Huella digital + Monitoreando Huella digital + Habilitar la apertura de base de datos por huella digital + Eliminar claves de cifrado + Eliminar todas las claves de cifrado relacionadas con el reconocimiento de huellas digitales + ¿Está seguro de que desea borrar todas las claves relacionadas con las huellas digitales? No se puede iniciar esta característica. Su versión de Android %1$s no es la versión mínima, se requiere %2$s. El hardware no es detectado. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index bb64228ba..b6765176a 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -104,9 +104,9 @@ Pasahitza estali Pasahitza estali modu lehenetsian Honi buruz - Gako Maisua Aldatu + Gako Maisua Aldatu Ezarpenak - Datubasearen ezarpenak + Datubasearen ezarpenak Ezabatu Dirua eman Editatu diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index bcfdb0629..a6ed13b8e 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -103,9 +103,9 @@ Piilota salasaan Piilota salasanat oletuksena Tietoa - Vaihda pääsalasanaa + Vaihda pääsalasanaa Asetukset - Salasanatietokannan asetukset + Salasanatietokannan asetukset Poista Lahjoita Muokkaa diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f82344e44..fb6ee4178 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -109,10 +109,10 @@ Masquer les mots de passe Masquer les mots de passe (***) par défaut À propos - Modifier la clé maîtresse + Modifier la clé maîtresse %1$s copié Paramètres - Paramètres de la base de données + Paramètres de la base de données Supprimer Faire un don Modifier @@ -121,7 +121,7 @@ Ouvrir Rechercher Afficher le mot de passe - Supprimer l’empreinte digitale enregistrée + Supprimer l’empreinte digitale enregistrée Ouvrir l’URL Moins Jamais @@ -178,16 +178,16 @@ Ne voulez-vous vraiment aucune protection de déverrouillage par mot de passe \? Êtes-vous sûr de ne vouloir utiliser aucune clé de chiffrement \? Version %1$s - La reconnaissance d’empreinte digitale est prise en charge mais pas configurée. - Reconnaissance d’empreinte digitale + La reconnaissance d’empreinte digitale est prise en charge mais pas configurée. + Reconnaissance d’empreinte digitale Mot de passe chiffré stocké - Impossible de lire la clé de l’empreinte digitale. Restaurer votre mot de passe. + Impossible de lire la clé de l’empreinte digitale. Restaurer votre mot de passe. Impossible de reconnaître l’empreinte digitale - Problème d’empreinte digitale : %1$s - Utiliser l’empreinte digitale pour stocker ce mot de passe - Cette base de données n’a pas encore de mot de passe. + Problème d’empreinte digitale : %1$s + Utiliser l’empreinte digitale pour stocker ce mot de passe + Cette base de données n’a pas encore de mot de passe. Historique - Apparence + Apparence Général Remplissage automatique Remplissage automatique des formulaires KeePass DX @@ -205,19 +205,19 @@ Verrouiller Verrouillage d’écran Verrouiller la base de données lorsque l’écran est éteint - Comment configurer la reconaissance de l’empreinte digitale pour un déverrouillage rapide \? + Comment configurer la reconaissance de l’empreinte digitale pour un déverrouillage rapide \? Enregistrer votre empreinte digitale pour votre appareil dans - « Paramètres » → « Sécurité » → « Empreinte digitale » - Saisir le mot de passe de verrouillage de la base de données + « Paramètres » → « Sécurité » → « Empreinte digitale » + Saisir le mot de passe de verrouillage de la base de données Numériser votre empreinte digitale pour stocker le mot de passe de verrouillage de votre base de données en sécurité. Numériser votre empreinte digitale pour ouvrir la base de données lorsque le mot de passe est désactivé. Utilisation - Empreinte digitale - Reconnaissance d’empreinte digitale - Vous permet de numériser votre empreinte digitale pour ouvrir la base de données - Supprimer les clés de chiffrement - Supprimer toutes les clés de chiffrement liées à la reconnaissance d’empreinte digitale - Êtes-vous sûr de vouloir supprimer toutes les clés liées à la reconnaissance d’empreinte digitale \? + Empreinte digitale + Reconnaissance d’empreinte digitale + Vous permet de numériser votre empreinte digitale pour ouvrir la base de données + Supprimer les clés de chiffrement + Supprimer toutes les clés de chiffrement liées à la reconnaissance d’empreinte digitale + Êtes-vous sûr de vouloir supprimer toutes les clés liées à la reconnaissance d’empreinte digitale \? Impossible de démarrer cette fonctionnalité. Votre version Android %1$s est inférieure à la version minimale %2$s requise. Impossible de trouver le matériel correspondant. @@ -343,8 +343,7 @@ Paramètres du Magikeyboard Configurer le clavier pour le remplissage automatique des formulaires en sécurité. Activer le « Magikeyboard » dans les paramètres de l’appareil. - « Paramètres » → « Langues et saisie » → « Clavier actuel » et en choisir un. - (Ou « Paramètres » → « Langues et saisie » → « Clavier virtuel » et en choisir un.) + « Paramètres » → « Langues et saisie » → « Clavier actuel » et en choisir un. Choisir le Magikeyboard lorsque vous avez besoin de remplir un formulaire. Basculer de clavier en appuyant longuement sur la barre d’espace de votre clavier ou, si ce n’est pas disponible, avec : Sélectionner votre entrée avec la clé. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 15b5272b9..87ac0ae9d 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -99,9 +99,9 @@ Jelszavak elrejtése Jelszavak alapértelmezett elrejtése (***) Névjegy - Mesterkulcs cseréje + Mesterkulcs cseréje Beállítások - Adatbázis-beállítások + Adatbázis-beállítások Törlés Támogatás Szerkesztés @@ -149,13 +149,13 @@ Kerülje a Latin-1 karakterkészlettől eltérő jelszókaraktereket a .kbd fájlokban, mert ugyanarra a betűre lesznek átalakítva. Csatolja az SD kártyát, hogy adatbázist hozzon létre vagy töltsön be. Verzió: %1$s - Az ujjlenyomat-leolvasás támogatott, de nincs beállítva. - Ujjlenyomat-leolvasás + Az ujjlenyomat-leolvasás támogatott, de nincs beállítva. + Ujjlenyomat-leolvasás Titkosított jelszó tárolva - Az ujjlenyomat kulcs nem olvasható. Állítsa vissza a jelszavát. - Ujjlenyomat probléma: %1$s - Használjon ujjlenyomatot a jelszó tárolásához - Az adatbázisnak még nincs jelszava. + Az ujjlenyomat kulcs nem olvasható. Állítsa vissza a jelszavát. + Ujjlenyomat probléma: %1$s + Használjon ujjlenyomatot a jelszó tárolásához + Az adatbázisnak még nincs jelszava. Adja meg a jelszót és/vagy a kulcsfájlt, hogy kinyithassa az adatbázist. \n @@ -195,7 +195,7 @@ Áthelyezés Beillesztés Mégse - Mentett ujjlenyomat törlése + Mentett ujjlenyomat törlése Írásvédett Módosítható Új adatbázis létrehozása @@ -220,7 +220,7 @@ Összeállítás: %1$s Az ujjlenyomat nem ismerhető fel Előzmények - Megjelenés + Megjelenés Általános Automatikus kitöltés KeePass DX űrlapkitöltés @@ -238,19 +238,19 @@ Zárolás Képernyőzár Az adatbázis zárolása, ha a képernyő kikapcsol - Hogyan állítsa be az ujjlenyomat-olvasást a gyors feloldáshoz\? + Hogyan állítsa be az ujjlenyomat-olvasást a gyors feloldáshoz\? Mentse a leolvasott ujjlenyomatát az eszközén a - „Beállítások” → „Biztonság” → „Ujjlenyomat” menüpontban - Adja meg az adatbázis zárolási jelszavát + „Beállítások” → „Biztonság” → „Ujjlenyomat” menüpontban + Adja meg az adatbázis zárolási jelszavát Olvassa le az ujjlenyomatát, hogy biztonságosan tárolja az adatbázist zároló jelszót. Olvassa le az ujjlenyomatát, hogy kinyissa az adatbázist, ha a jelszó ki van kapcsolva. Használat - Ujjlenyomat - Ujjlenyomat-leolvasás - Lehetővé teszi, hogy leolvassa az ujjlenyomatát az adatbázis feloldásához - Titkosítási kulcsok törlése - Az összes ujjlenyomat-felismeréssel kapcsolatos titkosítási kulcs törlése - Biztos, hogy törli az összes ujjlenyomat-felismeréssel kapcsolatos titkosítási kulcsot\? + Ujjlenyomat + Ujjlenyomat-leolvasás + Lehetővé teszi, hogy leolvassa az ujjlenyomatát az adatbázis feloldásához + Titkosítási kulcsok törlése + Az összes ujjlenyomat-felismeréssel kapcsolatos titkosítási kulcs törlése + Biztos, hogy törli az összes ujjlenyomat-felismeréssel kapcsolatos titkosítási kulcsot\? A funkciót nem sikerült elindítani. Az Android verziója (%1$s) nem éri el a minimálisan szükséges verziót (%2$s). Nem található a megfelelő hardver. @@ -282,8 +282,7 @@ Mágikus billentyűzet beállításai Billentyűzet beállítása az űrlapok biztonságos automatikus kitöltéséhez. Aktiválja a „Mágikus billentyűzetet” az eszközbeállításokban. - „Beállítások” → „Nyelvek és bevitel” → „Jelenlegi billentyűzet” és válasszon egyet. - vagy („Beállítások” → „Nyelv és bevitel” → „Virtuális billentyűzet” és válasszon egyet.) + „Beállítások” → „Nyelvek és bevitel” → „Jelenlegi billentyűzet” és válasszon egyet. Válassza a Mágikus billentyűzetet, ha egy űrlapot kell kitöltenie. Válasszon a a billentyűzetek közt a billentyűzet szóközének hosszú nyomásával, vagy ha nem érhető el, akkor így: Válassza ki a bejegyzést a gombbal. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index fbd37e3c1..9de6b688a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -104,9 +104,9 @@ Nascondi le password Maschera le password (***) in modo predefinito Informazioni - Modifica chiave principale + Modifica chiave principale Impostazioni - Impostazioni database + Impostazioni database Elimina Dona Modifica @@ -153,13 +153,13 @@ Permetti l\'accesso in scrittura alla scheda SD per salvare le modifiche al database. Monta la scheda SD per creare o caricare un database. Versione %1$s - La scansione di impronte è supportata ma non impostata. - Scansione impronte + La scansione di impronte è supportata ma non impostata. + Scansione impronte Password criptata salvata - Lettura dell\'impronta fallita. Ripristina la tua password. - Problema impronta: %1$s - Usa l\'impronta per salvare questa password - Questo database non ha ancora alcuna password. + Lettura dell\'impronta fallita. Ripristina la tua password. + Problema impronta: %1$s + Usa l\'impronta per salvare questa password + Questo database non ha ancora alcuna password. Inserisci una password e/o file chiave per sbloccare il database. \n \nRicorda di salvare una copia del tuo file .kdbx in un luogo sicuro dopo ogni modifica. @@ -194,7 +194,7 @@ Sposta Incolla Annulla - Elimina l\'impronta digitale salvata + Elimina l\'impronta digitale salvata Sola lettura Modificabile Algoritmo di cifratura del database usato per tutti i dati. @@ -216,7 +216,7 @@ Sei sicuro di non volere usare una chiave di cifratura? Impronta non riconosciuta Cronologia - Aspetto + Aspetto Generale Autocompletamento Autocompletamento di KeePass DX @@ -233,19 +233,19 @@ Blocca Blocco schermo Blocca il database quando lo schermo è spento - Come impostare la scansione di impronte per sbloccare velocemente\? + Come impostare la scansione di impronte per sbloccare velocemente\? Salva la tua impronta per il dispositivo in - \"Impostazioni\" → \"Sicurezza\" → \"Impronta\" - Digita la password di blocco database + \"Impostazioni\" → \"Sicurezza\" → \"Impronta\" + Digita la password di blocco database Scansiona la tua impronta per salvare la password di blocco database in modo sicuro. Scansiona la tua impronta per aprire il database quando la password è disattivata. Utilizzo - Impronta - Scansione di impronte - Consente la scansione di impronte per aprire il database - Elimina chiavi di cifratura - Elimina tutte le chiavi di cifratura relative al riconoscimento dell\'impronta - Sei sicuro di volere eliminare tutte le chiavi relative alle impronte? + Impronta + Scansione di impronte + Consente la scansione di impronte per aprire il database + Elimina chiavi di cifratura + Elimina tutte le chiavi di cifratura relative al riconoscimento dell\'impronta + Sei sicuro di volere eliminare tutte le chiavi relative alle impronte? Impossibile avviare questa funzione. La tua versione di Android %1$s non è la minima %2$s richiesta. L\'hardware relativo non è stato trovato. @@ -278,8 +278,7 @@ Impostazioni Magitastiera Imposta la tastiera per un\'autocompletamento sicuro dei moduli. Attiva la \"Magitastiera\" nelle impostazioni del dispositivo. - \"Impostazioni\" → \"Lingue e immissione\" → \"Tastiera attuale\" e scegline una. - o (\"Impostazioni\" → \"Lingue e immissione\" → \"Tastiera virtuale\" e scegline una.) + \"Impostazioni\" → \"Lingue e immissione\" → \"Tastiera attuale\" e scegline una. Scegli la Magitastiera quando devi compilare un modulo. Cambia tastiera premendo a lungo la barra spaziatrice, oppure, se non disponibile, con: Blocca il database. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 9714ca530..23f8332bf 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -100,9 +100,9 @@ מסיכת הסיסמה הסתר סיסמאות כברירת מחדל אודות - שנה מפתח ראשי + שנה מפתח ראשי העדפות - הגדרות מסד נתונים + הגדרות מסד נתונים מחק תרום ערוך diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 74a45136e..d0de44bee 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -91,9 +91,9 @@ パスワードを隠す パスワード欄をアスタリスクで表示します About - マスターキーを変更 + マスターキーを変更 設定 - データベース設定 + データベース設定 削除 寄付 編集 @@ -179,7 +179,7 @@ 移動 貼り付け キャンセル - 保存された指紋を削除 + 保存された指紋を削除 書き込み保護 変更可能 新しいデータベースを作成 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b2d93b0cc..dbedb7458 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -96,12 +96,12 @@ 비밀번호 숨기기 기본 비밀번호를 (***) 로 가리기 정보 - 마스터 키 바꾸기 + 마스터 키 바꾸기 %1$s 복사됨 설정 앱 설정 폼 채우기 - 데이터베이스 설정 + 데이터베이스 설정 기부 수정 복사 @@ -114,7 +114,7 @@ 열기 검색 비밀번호 보이기 - 저장된 지문 삭제됨 + 저장된 지문 삭제됨 링크로 가기 쓰기 보호됨 수정 가능 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 22d29b714..6a733d516 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -44,7 +44,7 @@ Grupės sąrašo dydis Apie Nustatymai - Duomenų bazės nustatymai + Duomenų bazės nustatymai Išrinti Aukoti Keisti @@ -84,7 +84,7 @@ Rakto failas neegzistuoja. Rakto failas Įrašo pavadinimas/aprašymas - Pakeisti master raktą + Pakeisti master raktą Naudota: Maskuoti slaptažodį Tarpas diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index e96ba006e..cfff4c1b9 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -83,9 +83,9 @@ Sēpt paroles Sēpt paroles ***** Par - Mainīt galveno paroli + Mainīt galveno paroli Iestatījumi - Datu bāzes iestatījumi + Datu bāzes iestatījumi Dzēst Ziedot Rediģēt diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 8555e0121..6753b7fb6 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -96,12 +96,12 @@ Masker passord Skjul passord som forvalg Om - Endre hovednøkkel + Endre hovednøkkel Kopierte %1$s Innstillinger Programinnstillinger Skjemautfylling - Databaseinnstillinger + Databaseinnstillinger Doner Rediger Kopier @@ -114,7 +114,7 @@ Åpne Søk Vis passord - Fjern fingeravtrykksnøkkelen + Fjern fingeravtrykksnøkkelen Gå til nettadresse Skrivebeskyttet Les og skriv @@ -173,16 +173,16 @@ Ønsker du virkelig å bruke en tom streng som ditt passord? Er du sikker på at du ønsker å bruke en krypteringsnøkkel? Versjon %1$s - Fingeravtrykket er satt støttet, men ikke satt opp på denne enheten. - Venter på fingeravtrykk + Fingeravtrykket er satt støttet, men ikke satt opp på denne enheten. + Venter på fingeravtrykk Kryptert passord lagret - Ugyldig fingeravtrykksnøkkelproblem. Gjenopprett passordet ditt. + Ugyldig fingeravtrykksnøkkelproblem. Gjenopprett passordet ditt. Fremmed fingeravtrykk - Fingeravtrykksproblem: %1$s - Bruk fingeravtrykk til å lagre dette passordet - Denne databasen har ikke et passord enda. + Fingeravtrykksproblem: %1$s + Bruk fingeravtrykk til å lagre dette passordet + Denne databasen har ikke et passord enda. Historikk - Utseende + Utseende Generelt Autofyll KeePass DX autofyll-tjeneste @@ -200,19 +200,19 @@ Lås Skjermlås Lås databasen når skjermen er av - Hvordan sette opp fingeravtrykk for rask opplåsing? + Hvordan sette opp fingeravtrykk for rask opplåsing? Sett ditt personlige fingeravtrykk for din enhet i - \"Innstillinger\" → \"Sikkerhet\" → \"Fingeravtrykk\" - Skriv passordet ditt i KeePass DX + \"Innstillinger\" → \"Sikkerhet\" → \"Fingeravtrykk\" + Skriv passordet ditt i KeePass DX Skann ditt fingeravtrykk for å lagre hovedpassordet ditt sikkert. Skann fingeravtrykket ditt for å åpne databasen når passord er avskrudd. Bruk - Fingeravtrykk - Skanner etter fingeravtrykk - Skru på fingeravtrykksåpning av database - Slett krypteringsnøkler - Slett alle fingeravtrykksnøkler som har med fingeravtrykksgjenkjennelse å gjøre - Er du sikker på at du vil slette alle nøklene som har med fingeravtrykk å gjøre? + Fingeravtrykk + Skanner etter fingeravtrykk + Skru på fingeravtrykksåpning av database + Slett krypteringsnøkler + Slett alle fingeravtrykksnøkler som har med fingeravtrykksgjenkjennelse å gjøre + Er du sikker på at du vil slette alle nøklene som har med fingeravtrykk å gjøre? Kunne ikke starte denne funksjonen. Din Androidversjon %1$s oppfyller ikke minimumskravet, %2$s kreves. Fant ikke maskinvaren. @@ -245,8 +245,7 @@ Magikeyboard-innstillinger Sett opp tastaturet for sikker skjemautfylling. Aktiver Magikeyboard i enhetsinnstillingene. - \"Innstillinger\" → \"Språk og inndata\" → \"Nåværende tastatur\" og velg ett. - eller (\"Innstillinger\" → \"Språk og inndata\" → \"Virtuelt tastatur\" og velg ett.) + \"Innstillinger\" → \"Språk og inndata\" → \"Nåværende tastatur\" og velg ett. Velg Magikeyboard når du trenger å fylle inn et skjema. Du kan enkelt bytte fra ditt hovedtastatur til Magikeyboard med språkknappen på tastaturet ditt, et langt trykk på ditt tastaturs mellomromstast, eller, hvis det ikke er tilgjengelig, med: Velg din oppføring med nøkkelen. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4f14c16cf..3f8a44eef 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -94,9 +94,9 @@ Wachtwoord verbergen Wachtwoorden standaard afschermen (***) Over - Hoofdsleutel wijzigen + Hoofdsleutel wijzigen Instellingen - Databank-instellingen + Databank-instellingen Verwijderen Doneren Bewerken @@ -186,7 +186,7 @@ Verplaatsen Plakken Annuleren - Opgeslagen vingerafdruk verwijderen + Opgeslagen vingerafdruk verwijderen Alleen-lezen Lezen en schrijven Beveiliging @@ -215,16 +215,16 @@ Vermijd letters bevatten die niet ondersteund worden door de Latin-1-tekenset van .kdb-bestanden; ze worden allemaal omgezet naar dezelfde letter. Weet je zeker dat je geen wachtwoordontgrendelbescherming wilt\? Weet je zeker dat je geen sleutel wilt koppelen aan je versleuteling? - Vingerafdruk wordt ondersteund, maar is niet ingesteld. - Vingerafdrukscanning + Vingerafdruk wordt ondersteund, maar is niet ingesteld. + Vingerafdrukscanning Versleuteld wachtwoord is opgeslagen - Kan vingerafdruksleutel niet lezen; herstel je wachtwoord. + Kan vingerafdruksleutel niet lezen; herstel je wachtwoord. Vingerafdruk niet herkend - Vingerafdrukprobleem: %1$s - Vingerafdruk gebruiken om dit wachtwoord op te slaan - Deze databank heeft nog geen wachtwoord. + Vingerafdrukprobleem: %1$s + Vingerafdruk gebruiken om dit wachtwoord op te slaan + Deze databank heeft nog geen wachtwoord. Geschiedenis - Uiterlijk + Uiterlijk Algemeen Auto-aanvullen KeePass DX auto-aanvullendienst @@ -242,19 +242,19 @@ Vergrendelen Schermvergrendeling Databank vergrendelen als het scherm uitgaat - Hoe stel ik mijn vingerafdruk in voor snelle ontgrendeling? + Hoe stel ik mijn vingerafdruk in voor snelle ontgrendeling? Stel je persoonlijke vingerafdruk voor je apparaat in via - \"Instellingen\" → \"Beveiliging\" → \"Vingerafdruk\" - Voer het databankwachtwoord in + \"Instellingen\" → \"Beveiliging\" → \"Vingerafdruk\" + Voer het databankwachtwoord in Scan je vingerafdruk om je databankwachtwoord veilig op te slaan. Scan je vingerafdruk om de databank te ontgrendelen als er geen vinkje staat bij wachtwoord Gebruik - Vingerafdruk - Bezig met vingerafdrukherkenning - Databank openen met vingerafdruk - Sleutels voor versleuteling verwijderen - Alle aan vingerafdrukherkenning gerelateerde sleutels wilt verwijderen - Weet je zeker dat je alle aan vingerafdrukherkenning gerelateerde sleutels wilt verwijderen? + Vingerafdruk + Bezig met vingerafdrukherkenning + Databank openen met vingerafdruk + Sleutels voor versleuteling verwijderen + Alle aan vingerafdrukherkenning gerelateerde sleutels wilt verwijderen + Weet je zeker dat je alle aan vingerafdrukherkenning gerelateerde sleutels wilt verwijderen? Kan deze functie niet starten. Je Android-versie, %1$s, voldoet niet aan de minimumvereiste %2$s. De hardware wordt niet herkend. @@ -288,8 +288,7 @@ Magikeyboard-instellingen Hoe stel ik het toetsenbord in voor het veilig invullen van formulieren? Activeer Magikeyboard in de apparaatinstellingen. - \"Instellingen\" → \"Taal en invoer\" → \"Huidig toetsenbord\" en kies er een. - of (\"Instellingen\" → \"Taal en invoer\" → \"Virtueel toetsenbord\") + \"Instellingen\" → \"Taal en invoer\" → \"Huidig toetsenbord\" en kies er een. Kies Magikeyboard als je een formulier moet invullen. Je kunt eenvoudig schakelen tussen je gebruikelijke toetsenbord en Magikeyboard middels de taalknop op je toetsenbord of door de spatiebalk lang ingedrukt te houden. Indien niet beschikbaar kan het ook met: Kies je iteminvoer met de toets. diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 9830b27da..854dad3d9 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -92,9 +92,9 @@ Masker passord Gøym passorda (standardval) Om - Endra hovudnøkkelen + Endra hovudnøkkelen Innstillingar - Databaseinnstillingar + Databaseinnstillingar Slett Doner Endra diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 86569599b..02a239393 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -89,9 +89,9 @@ along with KeePass DX. If not, see . Ukryj hasła Maskuj hasła (***) domyślnie O programie - Zmień klucz główny + Zmień klucz główny Ustawienia - Ustawienia bazy danych + Ustawienia bazy danych Usuń Dotuj Edytuj @@ -182,7 +182,7 @@ along with KeePass DX. If not, see . Przenieś Wklej Anuluj - Usuń zapisany klucz linii papilarnych + Usuń zapisany klucz linii papilarnych Chroniony przed zapisem Modyfikowalne Ochrona @@ -209,17 +209,17 @@ along with KeePass DX. If not, see . Czy naprawdę nie chcesz ochrony przed odblokowaniem hasła\? Czy na pewno nie chcesz używać żadnego klucza szyfrowania? Wersja %1$s - Skanowanie odcisków palców jest obsługiwane, ale nie skonfigurowane. + Skanowanie odcisków palców jest obsługiwane, ale nie skonfigurowane. "Przechowywane hasło zaszyfrowane " Grupy poprzednie - Skanowanie odcisków palców - Nie można odczytać klucza linii papilarnych. Przywróć swoje hasło. + Skanowanie odcisków palców + Nie można odczytać klucza linii papilarnych. Przywróć swoje hasło. Nie można rozpoznać odcisku palca - Problem z odciskiem palca: %1$s - Użyj odcisku palca, aby zapisać to hasło - Baza danych nie ma jeszcze hasła. + Problem z odciskiem palca: %1$s + Użyj odcisku palca, aby zapisać to hasło + Baza danych nie ma jeszcze hasła. Historia - Wygląd + Wygląd Ogólne Wypełnij automatycznie Autouzupełnianie formularzy KeePass DX @@ -237,19 +237,19 @@ along with KeePass DX. If not, see . Blokada Blokada ekranu Zablokuj bazę danych, gdy ekran jest wyłączony - Jak skonfigurować skanowanie linii papilarnych do szybkiego odblokowania\? + Jak skonfigurować skanowanie linii papilarnych do szybkiego odblokowania\? Zapisz zeskanowany odcisk palca w urządzeniu - \"Ustawienia\" → \"Bezpieczeństwo\" → \"Odcisk palca\" - Wprowadź hasło blokady bazy danych + \"Ustawienia\" → \"Bezpieczeństwo\" → \"Odcisk palca\" + Wprowadź hasło blokady bazy danych Zeskanuj swój odcisk palca, aby bezpiecznie zapisać hasło blokady bazy danych. Zeskanuj swój odcisk palca, aby otworzyć bazę danych po wyłączeniu hasła. Użycie - Odcisk palca - Skanowanie odcisków palców - Umożliwia skanowanie odcisku palca w celu otwarcia bazy danych - Usuń klucze szyfrowania - Usuń wszystkie klucze szyfrowania związane z rozpoznawaniem linii papilarnych - Czy na pewno chcesz usunąć wszystkie klucze związane z rozpoznawaniem linii papilarnych\? + Odcisk palca + Skanowanie odcisków palców + Umożliwia skanowanie odcisku palca w celu otwarcia bazy danych + Usuń klucze szyfrowania + Usuń wszystkie klucze szyfrowania związane z rozpoznawaniem linii papilarnych + Czy na pewno chcesz usunąć wszystkie klucze związane z rozpoznawaniem linii papilarnych\? Nie można uruchomić tej funkcji. Twoja wersja Androida %1$s nie spełnia wymaganej minimalnej wersji %2$s. Nie można znaleźć odpowiedniego sprzętu. @@ -282,8 +282,7 @@ along with KeePass DX. If not, see . Ustawienia Magikeyboard Skonfiguruj klawiaturę, aby bezpiecznie wypełniać formularze. Aktywuj \"Magikeyboard\" w ustawieniach urządzenia. - \"Ustawienia\" → \"Język i wprowadzanie\" → \"Aktualna klawiatura\" i wybierz jedną. - lub (\"Ustawienia\" → \"Język i wprowadzanie danych\" → \"Klawiatura wirtualna\" i wybierz jedną.) + \"Ustawienia\" → \"Język i wprowadzanie\" → \"Aktualna klawiatura\" i wybierz jedną. Wybierz Magikeyboard, gdy potrzebujesz wypełnić formularz. Przełącz klawiaturę naciskając klawisz spacji na klawiaturze lub, jeśli nie jest dostępny, z: Wybierz wpis za pomocą klawisza. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 21a4aedce..52d251960 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -94,9 +94,9 @@ Esconder senhas Mascarar senhas (***) por padrão Sobre - Modificar senha mestre + Modificar senha mestre Configurações - Configurações do banco de dados + Configurações do banco de dados Deletar Doaçoes Editar @@ -179,7 +179,7 @@ Mover Colar Cancelar - Deletar impressão digital salva + Deletar impressão digital salva Apenas leitura Leitura e escrita Proteção @@ -208,16 +208,16 @@ Evite caracteres fora do formato de codificação do arquivo do banco (todos os caracteres não reconhecidos são convertidos para a mesma letra). Você realmente não quer proteção por senha\? Você tem certeza de que não quer usar uma chave de encriptação\? - Impressão digital é suportada, mas não está configurada. - Escaneamento de impressão digital + Impressão digital é suportada, mas não está configurada. + Escaneamento de impressão digital Senha encriptada armazenada - Não pôde ler chave de impressão digital. Recupere sua senha. + Não pôde ler chave de impressão digital. Recupere sua senha. Não pôde reconhecer impressão digital - Problema de Impressão digital: %1$s - Use Impressão digital para armazenar esta senha - Ainda não há nenhuma senha armazenada nesse banco de dados. + Problema de Impressão digital: %1$s + Use Impressão digital para armazenar esta senha + Ainda não há nenhuma senha armazenada nesse banco de dados. Histórico - Aparência + Aparência Geral Preenchimento automático Preenchimento Automático KeePass DX @@ -234,19 +234,19 @@ Bloquear Bloqueio de tela Bloqueie o banco de dados quando a tela estiver desligada - Como configurar impressões digitais para desbloqueio rápido? + Como configurar impressões digitais para desbloqueio rápido? Salvar sua impressão digital esconeada para seu dispositivo em - \"Configurações\" → \"Segurança\" → \"Impressão Digital\" - Entre com sua senha no banco + \"Configurações\" → \"Segurança\" → \"Impressão Digital\" + Entre com sua senha no banco Escaneie sua impressão digital para armazenar sua senha da base com segurança. Escaneie sua impressão digital para abrir o banco de dados quando a senha estiber desativada. Uso - Impressão Digital - Escaneamento de impressão digital - Permite que você escaneie sua impressão digital para a abertura do banco de dados - Apague chaves de encriptação - Apague todas as chaves de encriptação relacionadas ao reconhecimento de Impressões digitais - Tem certeza que você quer apagar todas as chaves relacionadas ao reconhecimento de Impressões digitais\? + Impressão Digital + Escaneamento de impressão digital + Permite que você escaneie sua impressão digital para a abertura do banco de dados + Apague chaves de encriptação + Apague todas as chaves de encriptação relacionadas ao reconhecimento de Impressões digitais + Tem certeza que você quer apagar todas as chaves relacionadas ao reconhecimento de Impressões digitais\? Não foi possível iniciar esse recurso. Sua versão do Android %1$s não corresponde a versão mínima %2$s necessária. Não foi possível encontrar o hardware correspondente. @@ -279,8 +279,7 @@ Configurações do Magikeyboard Configure o teclado para automaticamente preencher formulários seguramente. Ative \"Magikeyboard\" nas configurações do dispositivo. - \"Configurações\" → \"Idioma e Entrada\" → \"Teclado Atual\" → \"Selecionar Teclado\" e escolha um. - ou (\"Configurações\" → \"Sistema → Idioma e Entrada\" → \"Teclado Virtual\" → \"Gerenciar Teclados\") + \"Configurações\" → \"Idioma e Entrada\" → \"Teclado Atual\" → \"Selecionar Teclado\" e escolha um. Escolha o Magikeyboard quando precisar preencher um formulário. Troque teclados ao pressionar e segurar a barra de espaço de seu teclado, ou, se não estiver disponível, com: Selecione uma entrada com a chave. diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index ae623e1db..cdaa5554d 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -103,9 +103,9 @@ Mascarar palavra-passe Ocultar as palavras-passe por predefinição Sobre - Alterar chave mestre + Alterar chave mestre Definições - Definições da base de dados + Definições da base de dados Eliminar Fazer donativo Editar @@ -182,7 +182,7 @@ Mover Colar Cancelar - Apagar de impressão digital gravada + Apagar de impressão digital gravada Protegido contra escrita Pode ser modificado Criar nova base de dados @@ -203,14 +203,14 @@ Quer realmente usar uma palavra-chave vazia\? Tem a certeza que não quer usar uma chave de encriptação\? Construir %1$s - Impressão digital suportada, mas não configurada para dispositivo. + Impressão digital suportada, mas não configurada para dispositivo. Palavra-chave encriptada armazenada Impressão digital não reconhecida - Problema da Impressão digital: %1$s - Use a impressão digital para armazenar esta palavra-chave - Ainda não há nenhuma palavra-chave armazenada nesta base de dados. + Problema da Impressão digital: %1$s + Use a impressão digital para armazenar esta palavra-chave + Ainda não há nenhuma palavra-chave armazenada nesta base de dados. Histórico - Aparência + Aparência Geral Preenchimento automático Serviço de Preenchimento Automático do KeePass DX diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 35f8bf8e8..d5f5613e3 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -103,9 +103,9 @@ Скрыть пароли Скрывать пароли по умолчанию Сведения - Изменить мастер-ключ + Изменить мастер-ключ Настройки - Настройки базы + Настройки базы Удалить Помочь Правка @@ -154,13 +154,13 @@ Карта помять в режиме только для чтения. Изменения не будут сохранены. Карта памяти не подключена. Работа с базой невозможна. Версия %1$s - Отпечатки пальцев поддерживаются, но не настроены. - Ожидание отпечатка пальца + Отпечатки пальцев поддерживаются, но не настроены. + Ожидание отпечатка пальца Зашифрованный пароль сохранён - Неверный ключ отпечатка пальца. Восстановите пароль. - Проблема с отпечатком пальца : %1$s - Используйте отпечаток пальца, чтобы сохранить пароль - Для этой базы пароль ещё не сохранён + Неверный ключ отпечатка пальца. Восстановите пароль. + Проблема с отпечатком пальца : %1$s + Используйте отпечаток пальца, чтобы сохранить пароль + Для этой базы пароль ещё не сохранён Введите пароль и/или файл ключа, чтобы разблокировать базу. \n \nНе забудьте сохранить копию .kdbx файла в безопасном месте после каждого изменения. @@ -210,7 +210,7 @@ Переместить Вставить Отмена - Удалить отпечаток + Удалить отпечаток Только чтение Чтение и запись Сначала группы @@ -220,7 +220,7 @@ Вы действительно не хотите использовать ключ шифрования? Отпечаток пальца не распознан История - Внешний вид + Внешний вид Общие Автозаполнение Сервис автозаполнения KeePass DX @@ -238,19 +238,19 @@ Блокировка Блокировка экрана Блокировать базу при отключении экрана - Как настроить отпечаток пальца для быстрой разблокировки? + Как настроить отпечаток пальца для быстрой разблокировки? Установите ваш личный отпечаток пальца для вашего устройства - \"Настройки\" → \"Безопасность\" → \"Отпечаток пальца\" - Введите ваш пароль в Keepass DX + \"Настройки\" → \"Безопасность\" → \"Отпечаток пальца\" + Введите ваш пароль в Keepass DX Сканируйте ваш отпечаток пальца для безопасного хранения вашего мастер-пароля Сканируйте ваш отпечаток пальца, когда поле \"Пароль\" не отмечено, для разблокировки базы Использование - Отпечаток пальца - Сканирование отпечатка пальца - Включить разблокировку базы с помощью отпечатка пальца - Удалить ключи шифрования - Удалить все ключи шифрования, связанные с распознаванием отпечатка пальца - Вы уверены, что хотите удалить все ключи, связанные с отпечатком пальца? + Отпечаток пальца + Сканирование отпечатка пальца + Включить разблокировку базы с помощью отпечатка пальца + Удалить ключи шифрования + Удалить все ключи шифрования, связанные с распознаванием отпечатка пальца + Вы уверены, что хотите удалить все ключи, связанные с отпечатком пальца? Невозможно запустить эту функцию. Ваша версия Android %1$s ниже минимально необходимой %2$s. Оборудование не найдено. @@ -283,8 +283,7 @@ Настройки Magikeyboard Как настроить клавиатуру для безопасного заполнения форм? Активировать Magikeyboard в настройках устройства. - \"Настройки\" → \"Язык и ввод\" → \"Текущая клавиатура\" и выбрать. - или (\"Настройки\" → \"Язык и ввод\" → \"Виртуальная клавиатура\" и выбрать.) + \"Настройки\" → \"Язык и ввод\" → \"Текущая клавиатура\" и выбрать. Выбрать Magikeyboard, когда вам необходимо заполнить форму. Вы можете легко переключаться с основной клавиатуры на Magikeyboard с помощью кнопки языка клавиатуры, длительного нажатия на пробел клавиатуры или, если она недоступна, с помощью: Выберите запись с помощью кнопки. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 2ca101a92..b4d44415f 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -91,9 +91,9 @@ Skryť heslo Skryť heslá štandardne O Programe - Zmeniť hlavný Kľúč + Zmeniť hlavný Kľúč Nastavenia - Nastavenia Databázy + Nastavenia Databázy Zmazať Darovať Upraviť diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 7c87a9dbb..e6a9f5129 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -103,9 +103,9 @@ Maskera lösenord Döljer lösenord som standard Om - Byt nyckelfil + Byt nyckelfil Inställningar - Databasinställningar + Databasinställningar Radera Donera Redigera diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 27856fb1e..5dcd7120f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -96,12 +96,12 @@ "Parolaları gizle " Parola maskesi. Varsayılan (***) Hakkında - Ana anahtarı değitir + Ana anahtarı değitir %1$s kopyalandı Ayarlar Uygulama ayarları Form doldurma - Veritabanı ayarları + Veritabanı ayarları Bağış Yap Düzen Kopyala @@ -114,7 +114,7 @@ Ara Parolayı göster - Kaydedilen parmak izini silin + Kaydedilen parmak izini silin URL\'a git Yazma korumalı Değiştirilebilir @@ -176,16 +176,16 @@ Herhangi bir şifreleme anahtarı kullanmak istemediğinize emin misiniz\? Sürüm %1$s Yapı %1$s - Parmak izi taraması desteklenir, ancak kurulmaz. - Parmak izi tarama + Parmak izi taraması desteklenir, ancak kurulmaz. + Parmak izi tarama Şifreli parola saklandı - Parmak izi anahtarı okunamadı. Şifreni geri yükle. + Parmak izi anahtarı okunamadı. Şifreni geri yükle. Parmak izi tanınamadı - Parmak izi sorunu: %1$s - Bu şifreyi saklamak için parmak izini kullanın - Bu veritabanının henüz bir parolası yok. + Parmak izi sorunu: %1$s + Bu şifreyi saklamak için parmak izini kullanın + Bu veritabanının henüz bir parolası yok. Geçmiş - Görünüm + Görünüm Genel Otomatik Doldurma KeePass DX formu otomatik doldurma @@ -203,19 +203,19 @@ Kilit Ekran kilidi Ekran kapalıyken veritabanını kilitle - Hızlı kilit açmak için parmak izi taraması nasıl ayarlanır\? + Hızlı kilit açmak için parmak izi taraması nasıl ayarlanır\? Cihazınız için taranmış parmak izinizi kaydet - \"Ayarlar\" → \"Güvenlik\" → \"Parmak İzi\" - Veritabanı kilitleme parolasını girin + \"Ayarlar\" → \"Güvenlik\" → \"Parmak İzi\" + Veritabanı kilitleme parolasını girin Veritabanı kilit parolanızı güvenli bir şekilde saklamak için parmak izinizi tarayın. Parola kapatıldığında veritabanını açmak için parmak izinizi tarayın. Kullanım - Parmakizi - Parmak izi tarama - Veritabanını açmak için parmak izinizi taramanızı sağlar - Şifreleme anahtarlarını silin - Parmak izi tanıma ile ilgili tüm şifreleme anahtarlarını silin - Parmak izi tanıma ile ilgili tüm tuşları silmek istediğinizden emin misiniz\? + Parmakizi + Parmak izi tarama + Veritabanını açmak için parmak izinizi taramanızı sağlar + Şifreleme anahtarlarını silin + Parmak izi tanıma ile ilgili tüm şifreleme anahtarlarını silin + Parmak izi tanıma ile ilgili tüm tuşları silmek istediğinizden emin misiniz\? Bu özellik başlatılamadı. Android sürümünüz %1$s, gerekli minimum %2$s sürümünü karşılamıyor. İlgili donanım bulunamadı. @@ -247,8 +247,7 @@ Magikeyboard ayarları Formları güvenli bir şekilde otomatik doldurmak için klavyeyi ayarlayın. Cihaz ayarlarında \"Magikeyboard\"u etkinleştirin. - \"Ayarlar\" → \"Dil & giriş\" → \"Geçerli Klavye\" ve birini seçin. - veya (\"Ayarlar\" → \"Dil & giriş\" → \"Sanal klavye\" ve birini seçin.) + \"Ayarlar\" → \"Dil & giriş\" → \"Geçerli Klavye\" ve birini seçin. Bir formu doldurmanız gerektiğinde Magikeyboard\'u seçin. Klavyenizdeki boşluk çubuğuna uzun basarak veya mevcut değilse şunu yaparak klavyeleri değiştirin: Anahtarlı girişinizi seçin. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 64d637cbb..ae2983f07 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -92,9 +92,9 @@ Маска пароля Приховати паролі за замовчуванням Про - Змінити головний ключ + Змінити головний ключ Налаштування - Налаштування бази даних + Налаштування бази даних Вилучити Пожертвувати Редагувати diff --git a/app/src/main/res/values-v23/donottranslate.xml b/app/src/main/res/values-v23/donottranslate.xml index 2e3d62eec..d442575f7 100644 --- a/app/src/main/res/values-v23/donottranslate.xml +++ b/app/src/main/res/values-v23/donottranslate.xml @@ -18,5 +18,5 @@ along with KeePass DX. If not, see . --> - true + true diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 222f32511..806dd1900 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -91,9 +91,9 @@ 隐藏密码 默认使用星号 (***) 隐藏密码 关于 - 更改主密钥 + 更改主密钥 设置 - 数据库设置 + 数据库设置 删除 捐赠 编辑 @@ -181,7 +181,7 @@ 粘贴 取消 显示密码 - 删除保存的指纹 + 删除保存的指纹 只读 可修改 搜索时忽略备份条目 @@ -207,9 +207,9 @@ 搜索结果 警告 版本 %1$s - 使用指纹保存此密码 + 使用指纹保存此密码 历史 - 外观 + 外观 通用 自动填充 KeePass DX 表单自动填充 @@ -217,7 +217,7 @@ 剪贴板 剪贴板通知 锁定 - 指纹 + 指纹 文件名 路径 创建新数据库 @@ -249,13 +249,13 @@ 你真的不需要密码解锁的保护吗? 你确认不使用任何的密钥吗? 版本 %1$s - 指纹解锁功能支持,但没有配置使用。 - 指纹扫描 + 指纹解锁功能支持,但没有配置使用。 + 指纹扫描 加密密码存储 - 不能读取指纹密钥,请重置你的密码。 + 不能读取指纹密钥,请重置你的密码。 未能识别的指纹 - 指纹识别问题:%1$s - 当前数据库没有任何密码。 + 指纹识别问题:%1$s + 当前数据库没有任何密码。 配置自动填充服务 打开自动填充功能,以便快速的在其他应用中填写信息 密码生成长度 @@ -266,18 +266,18 @@ 如果剪切板自动删除记录失败,则手动删除历史记录。 屏幕锁定 当屏幕熄灭时锁定数据库 - 如何设置指纹扫描以快速解锁? + 如何设置指纹扫描以快速解锁? 通过您设备的安全设置保存您的指纹: - \"设置\" → \"安全\" → \"指纹\" - 输入数据库密码 + \"设置\" → \"安全\" → \"指纹\" + 输入数据库密码 扫描你的指纹,以便安全的存储数据库密码。 当数据库密码功能关闭的时候,使用指纹扫描解锁数据库。 使用指纹解锁 - 指纹扫描 - 通过指纹扫描解锁数据库 - 删除加密密钥 - 删除所有与指纹解锁相关的加密密钥 - 确认要删除所有与指纹识别相关的密钥吗? + 指纹扫描 + 通过指纹扫描解锁数据库 + 删除加密密钥 + 删除所有与指纹解锁相关的加密密钥 + 确认要删除所有与指纹识别相关的密钥吗? 无法启动此功能。 你的安卓版本 %1$s 不能满足软件对最低系统版本 %2$s 的要求。 找不到所需的硬件。 @@ -294,8 +294,7 @@ 激活自定义键盘,填充密码和所有标识字段 选择“Magikeyboard键盘”以安全地自动填充表单。 在设备设置中激活“Magikeyboard键盘”。 - “设置”→“语言和输入”→“当前键盘”,并选择所需要用于输入的键盘。 - 或(“设置”→“语言和输入”→“虚拟键盘”,并选择所需要用于输入的键盘) + “设置”→“语言和输入”→“当前键盘”,并选择所需要用于输入的键盘。 当填写表单时,请选择“Magikeyboard”键盘。 剪切板权限 通过长时间按住“空格键”以切换键盘,如果“空格键”不可用,则: diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f14223b11..5536eadb6 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -91,9 +91,9 @@ 密碼遮罩 預設隱藏密碼 關於 - 變更主密鑰 + 變更主密鑰 設定 - 資料庫設定 + 資料庫設定 刪除 贊助 編輯 @@ -163,7 +163,7 @@ 貼上 取消 顯示密碼 - 移除指紋密鑰 + 移除指紋密鑰 唯讀 讀寫 不要搜尋備份的項目 diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 5bfc6ce33..fb251c9f5 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -65,7 +65,7 @@ --> - app + settings_app_key allow_no_password_key false @@ -92,9 +92,11 @@ true auto_open_file_uri_key true - fingerprint_enable_key - false - fingerprint_delete_all_key + biometric_unlock_enable_key + false + biometric_auto_open_prompt_key + false + biometric_delete_all_key_key settings_form_filling_key @@ -123,6 +125,9 @@ keyboard_key_sound_key false + + settings_advanced_unlock_key + settings_appearance_key @@ -142,8 +147,8 @@ relaunch_education_screens_key - database_main_menu_key - database_change_master_key_key + settings_database_key + settings_database_change_credentials_key database_general_key database_name_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d1d8d15f7..7430bb254 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,12 +144,13 @@ Hide passwords Mask passwords (***) by default About - Change master key + Change master key Copy of %1$s Settings App settings Form filling - Database settings + Advanced unlock + Database settings Donate Edit Copy @@ -162,7 +163,7 @@ Open Search Show password - Delete saved fingerprint + Delete saved biometric key Go to URL Write-protected Modifiable @@ -225,17 +226,22 @@ Are you sure you do not want to use any encryption key? Version %1$s Build %1$s - Fingerprint scanning is supported but not set up. - Fingerprint Scanning + Biometric prompt is supported but not set up. + Open the biometric prompt to unlock the database + Open the biometric prompt to store credentials + Save biometric recognition + Store database credentials with biometric data + Open database with biometric recognition + Extract database credential with biometric data Encrypted password stored - Could not read fingerprint key. Restore your password. + Could not read biometric key. Restore your credential. Could not recognize fingerprint - Fingerprinting problem: %1$s - Use fingerprint to store this password - This database does not have a password yet. + Biometric error: %1$s + This database does not have stored credential yet. History - Appearance + Appearance + Biometric General Autofill KeePass DX form autofilling @@ -255,20 +261,23 @@ Lock the database when the screen is off Press Back on root to lock Lock the database when the user clicks the back button on the root screen - How to set up fingerprint scanning for quick unlocking? - Save your scanned fingerprint for your device in - - \"Settings\" → \"Security\" → \"Fingerprint\" - Enter database lock password + How to set up fingerprint scanning for advanced unlocking? + Save your scanned fingerprint for your device in device settings. + Device Fingerprint Settings + Enter database lock credentials + Open the biometric prompt by clicking on biometric button. Scan your fingerprint to store your database lock password securely. Scan your fingerprint to open the database when password is turned off. + You can change the preference to quickly open the biometric prompt to not repeat step 1. Usage - Fingerprint - Fingerprint scanning - Lets you scan your fingerprint to open the database - Delete encryption keys - Delete all encryption keys related to fingerprint recognition - Are you sure you want to delete all keys related to fingerprint recognition? + Advanced unlock + Biometric unlocking + Lets you scan your fingerprint or other biometric to open the database + Auto open biometric prompt + Automatically open biometric prompt when a biometric key is defined for a database + Delete encryption keys + Delete all encryption keys related to biometric recognition + Are you sure you want to delete all keys related to biometric recognition? Could not start this feature. Your Android version %1$s does not meet the minimum version %2$s required. Could not find the corresponding hardware. @@ -305,10 +314,7 @@ Magikeyboard settings Set up the keyboard to autofill forms securely. Activate \"Magikeyboard\" in the device settings. - - \"Settings\" → \"Language & input\" → \"Current Keyboard\" and pick one. - - or (\"Settings\" → \"Language & input\" → \"Virtual keyboard\" and pick one.) + Device Keyboard Settings Choose the Magikeyboard when you need to fill a form. Switch keybaords by long pressing the spacebar of your keyboard, or, if it is not available, with: Select your entry with the key. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 176aac056..04ec775b2 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -226,6 +226,7 @@ ?attr/textColorInverse diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e8168ddfd..bf1a4807a 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -22,7 +22,7 @@ @@ -31,9 +31,14 @@ android:title="@string/menu_form_filling_settings" android:icon="@drawable/prefs_content_paste_24dp" android:persistent="false" /> + @@ -41,15 +46,15 @@ diff --git a/app/src/main/res/xml/preferences_advanced_unlock.xml b/app/src/main/res/xml/preferences_advanced_unlock.xml new file mode 100644 index 000000000..763ac1c21 --- /dev/null +++ b/app/src/main/res/xml/preferences_advanced_unlock.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/appearance_preferences.xml b/app/src/main/res/xml/preferences_appearance.xml similarity index 100% rename from app/src/main/res/xml/appearance_preferences.xml rename to app/src/main/res/xml/preferences_appearance.xml diff --git a/app/src/main/res/xml/application_preferences.xml b/app/src/main/res/xml/preferences_application.xml similarity index 90% rename from app/src/main/res/xml/application_preferences.xml rename to app/src/main/res/xml/preferences_application.xml index 213991e29..7966711b9 100644 --- a/app/src/main/res/xml/application_preferences.xml +++ b/app/src/main/res/xml/preferences_application.xml @@ -134,19 +134,4 @@ - - - - - - - diff --git a/app/src/main/res/xml/database_preferences.xml b/app/src/main/res/xml/preferences_database.xml similarity index 100% rename from app/src/main/res/xml/database_preferences.xml rename to app/src/main/res/xml/preferences_database.xml diff --git a/app/src/main/res/xml/form_filling_preferences.xml b/app/src/main/res/xml/preferences_form_filling.xml similarity index 100% rename from app/src/main/res/xml/form_filling_preferences.xml rename to app/src/main/res/xml/preferences_form_filling.xml diff --git a/app/src/main/res/xml/keyboard_preferences.xml b/app/src/main/res/xml/preferences_keyboard.xml similarity index 100% rename from app/src/main/res/xml/keyboard_preferences.xml rename to app/src/main/res/xml/preferences_keyboard.xml diff --git a/art/ic_fingerprint_remove.svg b/art/ic_fingerprint_remove.svg new file mode 100644 index 000000000..137817263 --- /dev/null +++ b/art/ic_fingerprint_remove.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/fastlane/metadata/android/en-US/changelogs/23.txt b/fastlane/metadata/android/en-US/changelogs/23.txt index 40cc39168..e93447fcf 100644 --- a/fastlane/metadata/android/en-US/changelogs/23.txt +++ b/fastlane/metadata/android/en-US/changelogs/23.txt @@ -1,2 +1,3 @@ * New, more secure database creation workflow - * Add alias for history files (WARNING: history is erased) \ No newline at end of file + * Add alias for history files (WARNING: history is erased) + * New Biometric unlock (Fingerprint with new API) \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/changelogs/23.txt b/fastlane/metadata/android/fr-FR/changelogs/23.txt index d7b76b52f..6a9e35d45 100644 --- a/fastlane/metadata/android/fr-FR/changelogs/23.txt +++ b/fastlane/metadata/android/fr-FR/changelogs/23.txt @@ -1,2 +1,3 @@ * Nouveau workflow de création de base de données plus sécurisé - * Ajout d'un alias pour les fichiers d'historiques (ATTENTION: l'historique est effacé) \ No newline at end of file + * Ajout d'un alias pour les fichiers d'historiques (ATTENTION: l'historique est effacé) + * Nouveau déblocage par biométrique (Empreinte digitale avec nouvelle API) \ No newline at end of file