From e5ea1e35aaf188b2cd99736ec49d665a7ba9446a Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Mon, 20 Oct 2025 19:41:00 +0200 Subject: [PATCH] fix: Magikeyboard Auto search #2233 --- .../keepass/activities/EntryEditActivity.kt | 11 +- .../activities/FileDatabaseSelectActivity.kt | 8 +- .../keepass/activities/GroupActivity.kt | 25 +- .../activities/MainCredentialActivity.kt | 10 +- .../EntrySelectionHelper.kt | 18 +- .../activity/AutofillLauncherActivity.kt | 23 +- .../EntrySelectionLauncherActivity.kt | 312 +++++++----------- .../activity/HardwareKeyActivity.kt | 4 +- .../activity/PasskeyLauncherActivity.kt | 14 +- .../magikeyboard/MagikeyboardService.kt | 18 +- .../viewmodel/AutofillLauncherViewModel.kt | 33 +- .../viewmodel/CredentialLauncherViewModel.kt | 26 +- .../viewmodel/EntrySelectionViewModel.kt | 297 +++++++++++++++++ .../viewmodel/PasskeyLauncherViewModel.kt | 10 +- 14 files changed, 513 insertions(+), 296 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/EntrySelectionViewModel.kt diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index f339459fa..9ac3e99b0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -63,7 +63,6 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecia import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.TypeMode -import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.element.Attachment @@ -486,14 +485,12 @@ class EntryEditActivity : DatabaseLockActivity(), } private fun entryValidatedForKeyboardSelection(database: ContextualDatabase, entry: Entry) { - // Populate Magikeyboard with entry - MagikeyboardService.populateKeyboardAndMoveAppToBackground( - this, - entry.getEntryInfo(database) + // Build Magikeyboard response with the entry selected + this.buildSpecialModeResponseAndSetResult( + entryInfo = entry.getEntryInfo(database), + extras = buildEntryResult(entry) ) onValidateSpecialMode() - // Don't keep activity history for entry edition - finishForEntryResult(entry) } private fun entryValidatedForAutofill(database: ContextualDatabase, entry: Entry) { 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 3c8ebc179..28cd408fe 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -467,7 +467,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), * ------------------------- */ - fun launchForSearchResult( + fun launchForSearch( context: Context, searchInfo: SearchInfo ) { @@ -488,7 +488,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), context: Context, typeMode: TypeMode, searchInfo: SearchInfo? = null, - activityResultLauncher: ActivityResultLauncher? = null, + activityResultLauncher: ActivityResultLauncher?, ) { EntrySelectionHelper.startActivityForSelectionModeResult( context = context, @@ -512,10 +512,10 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(), ) { EntrySelectionHelper.startActivityForRegistrationModeResult( context = context, - activityResultLauncher = activityResultLauncher, intent = Intent(context, FileDatabaseSelectActivity::class.java), registerInfo = registerInfo, - typeMode = typeMode + typeMode = typeMode, + activityResultLauncher = activityResultLauncher ) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index f0bba2b56..e59551158 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -915,11 +915,8 @@ class GroupActivity : DatabaseLockActivity(), private fun entrySelectedForKeyboardSelection(database: ContextualDatabase, entry: Entry) { removeSearch() - // Populate Magikeyboard with entry - MagikeyboardService.populateKeyboardAndMoveAppToBackground( - this, - entry.getEntryInfo(database) - ) + // Build response with the entry selected + this.buildSpecialModeResponseAndSetResult(entry.getEntryInfo(database)) onValidateSpecialMode() } @@ -1521,7 +1518,7 @@ class GroupActivity : DatabaseLockActivity(), * Search Launch * ------------------------- */ - fun launchForSearchResult( + fun launchForSearch( context: Context, database: ContextualDatabase, searchInfo: SearchInfo, @@ -1536,13 +1533,18 @@ class GroupActivity : DatabaseLockActivity(), } } + /* + * ------------------------- + * Selection Launch + * ------------------------- + */ fun launchForSelection( context: Context, database: ContextualDatabase, typeMode: TypeMode, searchInfo: SearchInfo? = null, autoSearch: Boolean = false, - activityResultLauncher: ActivityResultLauncher? = null, + activityResultLauncher: ActivityResultLauncher?, ) { if (database.loaded) { checkTimeAndBuildIntent(context, null) { intent -> @@ -1610,7 +1612,7 @@ class GroupActivity : DatabaseLockActivity(), searchAction = { searchInfo -> // Search action if (database.loaded) { - launchForSearchResult(activity, + launchForSearch(activity, database, searchInfo, true) @@ -1632,11 +1634,7 @@ class GroupActivity : DatabaseLockActivity(), MagikeyboardService.performSelection( items = items, actionPopulateKeyboard = { entryInfo -> - // Keyboard populated - MagikeyboardService.populateKeyboardAndMoveAppToBackground( - activity, - entryInfo - ) + activity.buildSpecialModeResponseAndSetResult(items) onValidateSpecialMode() }, actionEntrySelection = { autoSearch -> @@ -1645,6 +1643,7 @@ class GroupActivity : DatabaseLockActivity(), database = database, typeMode = TypeMode.MAGIKEYBOARD, searchInfo = searchInfo, + activityResultLauncher = activityResultLauncher, autoSearch = autoSearch ) onLaunchActivitySpecialMode() diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt index 95ce84881..2c670a5f5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt @@ -809,7 +809,7 @@ class MainCredentialActivity : DatabaseModeActivity() { hardwareKey: HardwareKey?, typeMode: TypeMode, searchInfo: SearchInfo?, - activityResultLauncher: ActivityResultLauncher? = null, + activityResultLauncher: ActivityResultLauncher? ) { buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent -> EntrySelectionHelper.startActivityForSelectionModeResult( @@ -831,20 +831,20 @@ class MainCredentialActivity : DatabaseModeActivity() { @Throws(FileNotFoundException::class) fun launchForRegistration( activity: Activity, - activityResultLauncher: ActivityResultLauncher?, databaseFile: Uri, keyFile: Uri?, hardwareKey: HardwareKey?, typeMode: TypeMode, - registerInfo: RegisterInfo? + registerInfo: RegisterInfo?, + activityResultLauncher: ActivityResultLauncher? ) { buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent -> EntrySelectionHelper.startActivityForRegistrationModeResult( context = activity, - activityResultLauncher = activityResultLauncher, intent = intent, typeMode = typeMode, - registerInfo = registerInfo + registerInfo = registerInfo, + activityResultLauncher = activityResultLauncher, ) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/EntrySelectionHelper.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/EntrySelectionHelper.kt index bf60df362..c5f31f4fb 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/EntrySelectionHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/EntrySelectionHelper.kt @@ -34,6 +34,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.IconCompat import com.kunzisoft.keepass.R import com.kunzisoft.keepass.database.ContextualDatabase +import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo @@ -43,6 +44,7 @@ import com.kunzisoft.keepass.utils.getParcelableExtraCompat import com.kunzisoft.keepass.utils.getParcelableList import com.kunzisoft.keepass.utils.putEnumExtra import com.kunzisoft.keepass.utils.putParcelableList +import java.io.IOException import java.util.UUID object EntrySelectionHelper { @@ -233,12 +235,26 @@ object EntrySelectionHelper { removeExtra(EXTRA_NODE_ID) } + /** + * Retrieve nodes ids from [intent] and get the corresponding entry info list in [database] + */ + fun Intent.retrieveAndRemoveEntries(database: ContextualDatabase): List { + val nodesIds = retrieveNodesIds() + ?: throw IOException("NodesIds is null") + removeNodesIds() + return nodesIds.mapNotNull { nodeId -> + database + .getEntryById(NodeIdUUID(nodeId)) + ?.getEntryInfo(database) + } + } + /** * Intent sender uses special retains data in callback */ fun isIntentSenderMode(specialMode: SpecialMode, typeMode: TypeMode): Boolean { return (specialMode == SpecialMode.SELECTION - && (typeMode == TypeMode.AUTOFILL || typeMode == TypeMode.PASSKEY)) + && (typeMode == TypeMode.MAGIKEYBOARD || typeMode == TypeMode.AUTOFILL || typeMode == TypeMode.PASSKEY)) || (specialMode == SpecialMode.REGISTRATION && (typeMode == TypeMode.AUTOFILL || typeMode == TypeMode.PASSKEY)) } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt index 1fad3d25d..aa3c34c04 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt @@ -45,7 +45,6 @@ import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper.addAutof import com.kunzisoft.keepass.credentialprovider.viewmodel.AutofillLauncherViewModel import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel import com.kunzisoft.keepass.database.ContextualDatabase -import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode @@ -87,10 +86,6 @@ class AutofillLauncherActivity : DatabaseModeActivity() { showBlockRestartMessage() autofillLauncherViewModel.cancelResult() } - is AutofillLauncherViewModel.UIState.ShowReadOnlyMessage -> { - showReadOnlySaveMessage() - autofillLauncherViewModel.cancelResult() - } is AutofillLauncherViewModel.UIState.ShowAutofillSuggestionMessage -> { showAutofillSuggestionMessage() } @@ -101,8 +96,8 @@ class AutofillLauncherActivity : DatabaseModeActivity() { // Retrieve the UI autofillLauncherViewModel.credentialUiState.collect { uiState -> when (uiState) { - is CredentialLauncherViewModel.UIState.Loading -> {} - is CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection -> { + is CredentialLauncherViewModel.CredentialState.Loading -> {} + is CredentialLauncherViewModel.CredentialState.LaunchGroupActivityForSelection -> { GroupActivity.launchForSelection( context = this@AutofillLauncherActivity, database = uiState.database, @@ -111,7 +106,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() { activityResultLauncher = mAutofillSelectionActivityResultLauncher, ) } - is CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> { + is CredentialLauncherViewModel.CredentialState.LaunchGroupActivityForRegistration -> { GroupActivity.launchForRegistration( context = this@AutofillLauncherActivity, database = uiState.database, @@ -120,7 +115,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() { activityResultLauncher = mAutofillRegistrationActivityResultLauncher ) } - is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> { + is CredentialLauncherViewModel.CredentialState.LaunchFileDatabaseSelectActivityForSelection -> { FileDatabaseSelectActivity.launchForSelection( context = this@AutofillLauncherActivity, searchInfo = uiState.searchInfo, @@ -128,7 +123,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() { activityResultLauncher = mAutofillSelectionActivityResultLauncher ) } - is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> { + is CredentialLauncherViewModel.CredentialState.LaunchFileDatabaseSelectActivityForRegistration -> { FileDatabaseSelectActivity.launchForRegistration( context = this@AutofillLauncherActivity, registerInfo = uiState.registerInfo, @@ -136,14 +131,14 @@ class AutofillLauncherActivity : DatabaseModeActivity() { activityResultLauncher = mAutofillRegistrationActivityResultLauncher, ) } - is CredentialLauncherViewModel.UIState.SetActivityResult -> { + is CredentialLauncherViewModel.CredentialState.SetActivityResult -> { setActivityResult( lockDatabase = uiState.lockDatabase, resultCode = uiState.resultCode, data = uiState.data ) } - is CredentialLauncherViewModel.UIState.ShowError -> { + is CredentialLauncherViewModel.CredentialState.ShowError -> { toastError(uiState.error) autofillLauncherViewModel.cancelResult() } @@ -174,10 +169,6 @@ class AutofillLauncherActivity : DatabaseModeActivity() { ).show() } - private fun showReadOnlySaveMessage() { - toastError(RegisterInReadOnlyDatabaseException()) - } - companion object { private val TAG = AutofillLauncherActivity::class.java.name diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt index 7bb3936e8..561d64184 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt @@ -22,20 +22,22 @@ package com.kunzisoft.keepass.credentialprovider.activity import android.content.Context import android.content.Intent import android.os.Bundle -import androidx.core.net.toUri +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity import com.kunzisoft.keepass.activities.GroupActivity import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity -import com.kunzisoft.keepass.credentialprovider.TypeMode +import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSearchInfo +import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService +import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel +import com.kunzisoft.keepass.credentialprovider.viewmodel.EntrySelectionViewModel import com.kunzisoft.keepass.database.ContextualDatabase -import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException -import com.kunzisoft.keepass.database.helper.SearchHelper import com.kunzisoft.keepass.model.SearchInfo -import com.kunzisoft.keepass.otp.OtpEntryFields -import com.kunzisoft.keepass.utils.KeyboardUtil.isKeyboardActivatedInSettings -import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.view.toastError +import kotlinx.coroutines.launch /** * Activity to search or select entry in database, @@ -43,201 +45,129 @@ import com.kunzisoft.keepass.view.toastError */ class EntrySelectionLauncherActivity : DatabaseModeActivity() { - override fun applyCustomStyle(): Boolean { - return false - } + private val entrySelectionViewModel: EntrySelectionViewModel by viewModels() - override fun finishActivityIfReloadRequested(): Boolean { - return false + private var mEntrySelectionActivityResultLauncher: ActivityResultLauncher? = + this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + entrySelectionViewModel.manageSelectionResult(it) + } + + override fun applyCustomStyle() = false + + override fun finishActivityIfReloadRequested() = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + entrySelectionViewModel.initialize() + lifecycleScope.launch { + // Initialize the parameters + entrySelectionViewModel.uiState.collect { uiState -> + when (uiState) { + is EntrySelectionViewModel.UIState.Loading -> {} + is EntrySelectionViewModel.UIState.PopulateKeyboard -> { + MagikeyboardService.addEntryAndLaunchNotificationIfAllowed( + context = this@EntrySelectionLauncherActivity, + entry = uiState.entryInfo, + toast = true + ) + } + is EntrySelectionViewModel.UIState.LaunchFileDatabaseSelectForSearch -> { + FileDatabaseSelectActivity.launchForSearch( + context = this@EntrySelectionLauncherActivity, + searchInfo = uiState.searchInfo + ) + } + is EntrySelectionViewModel.UIState.LaunchGroupActivityForSearch -> { + GroupActivity.launchForSearch( + context = this@EntrySelectionLauncherActivity, + database = uiState.database, + searchInfo = uiState.searchInfo + ) + } + } + } + } + lifecycleScope.launch { + // Retrieve the UI + entrySelectionViewModel.credentialUiState.collect { uiState -> + when (uiState) { + is CredentialLauncherViewModel.CredentialState.Loading -> {} + is CredentialLauncherViewModel.CredentialState.LaunchGroupActivityForSelection -> { + GroupActivity.launchForSelection( + context = this@EntrySelectionLauncherActivity, + database = uiState.database, + searchInfo = uiState.searchInfo, + typeMode = uiState.typeMode, + activityResultLauncher = mEntrySelectionActivityResultLauncher + ) + } + is CredentialLauncherViewModel.CredentialState.LaunchGroupActivityForRegistration -> { + GroupActivity.launchForRegistration( + context = this@EntrySelectionLauncherActivity, + database = uiState.database, + registerInfo = uiState.registerInfo, + typeMode = uiState.typeMode, + activityResultLauncher = null // Null to not get any callback + ) + finish() + } + is CredentialLauncherViewModel.CredentialState.LaunchFileDatabaseSelectActivityForSelection -> { + FileDatabaseSelectActivity.launchForSelection( + context = this@EntrySelectionLauncherActivity, + searchInfo = uiState.searchInfo, + typeMode = uiState.typeMode, + activityResultLauncher = mEntrySelectionActivityResultLauncher + ) + } + is CredentialLauncherViewModel.CredentialState.LaunchFileDatabaseSelectActivityForRegistration -> { + FileDatabaseSelectActivity.launchForRegistration( + context = this@EntrySelectionLauncherActivity, + registerInfo = uiState.registerInfo, + typeMode = uiState.typeMode, + activityResultLauncher = null // Null to not get any callback + ) + finish() + } + is CredentialLauncherViewModel.CredentialState.SetActivityResult -> { + setActivityResult( + lockDatabase = uiState.lockDatabase, + resultCode = uiState.resultCode, + data = uiState.data + ) + } + is CredentialLauncherViewModel.CredentialState.ShowError -> { + toastError(uiState.error) + entrySelectionViewModel.cancelResult() + } + } + } + } } override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) { super.onUnknownDatabaseRetrieved(database) - - val keySelectionBundle = intent.getBundleExtra(KEY_SELECTION_BUNDLE) - if (keySelectionBundle != null) { - // To manage package name - var searchInfo = SearchInfo() - keySelectionBundle.getParcelableCompat(KEY_SEARCH_INFO)?.let { mSearchInfo -> - searchInfo = mSearchInfo - } - launch(database, searchInfo) - } else { - // To manage share - var sharedWebDomain: String? = null - var otpString: String? = null - - when (intent?.action) { - Intent.ACTION_SEND -> { - if ("text/plain" == intent.type) { - // Retrieve web domain or OTP - intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra -> - if (OtpEntryFields.isOTPUri(extra)) - otpString = extra - else - sharedWebDomain = extra.toUri().host - } - } - launchSelection(database, sharedWebDomain, otpString) - } - Intent.ACTION_VIEW -> { - // Retrieve OTP - intent.dataString?.let { extra -> - if (OtpEntryFields.isOTPUri(extra)) - otpString = extra - } - launchSelection(database, null, otpString) - } - else -> { - if (database != null) { - GroupActivity.launch(this, database) - } else { - FileDatabaseSelectActivity.launch(this) - } - } - } - } - finish() + entrySelectionViewModel.launchActionIfNeeded(intent, mSpecialMode, database) } - private fun launchSelection(database: ContextualDatabase?, - sharedWebDomain: String?, - otpString: String?) { - // Build domain search param - val searchInfo = SearchInfo().apply { - this.webDomain = sharedWebDomain - this.otpString = otpString - } - launch(database, searchInfo) - } - - private fun launch(database: ContextualDatabase?, - searchInfo: SearchInfo) { - - // Setting to integrate Magikeyboard - val searchShareForMagikeyboard = isKeyboardActivatedInSettings() - - // If database is open - val readOnly = database?.isReadOnly != false - SearchHelper.checkAutoSearchInfo( - context = this, - database = database, - searchInfo = searchInfo, - onItemsFound = { openedDatabase, items -> - // Items found - if (searchInfo.otpString != null) { - if (!readOnly) { - GroupActivity.launchForRegistration( - context = this, - activityResultLauncher = null, - database = openedDatabase, - registerInfo = searchInfo.toRegisterInfo(), - typeMode = TypeMode.DEFAULT - ) - } else { - toastError(RegisterInReadOnlyDatabaseException()) - } - } else if (searchShareForMagikeyboard) { - MagikeyboardService.performSelection( - items, - { entryInfo -> - // Automatically populate keyboard - MagikeyboardService.populateKeyboardAndMoveAppToBackground( - this, - entryInfo - ) - }, - { autoSearch -> - GroupActivity.launchForSelection( - context = this, - database = openedDatabase, - typeMode = TypeMode.MAGIKEYBOARD, - searchInfo = searchInfo, - autoSearch = autoSearch - ) - } - ) - } else { - GroupActivity.launchForSearchResult( - this, - openedDatabase, - searchInfo, - true - ) - } - }, - onItemNotFound = { openedDatabase -> - // Show the database UI to select the entry - if (searchInfo.otpString != null) { - if (!readOnly) { - GroupActivity.launchForRegistration( - context = this, - activityResultLauncher = null, - database = openedDatabase, - registerInfo = searchInfo.toRegisterInfo(), - typeMode = TypeMode.DEFAULT - ) - } else { - toastError(RegisterInReadOnlyDatabaseException()) - } - } else if (searchShareForMagikeyboard) { - GroupActivity.launchForSelection( - context = this, - database = openedDatabase, - typeMode = TypeMode.MAGIKEYBOARD, - searchInfo = searchInfo, - autoSearch = false - ) - } else { - GroupActivity.launchForSearchResult( - this, - openedDatabase, - searchInfo, - false - ) - } - }, - onDatabaseClosed = { - // If database not open - if (searchInfo.otpString != null) { - FileDatabaseSelectActivity.launchForRegistration( - context = this, - activityResultLauncher = null, - registerInfo = searchInfo.toRegisterInfo(), - typeMode = TypeMode.DEFAULT - ) - } else if (searchShareForMagikeyboard) { - FileDatabaseSelectActivity.launchForSelection( - context = this, - typeMode = TypeMode.MAGIKEYBOARD, - searchInfo = searchInfo - ) - } else { - FileDatabaseSelectActivity.launchForSearchResult( - this, - searchInfo - ) - } - } - ) + override fun onDestroy() { + super.onDestroy() } companion object { - private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE" - private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO" - - fun launch(context: Context, - searchInfo: SearchInfo? = null) { - val intent = Intent(context, EntrySelectionLauncherActivity::class.java).apply { - putExtra(KEY_SELECTION_BUNDLE, Bundle().apply { - putParcelable(KEY_SEARCH_INFO, searchInfo) - }) - } - // New task needed because don't launch from an Activity context - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - context.startActivity(intent) + fun launch( + context: Context, + searchInfo: SearchInfo? = null + ) { + context.startActivity(Intent( + context, + EntrySelectionLauncherActivity::class.java + ).apply { + addSearchInfo(searchInfo) + // New task needed because don't launch from an Activity context + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + }) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/HardwareKeyActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/HardwareKeyActivity.kt index 8759cc86e..d486bd02e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/HardwareKeyActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/HardwareKeyActivity.kt @@ -76,14 +76,14 @@ class HardwareKeyActivity: DatabaseModeActivity(){ lifecycleScope.launch { mHardwareKeyLauncherViewModel.credentialUiState.collect { uiState -> when (uiState) { - is CredentialLauncherViewModel.UIState.SetActivityResult -> { + is CredentialLauncherViewModel.CredentialState.SetActivityResult -> { setActivityResult( lockDatabase = uiState.lockDatabase, resultCode = uiState.resultCode, data = uiState.data ) } - is CredentialLauncherViewModel.UIState.ShowError -> { + is CredentialLauncherViewModel.CredentialState.ShowError -> { toastError(uiState.error) mHardwareKeyLauncherViewModel.cancelResult() } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt index 1fc0c04c6..077034060 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt @@ -112,19 +112,19 @@ class PasskeyLauncherActivity : DatabaseLockActivity() { lifecycleScope.launch { passkeyLauncherViewModel.credentialUiState.collect { uiState -> when (uiState) { - is CredentialLauncherViewModel.UIState.Loading -> {} - is CredentialLauncherViewModel.UIState.SetActivityResult -> { + is CredentialLauncherViewModel.CredentialState.Loading -> {} + is CredentialLauncherViewModel.CredentialState.SetActivityResult -> { setActivityResult( lockDatabase = uiState.lockDatabase, resultCode = uiState.resultCode, data = uiState.data ) } - is CredentialLauncherViewModel.UIState.ShowError -> { + is CredentialLauncherViewModel.CredentialState.ShowError -> { toastError(uiState.error) passkeyLauncherViewModel.cancelResult() } - is CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection -> { + is CredentialLauncherViewModel.CredentialState.LaunchGroupActivityForSelection -> { GroupActivity.launchForSelection( context = this@PasskeyLauncherActivity, database = uiState.database, @@ -133,7 +133,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() { activityResultLauncher = mPasskeySelectionActivityResultLauncher ) } - is CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> { + is CredentialLauncherViewModel.CredentialState.LaunchGroupActivityForRegistration -> { GroupActivity.launchForRegistration( context = this@PasskeyLauncherActivity, database = uiState.database, @@ -142,7 +142,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() { activityResultLauncher = mPasskeyRegistrationActivityResultLauncher ) } - is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> { + is CredentialLauncherViewModel.CredentialState.LaunchFileDatabaseSelectActivityForSelection -> { FileDatabaseSelectActivity.launchForSelection( context = this@PasskeyLauncherActivity, typeMode = uiState.typeMode, @@ -150,7 +150,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() { activityResultLauncher = mPasskeySelectionActivityResultLauncher ) } - is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> { + is CredentialLauncherViewModel.CredentialState.LaunchFileDatabaseSelectActivityForRegistration -> { FileDatabaseSelectActivity.launchForRegistration( context = this@PasskeyLauncherActivity, typeMode = uiState.typeMode, diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/magikeyboard/MagikeyboardService.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/magikeyboard/MagikeyboardService.kt index 875609bd4..8677698bd 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/magikeyboard/MagikeyboardService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/magikeyboard/MagikeyboardService.kt @@ -462,9 +462,11 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL KeyboardEntryNotificationService.launchNotificationIfAllowed(context, entry, toast) } - fun performSelection(items: List, - actionPopulateKeyboard: (entryInfo: EntryInfo) -> Unit, - actionEntrySelection: (autoSearch: Boolean) -> Unit) { + fun performSelection( + items: List, + actionPopulateKeyboard: (entryInfo: EntryInfo) -> Unit, + actionEntrySelection: (autoSearch: Boolean) -> Unit + ) { EntrySelectionHelper.performSelection( items = items, actionPopulateCredentialProvider = { itemFound -> @@ -478,15 +480,5 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL actionEntrySelection = actionEntrySelection ) } - - fun populateKeyboardAndMoveAppToBackground(activity: Activity, - entry: EntryInfo, - toast: Boolean = true) { - // Populate Magikeyboard with entry - addEntryAndLaunchNotificationIfAllowed(activity, entry, toast) - // Consume the selection mode - activity.intent.removeModes() - activity.moveTaskToBack(true) - } } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/AutofillLauncherViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/AutofillLauncherViewModel.kt index b16a552fa..13b109a5f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/AutofillLauncherViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/AutofillLauncherViewModel.kt @@ -9,8 +9,7 @@ import android.util.Log import androidx.activity.result.ActivityResult import androidx.annotation.RequiresApi import androidx.lifecycle.viewModelScope -import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeNodesIds -import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNodesIds +import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveAndRemoveEntries import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSpecialMode @@ -21,7 +20,7 @@ import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper.retrieveAutofillComponent import com.kunzisoft.keepass.credentialprovider.autofill.KeeAutofillService import com.kunzisoft.keepass.database.ContextualDatabase -import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException import com.kunzisoft.keepass.database.helper.SearchHelper import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.SearchInfo @@ -119,7 +118,7 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie onItemNotFound = { openedDatabase -> // Show the database UI to select the entry mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection( + CredentialState.LaunchGroupActivityForSelection( database = openedDatabase, searchInfo = searchInfo, typeMode = TypeMode.AUTOFILL @@ -128,7 +127,7 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie onDatabaseClosed = { // If database not open mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection( + CredentialState.LaunchFileDatabaseSelectActivityForSelection( searchInfo = searchInfo, typeMode = TypeMode.AUTOFILL ) @@ -156,17 +155,10 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie Log.d(TAG, "Autofill selection result") if (intent == null) throw IOException("Intent is null") - val nodesIds = intent.retrieveNodesIds() - ?: throw IOException("NodesIds is null") - intent.removeNodesIds() + val entries = intent.retrieveAndRemoveEntries(database) val autofillComponent = mAutofillComponent if (autofillComponent == null) throw IOException("Autofill component is null") - val entries = nodesIds.mapNotNull { nodeId -> - database - .getEntryById(NodeIdUUID(nodeId)) - ?.getEntryInfo(database) - } withContext(Dispatchers.Main) { AutofillHelper.buildResponse( context = getApplication(), @@ -211,32 +203,36 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie if (!readOnly) { // Show the database UI to select the entry mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration( + CredentialState.LaunchGroupActivityForRegistration( database = openedDatabase, registerInfo = registerInfo, typeMode = TypeMode.AUTOFILL ) } else { - mUiState.value = UIState.ShowReadOnlyMessage + mCredentialUiState.value = CredentialState.ShowError( + RegisterInReadOnlyDatabaseException() + ) } }, onItemNotFound = { openedDatabase -> if (!readOnly) { // Show the database UI to select the entry mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration( + CredentialState.LaunchGroupActivityForRegistration( database = openedDatabase, registerInfo = registerInfo, typeMode = TypeMode.AUTOFILL ) } else { - mUiState.value = UIState.ShowReadOnlyMessage + mCredentialUiState.value = CredentialState.ShowError( + RegisterInReadOnlyDatabaseException() + ) } }, onDatabaseClosed = { // If database not open mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration( + CredentialState.LaunchFileDatabaseSelectActivityForRegistration( registerInfo = registerInfo, typeMode = TypeMode.AUTOFILL ) @@ -274,7 +270,6 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie sealed class UIState { object Loading: UIState() object ShowBlockRestartMessage: UIState() - object ShowReadOnlyMessage: UIState() object ShowAutofillSuggestionMessage: UIState() } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt index 7eb876f2a..01d441831 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/CredentialLauncherViewModel.kt @@ -25,12 +25,12 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie protected var isResultLauncherRegistered: Boolean = false private var mSelectionResult: ActivityResult? = null - protected val mCredentialUiState = MutableStateFlow(UIState.Loading) - val credentialUiState: StateFlow = mCredentialUiState + protected val mCredentialUiState = MutableStateFlow(CredentialState.Loading) + val credentialUiState: StateFlow = mCredentialUiState fun showError(error: Throwable) { Log.e(TAG, "Error on credential provider launch", error) - mCredentialUiState.value = UIState.ShowError(error) + mCredentialUiState.value = CredentialState.ShowError(error) } open fun onResult() { @@ -41,7 +41,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie fun setResult(intent: Intent, lockDatabase: Boolean = false) { // Remove the launcher register onResult() - mCredentialUiState.value = UIState.SetActivityResult( + mCredentialUiState.value = CredentialState.SetActivityResult( lockDatabase = lockDatabase, resultCode = RESULT_OK, data = intent @@ -50,7 +50,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie fun cancelResult(lockDatabase: Boolean = false) { onResult() - mCredentialUiState.value = UIState.SetActivityResult( + mCredentialUiState.value = CredentialState.SetActivityResult( lockDatabase = lockDatabase, resultCode = RESULT_CANCELED ) @@ -115,34 +115,34 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie database: ContextualDatabase? ) - sealed class UIState { - object Loading : UIState() + sealed class CredentialState { + object Loading : CredentialState() data class LaunchGroupActivityForSelection( val database: ContextualDatabase, val searchInfo: SearchInfo?, val typeMode: TypeMode - ): UIState() + ): CredentialState() data class LaunchGroupActivityForRegistration( val database: ContextualDatabase, val registerInfo: RegisterInfo?, val typeMode: TypeMode - ): UIState() + ): CredentialState() data class LaunchFileDatabaseSelectActivityForSelection( val searchInfo: SearchInfo?, val typeMode: TypeMode - ): UIState() + ): CredentialState() data class LaunchFileDatabaseSelectActivityForRegistration( val registerInfo: RegisterInfo?, val typeMode: TypeMode - ): UIState() + ): CredentialState() data class SetActivityResult( val lockDatabase: Boolean, val resultCode: Int, val data: Intent? = null - ): UIState() + ): CredentialState() data class ShowError( val error: Throwable - ): UIState() + ): CredentialState() } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/EntrySelectionViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/EntrySelectionViewModel.kt new file mode 100644 index 000000000..703c4d385 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/EntrySelectionViewModel.kt @@ -0,0 +1,297 @@ +package com.kunzisoft.keepass.credentialprovider.viewmodel + +import android.app.Activity.RESULT_CANCELED +import android.app.Activity.RESULT_OK +import android.app.Application +import android.content.Intent +import android.util.Log +import androidx.activity.result.ActivityResult +import androidx.core.net.toUri +import androidx.lifecycle.viewModelScope +import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveAndRemoveEntries +import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNodeId +import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo +import com.kunzisoft.keepass.credentialprovider.SpecialMode +import com.kunzisoft.keepass.credentialprovider.TypeMode +import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService +import com.kunzisoft.keepass.database.ContextualDatabase +import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException +import com.kunzisoft.keepass.database.helper.SearchHelper +import com.kunzisoft.keepass.model.EntryInfo +import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.otp.OtpEntryFields +import com.kunzisoft.keepass.utils.KeyboardUtil.isKeyboardActivatedInSettings +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class EntrySelectionViewModel(application: Application): CredentialLauncherViewModel(application) { + + private var searchShareForMagikeyboard: Boolean = false + private var mLockDatabaseAfterSelection: Boolean = false + private val mUiState = MutableStateFlow(UIState.Loading) + val uiState: StateFlow = mUiState + + fun initialize() { + searchShareForMagikeyboard = getApplication().isKeyboardActivatedInSettings() + mLockDatabaseAfterSelection = false // TODO Close database after selection + } + + override fun launchActionIfNeeded( + intent: Intent, + specialMode: SpecialMode, + database: ContextualDatabase? + ) { + // Launch with database when a nodeId is present + if ((database != null && database.loaded) || intent.retrieveNodeId() == null) { + super.launchActionIfNeeded(intent, specialMode, database) + } + } + + override suspend fun launchAction( + intent: Intent, + specialMode: SpecialMode, + database: ContextualDatabase? + ) { + val searchInfo: SearchInfo? = intent.retrieveSearchInfo() + if (searchInfo != null) { + launch(database, searchInfo) + } else { + // To manage share + var sharedWebDomain: String? = null + var otpString: String? = null + + when (intent.action) { + Intent.ACTION_SEND -> { + if ("text/plain" == intent.type) { + // Retrieve web domain or OTP + intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra -> + if (OtpEntryFields.isOTPUri(extra)) + otpString = extra + else + sharedWebDomain = extra.toUri().host + } + } + launchSelection(database, sharedWebDomain, otpString) + } + Intent.ACTION_VIEW -> { + // Retrieve OTP + intent.dataString?.let { extra -> + if (OtpEntryFields.isOTPUri(extra)) + otpString = extra + } + launchSelection(database, null, otpString) + } + else -> { + if (database != null && database.loaded) { + mUiState.value = UIState.LaunchGroupActivityForSearch( + database = database, + searchInfo = SearchInfo() + ) + } else { + mUiState.value = UIState.LaunchFileDatabaseSelectForSearch( + searchInfo = SearchInfo() + ) + } + } + } + } + } + + // ------------- + // Selection + // ------------- + + private fun launchSelection( + database: ContextualDatabase?, + sharedWebDomain: String?, + otpString: String? + ) { + // Build domain search param + val searchInfo = SearchInfo().apply { + this.webDomain = sharedWebDomain + this.otpString = otpString + } + launch(database, searchInfo) + } + + private fun launch( + database: ContextualDatabase?, + searchInfo: SearchInfo + ) { + // If database is open + val readOnly = database?.isReadOnly != false + SearchHelper.checkAutoSearchInfo( + context = getApplication(), + database = database, + searchInfo = searchInfo, + onItemsFound = { openedDatabase, items -> + // Items found + if (searchInfo.otpString != null) { + if (!readOnly) { + mCredentialUiState.value = + CredentialState.LaunchGroupActivityForRegistration( + database = openedDatabase, + registerInfo = searchInfo.toRegisterInfo(), + typeMode = TypeMode.DEFAULT + ) + } else { + mCredentialUiState.value = CredentialState.ShowError( + RegisterInReadOnlyDatabaseException() + ) + } + } else if (searchShareForMagikeyboard) { + MagikeyboardService.performSelection( + items, + { entryInfo -> + populateKeyboard(entryInfo) + }, + { autoSearch -> + mCredentialUiState.value = CredentialState.LaunchGroupActivityForSelection( + database = openedDatabase, + searchInfo = searchInfo, + typeMode = TypeMode.MAGIKEYBOARD + ) + } + ) + } else { + mUiState.value = UIState.LaunchGroupActivityForSearch( + database = openedDatabase, + searchInfo = searchInfo + ) + } + }, + onItemNotFound = { openedDatabase -> + // Show the database UI to select the entry + if (searchInfo.otpString != null) { + if (!readOnly) { + mCredentialUiState.value = + CredentialState.LaunchGroupActivityForRegistration( + database = openedDatabase, + registerInfo = searchInfo.toRegisterInfo(), + typeMode = TypeMode.DEFAULT + ) + } else { + mCredentialUiState.value = CredentialState.ShowError( + RegisterInReadOnlyDatabaseException() + ) + } + } else if (searchShareForMagikeyboard) { + mCredentialUiState.value = CredentialState.LaunchGroupActivityForSelection( + database = openedDatabase, + searchInfo = searchInfo, + typeMode = TypeMode.MAGIKEYBOARD + ) + } else { + mUiState.value = UIState.LaunchGroupActivityForSearch( + database = openedDatabase, + searchInfo = searchInfo + ) + } + }, + onDatabaseClosed = { + // If database not open + if (searchInfo.otpString != null) { + mCredentialUiState.value = CredentialState.LaunchFileDatabaseSelectActivityForRegistration( + registerInfo = searchInfo.toRegisterInfo(), + typeMode = TypeMode.DEFAULT + ) + } else if (searchShareForMagikeyboard) { + mCredentialUiState.value = CredentialState.LaunchFileDatabaseSelectActivityForSelection( + searchInfo = searchInfo, + typeMode = TypeMode.MAGIKEYBOARD + ) + } else { + mUiState.value = UIState.LaunchFileDatabaseSelectForSearch( + searchInfo = searchInfo + ) + } + } + ) + } + + private fun populateKeyboard(entryInfo: EntryInfo) { + // Automatically populate keyboard + mUiState.value = UIState.PopulateKeyboard(entryInfo) + setResult(Intent(), lockDatabase = mLockDatabaseAfterSelection) + } + + override fun manageSelectionResult( + database: ContextualDatabase, + activityResult: ActivityResult + ) { + super.manageSelectionResult(database, activityResult) + val intent = activityResult.data + viewModelScope.launch(CoroutineExceptionHandler { _, e -> + Log.e(TAG, "Unable to create selection response for Magikeyboard", e) + showError(e) + }) { + when (activityResult.resultCode) { + RESULT_OK -> { + withContext(Dispatchers.IO) { + Log.d(TAG, "Magikeyboard selection result") + if (intent == null) + throw IOException("Intent is null") + val entries = intent.retrieveAndRemoveEntries(database) + withContext(Dispatchers.Main) { + // Populate Magikeyboard with entry + entries.firstOrNull()?.let { entryInfo -> + populateKeyboard(entryInfo) + } // TODO Manage multiple entries in Magikeyboard + } + } + } + RESULT_CANCELED -> { + withContext(Dispatchers.Main) { + cancelResult() + } + } + } + } + } + + override fun manageRegistrationResult(activityResult: ActivityResult) { + super.manageRegistrationResult(activityResult) + viewModelScope.launch(CoroutineExceptionHandler { _, e -> + Log.e(TAG, "Unable to create selection response for Magikeyboard", e) + showError(e) + }) { + when (activityResult.resultCode) { + RESULT_OK -> { + // Empty data result + // TODO Show Toast indicating value is saved + withContext(Dispatchers.Main) { + setResult(Intent(), lockDatabase = false) + } + } + RESULT_CANCELED -> { + withContext(Dispatchers.Main) { + cancelResult() + } + } + } + } + } + + sealed class UIState { + object Loading: UIState() + data class PopulateKeyboard( + val entryInfo: EntryInfo + ): UIState() + data class LaunchFileDatabaseSelectForSearch( + val searchInfo: SearchInfo + ): UIState() + data class LaunchGroupActivityForSearch( + val database: ContextualDatabase, + val searchInfo: SearchInfo + ): UIState() + } + + companion object { + private val TAG = EntrySelectionViewModel::class.java.name + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/PasskeyLauncherViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/PasskeyLauncherViewModel.kt index 138cef430..2e49c6843 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/PasskeyLauncherViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/viewmodel/PasskeyLauncherViewModel.kt @@ -238,7 +238,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView "launch manual selection in opened database" ) mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection( + CredentialState.LaunchGroupActivityForSelection( database = openedDatabase, searchInfo = searchInfo, typeMode = TypeMode.PASSKEY @@ -247,7 +247,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView onDatabaseClosed = { Log.d(TAG, "Manual passkey selection in closed database") mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection( + CredentialState.LaunchFileDatabaseSelectActivityForSelection( searchInfo = searchInfo, typeMode = TypeMode.PASSKEY ) @@ -426,7 +426,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView "but launch manual registration for a new entry" ) mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration( + CredentialState.LaunchGroupActivityForRegistration( database = openedDatabase, registerInfo = registerInfo, typeMode = TypeMode.PASSKEY @@ -435,7 +435,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView onItemNotFound = { openedDatabase -> Log.d(TAG, "Launch new manual registration in opened database") mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration( + CredentialState.LaunchGroupActivityForRegistration( database = openedDatabase, registerInfo = registerInfo, typeMode = TypeMode.PASSKEY @@ -444,7 +444,7 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView onDatabaseClosed = { Log.d(TAG, "Manual passkey registration in closed database") mCredentialUiState.value = - CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration( + CredentialState.LaunchFileDatabaseSelectActivityForRegistration( registerInfo = registerInfo, typeMode = TypeMode.PASSKEY )