mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Autofill refactoring
This commit is contained in:
@@ -56,9 +56,10 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecialModeResponseAndSetResult
|
||||
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.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
@@ -203,8 +204,8 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
mDatabase,
|
||||
entryId,
|
||||
parentId,
|
||||
EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
|
||||
?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)?.toRegisterInfo()
|
||||
intent.retrieveRegisterInfo()
|
||||
?: intent.retrieveSearchInfo()?.toRegisterInfo()
|
||||
)
|
||||
|
||||
// To retrieve attachment
|
||||
@@ -378,7 +379,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
intent = intent,
|
||||
defaultAction = {},
|
||||
searchAction = {},
|
||||
selectionAction = { intentSender, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSender, typeMode, searchInfo ->
|
||||
when(typeMode) {
|
||||
TypeMode.DEFAULT -> {}
|
||||
TypeMode.MAGIKEYBOARD ->
|
||||
@@ -396,7 +397,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
TypeMode.PASSKEY ->
|
||||
entryValidatedForPasskeyRegistration(database, entrySave.newEntry)
|
||||
TypeMode.AUTOFILL ->
|
||||
entryValidatedForAutofillRegistration(entrySave.newEntry)
|
||||
entryValidatedForAutofillRegistration(database, entrySave.newEntry)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -444,7 +445,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
searchAction = {
|
||||
// Nothing when search retrieved
|
||||
},
|
||||
selectionAction = { intentSender, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSender, typeMode, searchInfo ->
|
||||
when(typeMode) {
|
||||
TypeMode.DEFAULT -> {}
|
||||
TypeMode.MAGIKEYBOARD ->
|
||||
@@ -463,7 +464,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
TypeMode.PASSKEY ->
|
||||
entryValidatedForPasskeyRegistration(database, entry)
|
||||
TypeMode.AUTOFILL ->
|
||||
entryValidatedForAutofillRegistration(entry)
|
||||
entryValidatedForAutofillRegistration(database, entry)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -496,9 +497,10 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
private fun entryValidatedForAutofillSelection(database: ContextualDatabase, entry: Entry) {
|
||||
// Build Autofill response with the entry selected
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
|
||||
database,
|
||||
entry.getEntryInfo(database))
|
||||
this.buildSpecialModeResponseAndSetResult(
|
||||
entryInfo = entry.getEntryInfo(database),
|
||||
extras = buildEntryResult(entry)
|
||||
)
|
||||
}
|
||||
onValidateSpecialMode()
|
||||
}
|
||||
@@ -506,20 +508,21 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
private fun entryValidatedForPasskeySelection(database: ContextualDatabase, entry: Entry) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
this.buildPasskeyResponseAndSetResult(
|
||||
entryInfo = entry.getEntryInfo(database)
|
||||
entryInfo = entry.getEntryInfo(database),
|
||||
extras = buildEntryResult(entry)
|
||||
)
|
||||
}
|
||||
onValidateSpecialMode()
|
||||
}
|
||||
|
||||
private fun entryValidatedForAutofillRegistration(entry: Entry) {
|
||||
//if (isIntentSender()) {
|
||||
// TODO Autofill Callback #765
|
||||
//}
|
||||
onValidateSpecialMode()
|
||||
if (!isIntentSender()) {
|
||||
finishForEntryResult(entry)
|
||||
private fun entryValidatedForAutofillRegistration(database: ContextualDatabase, entry: Entry) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
this.buildSpecialModeResponseAndSetResult(
|
||||
entryInfo = entry.getEntryInfo(database),
|
||||
extras = buildEntryResult(entry)
|
||||
)
|
||||
}
|
||||
onValidateSpecialMode()
|
||||
}
|
||||
|
||||
private fun entryValidatedForPasskeyRegistration(database: ContextualDatabase, entry: Entry) {
|
||||
@@ -856,7 +859,6 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
typeMode: TypeMode,
|
||||
groupId: NodeId<*>,
|
||||
searchInfo: SearchInfo? = null,
|
||||
autofillComponent: AutofillComponent? = null,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>? = null,
|
||||
) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
@@ -868,7 +870,6 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
intent = intent,
|
||||
typeMode = typeMode,
|
||||
searchInfo = searchInfo,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = activityResultLauncher
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.MainCredential
|
||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||
@@ -303,7 +302,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
||||
)
|
||||
onLaunchActivitySpecialMode()
|
||||
},
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo ->
|
||||
MainCredentialActivity.launchForSelection(
|
||||
activity = this,
|
||||
activityResultLauncher = if (intentSenderMode)
|
||||
@@ -312,8 +311,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
||||
keyFile = keyFile,
|
||||
hardwareKey = hardwareKey,
|
||||
typeMode = typeMode,
|
||||
searchInfo = searchInfo,
|
||||
autofillComponent = autofillComponent,
|
||||
searchInfo = searchInfo
|
||||
)
|
||||
onLaunchActivitySpecialMode()
|
||||
},
|
||||
@@ -496,18 +494,16 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
||||
*/
|
||||
|
||||
fun launchForSelection(
|
||||
activity: Activity,
|
||||
context: Context,
|
||||
typeMode: TypeMode,
|
||||
searchInfo: SearchInfo? = null,
|
||||
autofillComponent: AutofillComponent? = null,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>? = null,
|
||||
) {
|
||||
EntrySelectionHelper.startActivityForSelectionModeResult(
|
||||
context = activity,
|
||||
intent = Intent(activity, FileDatabaseSelectActivity::class.java),
|
||||
context = context,
|
||||
intent = Intent(context, FileDatabaseSelectActivity::class.java),
|
||||
searchInfo = searchInfo,
|
||||
typeMode = typeMode,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = activityResultLauncher
|
||||
)
|
||||
}
|
||||
|
||||
@@ -64,10 +64,13 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.adapters.BreadcrumbAdapter
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecialModeResponseAndSetResult
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
@@ -495,14 +498,13 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
searchAction = {
|
||||
// Search not used
|
||||
},
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo ->
|
||||
EntryEditActivity.launchForSelection(
|
||||
context = this@GroupActivity,
|
||||
database = database,
|
||||
typeMode = typeMode,
|
||||
groupId = currentGroup.nodeId,
|
||||
searchInfo = searchInfo,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = if (intentSenderMode)
|
||||
mCredentialActivityResultLauncher else null
|
||||
)
|
||||
@@ -666,7 +668,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
searchAction = {
|
||||
// Search not used
|
||||
},
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo ->
|
||||
when (typeMode) {
|
||||
TypeMode.DEFAULT -> {}
|
||||
TypeMode.MAGIKEYBOARD -> entry?.let {
|
||||
@@ -700,7 +702,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
*/
|
||||
private fun transformSearchInfoIntent(intent: Intent) {
|
||||
// To relaunch the activity as ACTION_SEARCH
|
||||
val searchInfo: SearchInfo? = EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
||||
val searchInfo: SearchInfo? = intent.retrieveSearchInfo()
|
||||
val autoSearch = intent.getBooleanExtra(AUTO_SEARCH_KEY, false)
|
||||
intent.removeExtra(AUTO_SEARCH_KEY)
|
||||
if (searchInfo != null && autoSearch) {
|
||||
@@ -840,7 +842,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
searchAction = {
|
||||
// Nothing here, a search is simply performed
|
||||
},
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo ->
|
||||
when (typeMode) {
|
||||
TypeMode.DEFAULT -> {}
|
||||
TypeMode.MAGIKEYBOARD -> {
|
||||
@@ -910,11 +912,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
removeSearch()
|
||||
// Build response with the entry selected
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.buildResponseAndSetResult(
|
||||
this,
|
||||
database,
|
||||
entry.getEntryInfo(database)
|
||||
)
|
||||
this.buildSpecialModeResponseAndSetResult(entry.getEntryInfo(database))
|
||||
}
|
||||
onValidateSpecialMode()
|
||||
}
|
||||
@@ -1381,8 +1379,8 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
// Else in root, lock if needed
|
||||
else {
|
||||
removeSearch()
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
EntrySelectionHelper.removeInfoFromIntent(intent)
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
if (PreferencesUtil.isLockDatabaseWhenBackButtonOnRootClicked(this)) {
|
||||
lockAndExit()
|
||||
} else {
|
||||
@@ -1513,10 +1511,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
if (database.loaded) {
|
||||
checkTimeAndBuildIntent(context, null) { intent ->
|
||||
intent.putExtra(AUTO_SEARCH_KEY, autoSearch)
|
||||
EntrySelectionHelper.addSearchInfoInIntent(
|
||||
intent,
|
||||
searchInfo
|
||||
)
|
||||
intent.addSearchInfo(searchInfo)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@@ -1528,7 +1523,6 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
typeMode: TypeMode,
|
||||
searchInfo: SearchInfo? = null,
|
||||
autoSearch: Boolean = false,
|
||||
autofillComponent: AutofillComponent? = null,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>? = null,
|
||||
) {
|
||||
if (database.loaded) {
|
||||
@@ -1539,7 +1533,6 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
intent = intent,
|
||||
typeMode = typeMode,
|
||||
searchInfo = searchInfo,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = activityResultLauncher
|
||||
)
|
||||
}
|
||||
@@ -1608,7 +1601,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
onCancelSpecialMode()
|
||||
}
|
||||
},
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo, autofillComponent ->
|
||||
selectionAction = { intentSenderMode, typeMode, searchInfo ->
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = activity,
|
||||
database = database,
|
||||
@@ -1644,7 +1637,9 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
EntrySelectionHelper.performSelection(
|
||||
items = items,
|
||||
actionPopulateCredentialProvider = { entryInfo ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
activity.buildPasskeyResponseAndSetResult(entryInfo)
|
||||
}
|
||||
onValidateSpecialMode()
|
||||
},
|
||||
actionEntrySelection = {
|
||||
@@ -1662,7 +1657,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
}
|
||||
TypeMode.AUTOFILL -> {
|
||||
// Response is build
|
||||
AutofillHelper.buildResponseAndSetResult(activity, openedDatabase, items)
|
||||
activity.buildSpecialModeResponseAndSetResult(items)
|
||||
onValidateSpecialMode()
|
||||
}
|
||||
}
|
||||
@@ -1675,7 +1670,6 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
typeMode = typeMode,
|
||||
searchInfo = searchInfo,
|
||||
autoSearch = false,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = if (intentSenderMode)
|
||||
activityResultLauncher else null
|
||||
)
|
||||
|
||||
@@ -57,7 +57,6 @@ import com.kunzisoft.keepass.biometric.deviceUnlockError
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.MainCredential
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
@@ -815,7 +814,6 @@ class MainCredentialActivity : DatabaseModeActivity() {
|
||||
hardwareKey: HardwareKey?,
|
||||
typeMode: TypeMode,
|
||||
searchInfo: SearchInfo?,
|
||||
autofillComponent: AutofillComponent? = null,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>? = null,
|
||||
) {
|
||||
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
|
||||
@@ -824,7 +822,6 @@ class MainCredentialActivity : DatabaseModeActivity() {
|
||||
intent = intent,
|
||||
typeMode = typeMode,
|
||||
searchInfo = searchInfo,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = activityResultLauncher
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.adapters.NodesAdapter
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
@@ -248,7 +248,7 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
|
||||
|
||||
mNodesRecyclerView?.addOnScrollListener(mRecycleViewScrollListener)
|
||||
activity?.intent?.let {
|
||||
specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it)
|
||||
specialMode = it.retrieveSpecialMode()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import androidx.appcompat.app.AlertDialog
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.MainCredential
|
||||
@@ -395,7 +395,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
// If in registration mode, don't allow read only
|
||||
if (mSpecialMode == SpecialMode.REGISTRATION && mDatabaseReadOnly) {
|
||||
Toast.makeText(this, R.string.error_registration_read_only , Toast.LENGTH_LONG).show()
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
intent.removeModes()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,14 @@ import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.isIntentSenderMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveTypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
@@ -56,8 +61,8 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
|
||||
|
||||
fun onLaunchActivitySpecialMode() {
|
||||
if (!isIntentSender()) {
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
EntrySelectionHelper.removeInfoFromIntent(intent)
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -66,8 +71,8 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
|
||||
if (isIntentSender()) {
|
||||
super.finish()
|
||||
} else {
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
EntrySelectionHelper.removeInfoFromIntent(intent)
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
if (mSpecialMode != SpecialMode.DEFAULT) {
|
||||
backToTheMainAppAndFinish()
|
||||
}
|
||||
@@ -79,8 +84,8 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
|
||||
// To get the app caller, only for IntentSender
|
||||
onRegularBackPressed()
|
||||
} else {
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
EntrySelectionHelper.removeInfoFromIntent(intent)
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
if (mSpecialMode != SpecialMode.DEFAULT) {
|
||||
backToTheMainAppAndFinish()
|
||||
}
|
||||
@@ -111,18 +116,18 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
|
||||
}
|
||||
})
|
||||
|
||||
mSpecialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(intent)
|
||||
mTypeMode = EntrySelectionHelper.retrieveTypeModeFromIntent(intent)
|
||||
mSpecialMode = intent.retrieveSpecialMode()
|
||||
mTypeMode = intent.retrieveTypeMode()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
mSpecialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(intent)
|
||||
mTypeMode = EntrySelectionHelper.retrieveTypeModeFromIntent(intent)
|
||||
val registerInfo: RegisterInfo? = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
|
||||
mSpecialMode = intent.retrieveSpecialMode()
|
||||
mTypeMode = intent.retrieveTypeMode()
|
||||
val registerInfo: RegisterInfo? = intent.retrieveRegisterInfo()
|
||||
val searchInfo: SearchInfo? = registerInfo?.searchInfo
|
||||
?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
||||
?: intent.retrieveSearchInfo()
|
||||
|
||||
// To show the selection mode
|
||||
mToolbarSpecial = findViewById(R.id.special_mode_view)
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelUuid
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
@@ -32,9 +34,6 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper.addAutofillComponent
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
@@ -43,7 +42,10 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
import com.kunzisoft.keepass.utils.getEnumExtra
|
||||
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.util.UUID
|
||||
|
||||
object EntrySelectionHelper {
|
||||
|
||||
@@ -51,6 +53,8 @@ object EntrySelectionHelper {
|
||||
private const val KEY_TYPE_MODE = "com.kunzisoft.keepass.extra.TYPE_MODE"
|
||||
private const val KEY_SEARCH_INFO = "com.kunzisoft.keepass.extra.SEARCH_INFO"
|
||||
private const val KEY_REGISTER_INFO = "com.kunzisoft.keepass.extra.REGISTER_INFO"
|
||||
private const val EXTRA_NODES_IDS = "com.kunzisoft.keepass.extra.NODES_IDS"
|
||||
private const val EXTRA_NODE_ID = "com.kunzisoft.keepass.extra.NODE_ID"
|
||||
|
||||
/**
|
||||
* Finish the activity by passing the result code and by locking the database if necessary
|
||||
@@ -107,8 +111,8 @@ object EntrySelectionHelper {
|
||||
intent: Intent,
|
||||
searchInfo: SearchInfo
|
||||
) {
|
||||
addSpecialModeInIntent(intent, SpecialMode.SEARCH)
|
||||
addSearchInfoInIntent(intent, searchInfo)
|
||||
intent.addSpecialMode(SpecialMode.SEARCH)
|
||||
intent.addSearchInfo(searchInfo)
|
||||
intent.flags = intent.flags or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
context.startActivity(intent)
|
||||
}
|
||||
@@ -118,17 +122,11 @@ object EntrySelectionHelper {
|
||||
intent: Intent,
|
||||
typeMode: TypeMode,
|
||||
searchInfo: SearchInfo?,
|
||||
autofillComponent: AutofillComponent? = null,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>? = null,
|
||||
) {
|
||||
addSpecialModeInIntent(intent, SpecialMode.SELECTION)
|
||||
addTypeModeInIntent(intent, typeMode)
|
||||
addSearchInfoInIntent(intent, searchInfo)
|
||||
autofillComponent?.let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
intent.addAutofillComponent(context, autofillComponent)
|
||||
}
|
||||
}
|
||||
intent.addSpecialMode(SpecialMode.SELECTION)
|
||||
intent.addTypeMode(typeMode)
|
||||
intent.addSearchInfo(searchInfo)
|
||||
if (activityResultLauncher == null) {
|
||||
intent.flags = intent.flags or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
@@ -142,77 +140,123 @@ object EntrySelectionHelper {
|
||||
registerInfo: RegisterInfo?,
|
||||
typeMode: TypeMode
|
||||
) {
|
||||
addSpecialModeInIntent(intent, SpecialMode.REGISTRATION)
|
||||
addTypeModeInIntent(intent, typeMode)
|
||||
addRegisterInfoInIntent(intent, registerInfo)
|
||||
intent.addSpecialMode(SpecialMode.REGISTRATION)
|
||||
intent.addTypeMode(typeMode)
|
||||
intent.addRegisterInfo(registerInfo)
|
||||
if (activityResultLauncher == null) {
|
||||
intent.flags = intent.flags or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
activityResultLauncher?.launch(intent) ?: context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun addSearchInfoInIntent(intent: Intent, searchInfo: SearchInfo?) {
|
||||
/**
|
||||
* Build the special mode response for internal entry selection for one entry
|
||||
*/
|
||||
fun Activity.buildSpecialModeResponseAndSetResult(
|
||||
entryInfo: EntryInfo,
|
||||
extras: Bundle? = null
|
||||
) {
|
||||
this.buildSpecialModeResponseAndSetResult(listOf(entryInfo), extras)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the special mode response for internal entry selection for multiple entries
|
||||
*/
|
||||
fun Activity.buildSpecialModeResponseAndSetResult(
|
||||
entriesInfo: List<EntryInfo>,
|
||||
extras: Bundle? = null
|
||||
) {
|
||||
try {
|
||||
val mReplyIntent = Intent()
|
||||
Log.d(javaClass.name, "Success special mode manual selection")
|
||||
mReplyIntent.addNodesIds(entriesInfo.map { it.id })
|
||||
extras?.let {
|
||||
mReplyIntent.putExtras(it)
|
||||
}
|
||||
setResult(Activity.RESULT_OK, mReplyIntent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(javaClass.name, "Unable to add the result", e)
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
|
||||
fun Intent.addSearchInfo(searchInfo: SearchInfo?): Intent {
|
||||
searchInfo?.let {
|
||||
intent.putExtra(KEY_SEARCH_INFO, it)
|
||||
putExtra(KEY_SEARCH_INFO, it)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun retrieveSearchInfoFromIntent(intent: Intent): SearchInfo? {
|
||||
return intent.getParcelableExtraCompat(KEY_SEARCH_INFO)
|
||||
fun Intent.retrieveSearchInfo(): SearchInfo? {
|
||||
return getParcelableExtraCompat(KEY_SEARCH_INFO)
|
||||
}
|
||||
|
||||
private fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) {
|
||||
fun Intent.addRegisterInfo(registerInfo: RegisterInfo?): Intent {
|
||||
registerInfo?.let {
|
||||
intent.putExtra(KEY_REGISTER_INFO, it)
|
||||
putExtra(KEY_REGISTER_INFO, it)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun retrieveRegisterInfoFromIntent(intent: Intent): RegisterInfo? {
|
||||
return intent.getParcelableExtraCompat(KEY_REGISTER_INFO)
|
||||
fun Intent.retrieveRegisterInfo(): RegisterInfo? {
|
||||
return getParcelableExtraCompat(KEY_REGISTER_INFO)
|
||||
}
|
||||
|
||||
fun removeInfoFromIntent(intent: Intent) {
|
||||
intent.removeExtra(KEY_SEARCH_INFO)
|
||||
intent.removeExtra(KEY_REGISTER_INFO)
|
||||
fun Intent.removeInfo() {
|
||||
removeExtra(KEY_SEARCH_INFO)
|
||||
removeExtra(KEY_REGISTER_INFO)
|
||||
}
|
||||
|
||||
fun addSpecialModeInIntent(intent: Intent, specialMode: SpecialMode) {
|
||||
// TODO Replace by Intent.addSpecialMode
|
||||
intent.putEnumExtra(KEY_SPECIAL_MODE, specialMode)
|
||||
}
|
||||
fun Intent.addSpecialMode(specialMode: SpecialMode): Intent {
|
||||
this.putEnumExtra(KEY_SPECIAL_MODE, specialMode)
|
||||
return this
|
||||
}
|
||||
|
||||
fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (AutofillHelper.retrieveAutofillComponent(intent) != null)
|
||||
return SpecialMode.SELECTION
|
||||
}
|
||||
return intent.getEnumExtra<SpecialMode>(KEY_SPECIAL_MODE) ?: SpecialMode.DEFAULT
|
||||
fun Intent.retrieveSpecialMode(): SpecialMode {
|
||||
return getEnumExtra<SpecialMode>(KEY_SPECIAL_MODE) ?: SpecialMode.DEFAULT
|
||||
}
|
||||
|
||||
private fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) {
|
||||
// TODO Replace by Intent.addTypeMode
|
||||
intent.putEnumExtra(KEY_TYPE_MODE, typeMode)
|
||||
}
|
||||
fun Intent.addTypeMode(typeMode: TypeMode): Intent {
|
||||
this.putEnumExtra(KEY_TYPE_MODE, typeMode)
|
||||
return this
|
||||
}
|
||||
|
||||
fun retrieveTypeModeFromIntent(intent: Intent): TypeMode {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (AutofillHelper.retrieveAutofillComponent(intent) != null)
|
||||
return TypeMode.AUTOFILL
|
||||
}
|
||||
return intent.getEnumExtra<TypeMode>(KEY_TYPE_MODE) ?: TypeMode.DEFAULT
|
||||
fun Intent.retrieveTypeMode(): TypeMode {
|
||||
return getEnumExtra<TypeMode>(KEY_TYPE_MODE) ?: TypeMode.DEFAULT
|
||||
}
|
||||
|
||||
fun removeModesFromIntent(intent: Intent) {
|
||||
intent.removeExtra(KEY_SPECIAL_MODE)
|
||||
intent.removeExtra(KEY_TYPE_MODE)
|
||||
fun Intent.removeModes() {
|
||||
removeExtra(KEY_SPECIAL_MODE)
|
||||
removeExtra(KEY_TYPE_MODE)
|
||||
}
|
||||
|
||||
fun Intent.addNodesIds(nodesIds: List<UUID>): Intent {
|
||||
this.putParcelableList(EXTRA_NODES_IDS, nodesIds.map { ParcelUuid(it) })
|
||||
return this
|
||||
}
|
||||
|
||||
fun Intent.retrieveNodesIds(): List<UUID>? {
|
||||
return getParcelableList<ParcelUuid>(EXTRA_NODES_IDS)?.map { it.uuid }
|
||||
}
|
||||
|
||||
fun Intent.removeNodesIds() {
|
||||
removeExtra(EXTRA_NODES_IDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the node id to the intent
|
||||
*/
|
||||
fun Intent.addNodeId(nodeId: UUID?) {
|
||||
nodeId?.let {
|
||||
putExtra(EXTRA_NODE_ID, ParcelUuid(nodeId))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the node id from the intent
|
||||
*/
|
||||
fun Intent.retrieveNodeId(): UUID? {
|
||||
return getParcelableExtraCompat<ParcelUuid>(EXTRA_NODE_ID)?.uuid
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,9 +265,8 @@ object EntrySelectionHelper {
|
||||
fun isIntentSenderMode(specialMode: SpecialMode, typeMode: TypeMode): Boolean {
|
||||
return (specialMode == SpecialMode.SELECTION
|
||||
&& (typeMode == TypeMode.AUTOFILL || typeMode == TypeMode.PASSKEY))
|
||||
// TODO Autofill Registration callback #765 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||
|| (specialMode == SpecialMode.REGISTRATION
|
||||
&& typeMode == TypeMode.PASSKEY)
|
||||
&& (typeMode == TypeMode.AUTOFILL || typeMode == TypeMode.PASSKEY))
|
||||
}
|
||||
|
||||
fun doSpecialAction(
|
||||
@@ -233,8 +276,7 @@ object EntrySelectionHelper {
|
||||
selectionAction: (
|
||||
intentSenderMode: Boolean,
|
||||
typeMode: TypeMode,
|
||||
searchInfo: SearchInfo?,
|
||||
autofillComponent: AutofillComponent?
|
||||
searchInfo: SearchInfo?
|
||||
) -> Unit,
|
||||
registrationAction: (
|
||||
intentSenderMode: Boolean,
|
||||
@@ -242,16 +284,16 @@ object EntrySelectionHelper {
|
||||
registerInfo: RegisterInfo?
|
||||
) -> Unit
|
||||
) {
|
||||
when (val specialMode = retrieveSpecialModeFromIntent(intent)) {
|
||||
when (val specialMode = intent.retrieveSpecialMode()) {
|
||||
SpecialMode.DEFAULT -> {
|
||||
removeModesFromIntent(intent)
|
||||
removeInfoFromIntent(intent)
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
defaultAction.invoke()
|
||||
}
|
||||
SpecialMode.SEARCH -> {
|
||||
val searchInfo = retrieveSearchInfoFromIntent(intent)
|
||||
removeModesFromIntent(intent)
|
||||
removeInfoFromIntent(intent)
|
||||
val searchInfo = intent.retrieveSearchInfo()
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
if (searchInfo != null)
|
||||
searchAction.invoke(searchInfo)
|
||||
else {
|
||||
@@ -259,24 +301,11 @@ object EntrySelectionHelper {
|
||||
}
|
||||
}
|
||||
SpecialMode.SELECTION -> {
|
||||
val searchInfo: SearchInfo? = retrieveSearchInfoFromIntent(intent)
|
||||
var autofillComponentInit = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.retrieveAutofillComponent(intent)?.let { autofillComponent ->
|
||||
selectionAction.invoke(
|
||||
isIntentSenderMode(specialMode, TypeMode.AUTOFILL),
|
||||
TypeMode.AUTOFILL,
|
||||
searchInfo,
|
||||
autofillComponent
|
||||
)
|
||||
autofillComponentInit = true
|
||||
}
|
||||
}
|
||||
if (!autofillComponentInit) {
|
||||
val searchInfo: SearchInfo? = intent.retrieveSearchInfo()
|
||||
if (intent.getEnumExtra<SpecialMode>(KEY_SPECIAL_MODE) != null) {
|
||||
when (val typeMode = retrieveTypeModeFromIntent(intent)) {
|
||||
when (val typeMode = intent.retrieveTypeMode()) {
|
||||
TypeMode.DEFAULT -> {
|
||||
removeModesFromIntent(intent)
|
||||
intent.removeModes()
|
||||
if (searchInfo != null)
|
||||
searchAction.invoke(searchInfo)
|
||||
else
|
||||
@@ -285,23 +314,26 @@ object EntrySelectionHelper {
|
||||
TypeMode.MAGIKEYBOARD -> selectionAction.invoke(
|
||||
isIntentSenderMode(specialMode, typeMode),
|
||||
typeMode,
|
||||
searchInfo,
|
||||
null
|
||||
searchInfo
|
||||
)
|
||||
TypeMode.PASSKEY ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
selectionAction.invoke(
|
||||
isIntentSenderMode(specialMode, typeMode),
|
||||
typeMode,
|
||||
searchInfo,
|
||||
null
|
||||
searchInfo
|
||||
)
|
||||
} else
|
||||
defaultAction.invoke()
|
||||
TypeMode.AUTOFILL -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
selectionAction.invoke(
|
||||
isIntentSenderMode(specialMode, typeMode),
|
||||
typeMode,
|
||||
searchInfo
|
||||
)
|
||||
} else
|
||||
defaultAction.invoke()
|
||||
else -> {
|
||||
// In this case, error
|
||||
removeModesFromIntent(intent)
|
||||
removeInfoFromIntent(intent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -311,14 +343,13 @@ object EntrySelectionHelper {
|
||||
defaultAction.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
SpecialMode.REGISTRATION -> {
|
||||
val registerInfo: RegisterInfo? = retrieveRegisterInfoFromIntent(intent)
|
||||
val typeMode = retrieveTypeModeFromIntent(intent)
|
||||
val registerInfo: RegisterInfo? = intent.retrieveRegisterInfo()
|
||||
val typeMode = intent.retrieveTypeMode()
|
||||
val intentSenderMode = isIntentSenderMode(specialMode, typeMode)
|
||||
if (!intentSenderMode) {
|
||||
removeModesFromIntent(intent)
|
||||
removeInfoFromIntent(intent)
|
||||
intent.removeModes()
|
||||
intent.removeInfo()
|
||||
}
|
||||
if (registerInfo != null)
|
||||
registrationAction.invoke(
|
||||
|
||||
@@ -27,34 +27,46 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addRegisterInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.CompatInlineSuggestionsRequest
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.KeeAutofillService
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper.addAutofillComponent
|
||||
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.database.helper.SearchHelper
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
||||
import com.kunzisoft.keepass.utils.getParcelableCompat
|
||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
|
||||
import com.kunzisoft.keepass.view.toastError
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
|
||||
override var mCredentialActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||
this.buildActivityResultLauncher(typeMode = TypeMode.AUTOFILL, lockDatabase = true)
|
||||
private val autofillLauncherViewModel: AutofillLauncherViewModel by viewModels()
|
||||
|
||||
private var mAutofillSelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
autofillLauncherViewModel.manageSelectionResult(it)
|
||||
}
|
||||
|
||||
private var mAutofillRegistrationActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
autofillLauncherViewModel.manageRegistrationResult(it)
|
||||
}
|
||||
|
||||
override fun applyCustomStyle(): Boolean {
|
||||
return false
|
||||
@@ -64,176 +76,103 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
// Retrieve the UI
|
||||
autofillLauncherViewModel.credentialUiState.collect { uiState ->
|
||||
when (uiState) {
|
||||
is CredentialLauncherViewModel.UIState.Loading -> {}
|
||||
is CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection -> {
|
||||
GroupActivity.launchForSelection(
|
||||
context = this@AutofillLauncherActivity,
|
||||
database = uiState.database,
|
||||
searchInfo = uiState.searchInfo,
|
||||
typeMode = uiState.typeMode,
|
||||
activityResultLauncher = mAutofillSelectionActivityResultLauncher,
|
||||
)
|
||||
}
|
||||
is CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> {
|
||||
GroupActivity.launchForRegistration(
|
||||
context = this@AutofillLauncherActivity,
|
||||
database = uiState.database,
|
||||
registerInfo = uiState.registerInfo,
|
||||
typeMode = uiState.typeMode,
|
||||
activityResultLauncher = mAutofillRegistrationActivityResultLauncher
|
||||
)
|
||||
}
|
||||
is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> {
|
||||
FileDatabaseSelectActivity.launchForSelection(
|
||||
context = this@AutofillLauncherActivity,
|
||||
searchInfo = uiState.searchInfo,
|
||||
typeMode = uiState.typeMode,
|
||||
activityResultLauncher = mAutofillSelectionActivityResultLauncher
|
||||
)
|
||||
}
|
||||
is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> {
|
||||
FileDatabaseSelectActivity.launchForRegistration(
|
||||
context = this@AutofillLauncherActivity,
|
||||
registerInfo = uiState.registerInfo,
|
||||
typeMode = uiState.typeMode,
|
||||
activityResultLauncher = mAutofillRegistrationActivityResultLauncher,
|
||||
)
|
||||
}
|
||||
is CredentialLauncherViewModel.UIState.SetActivityResult -> {
|
||||
setActivityResult(
|
||||
typeMode = TypeMode.AUTOFILL,
|
||||
lockDatabase = uiState.lockDatabase,
|
||||
resultCode = uiState.resultCode,
|
||||
data = uiState.data
|
||||
)
|
||||
}
|
||||
is CredentialLauncherViewModel.UIState.ShowError -> {
|
||||
toastError(uiState.error)
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
// Initialize the parameters
|
||||
autofillLauncherViewModel.uiState.collect { uiState ->
|
||||
when (uiState) {
|
||||
AutofillLauncherViewModel.UIState.Loading -> {}
|
||||
is AutofillLauncherViewModel.UIState.ShowBlockRestartMessage -> {
|
||||
showBlockRestartMessage()
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
is AutofillLauncherViewModel.UIState.ShowReadOnlyMessage -> {
|
||||
showReadOnlySaveMessage()
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
is AutofillLauncherViewModel.UIState.ShowAutofillSuggestionMessage -> {
|
||||
showAutofillSuggestionMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
|
||||
// Retrieve selection mode
|
||||
EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
|
||||
when (specialMode) {
|
||||
SpecialMode.SELECTION -> {
|
||||
intent.getBundleExtra(KEY_SELECTION_BUNDLE)?.let { bundle ->
|
||||
// To pass extra inline request
|
||||
var compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
compatInlineSuggestionsRequest = bundle.getParcelableCompat(
|
||||
KEY_INLINE_SUGGESTION
|
||||
)
|
||||
}
|
||||
// Build search param
|
||||
bundle.getParcelableCompat<SearchInfo>(KEY_SEARCH_INFO)?.let { searchInfo ->
|
||||
searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
|
||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||
val assistStructure = AutofillHelper
|
||||
.retrieveAutofillComponent(intent)
|
||||
?.assistStructure
|
||||
val newAutofillComponent = if (assistStructure != null) {
|
||||
AutofillComponent(
|
||||
assistStructure,
|
||||
compatInlineSuggestionsRequest
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
launchSelection(database, newAutofillComponent, searchInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove bundle
|
||||
intent.removeExtra(KEY_SELECTION_BUNDLE)
|
||||
}
|
||||
SpecialMode.REGISTRATION -> {
|
||||
// To register info
|
||||
val registerInfo = intent.getParcelableExtraCompat<RegisterInfo>(
|
||||
KEY_REGISTER_INFO
|
||||
)
|
||||
val searchInfo = SearchInfo(registerInfo?.searchInfo)
|
||||
searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
launchRegistration(database, searchInfo, registerInfo)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Not an autofill call
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchSelection(database: ContextualDatabase?,
|
||||
autofillComponent: AutofillComponent?,
|
||||
searchInfo: SearchInfo) {
|
||||
if (autofillComponent == null) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
} else if (KeeAutofillService.autofillAllowedFor(
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = this
|
||||
)) {
|
||||
// If database is open
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
database = database,
|
||||
searchInfo = searchInfo,
|
||||
onItemsFound = { openedDatabase, items ->
|
||||
// Items found
|
||||
AutofillHelper.buildResponseAndSetResult(this, openedDatabase, items)
|
||||
finish()
|
||||
},
|
||||
onItemNotFound = { openedDatabase ->
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForSelection(
|
||||
context = this,
|
||||
database = openedDatabase,
|
||||
typeMode = TypeMode.AUTOFILL,
|
||||
searchInfo = searchInfo,
|
||||
autoSearch = false,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = mCredentialActivityResultLauncher,
|
||||
)
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForSelection(
|
||||
activity = this,
|
||||
typeMode = TypeMode.AUTOFILL,
|
||||
searchInfo = searchInfo,
|
||||
autofillComponent = autofillComponent,
|
||||
activityResultLauncher = mCredentialActivityResultLauncher
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showBlockRestartMessage()
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchRegistration(database: ContextualDatabase?,
|
||||
searchInfo: SearchInfo,
|
||||
registerInfo: RegisterInfo?) {
|
||||
if (KeeAutofillService.autofillAllowedFor(
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = this
|
||||
)) {
|
||||
val readOnly = database?.isReadOnly != false
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
database = database,
|
||||
searchInfo = searchInfo,
|
||||
onItemsFound = { openedDatabase, _ ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(
|
||||
context = this,
|
||||
activityResultLauncher = null, // TODO Autofill result launcher #765
|
||||
database = openedDatabase,
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
onItemNotFound = { openedDatabase ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(
|
||||
context = this,
|
||||
activityResultLauncher = null, // TODO Autofill result launcher #765
|
||||
database = openedDatabase,
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
// If database not open
|
||||
FileDatabaseSelectActivity.launchForRegistration(
|
||||
context = this,
|
||||
activityResultLauncher = null, // TODO Autofill result launcher #765
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showBlockRestartMessage()
|
||||
setResult(RESULT_CANCELED)
|
||||
}
|
||||
finish()
|
||||
autofillLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
||||
}
|
||||
|
||||
private fun showBlockRestartMessage() {
|
||||
// If item not allowed, show a toast
|
||||
Toast.makeText(this.applicationContext, R.string.autofill_block_restart, Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.autofill_block_restart,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun showAutofillSuggestionMessage() {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.autofill_inline_suggestions_keyboard,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun showReadOnlySaveMessage() {
|
||||
@@ -244,27 +183,21 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
|
||||
private val TAG = AutofillLauncherActivity::class.java.name
|
||||
|
||||
private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE"
|
||||
private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
|
||||
private const val KEY_INLINE_SUGGESTION = "KEY_INLINE_SUGGESTION"
|
||||
|
||||
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
|
||||
|
||||
fun getPendingIntentForSelection(context: Context,
|
||||
fun getPendingIntentForSelection(
|
||||
context: Context,
|
||||
searchInfo: SearchInfo? = null,
|
||||
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null): PendingIntent? {
|
||||
autofillComponent: AutofillComponent
|
||||
): PendingIntent? {
|
||||
try {
|
||||
return PendingIntent.getActivity(
|
||||
context, 0,
|
||||
context,
|
||||
randomRequestCode(),
|
||||
// Doesn't work with direct extra Parcelable (don't know why?)
|
||||
// Wrap into a bundle to bypass the problem
|
||||
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||
putExtra(KEY_SELECTION_BUNDLE, Bundle().apply {
|
||||
putParcelable(KEY_SEARCH_INFO, searchInfo)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
putParcelable(KEY_INLINE_SUGGESTION, compatInlineSuggestionsRequest)
|
||||
}
|
||||
})
|
||||
addSpecialMode(SpecialMode.SELECTION)
|
||||
addSearchInfo(searchInfo)
|
||||
addAutofillComponent(autofillComponent)
|
||||
},
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
@@ -278,14 +211,17 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getPendingIntentForRegistration(context: Context,
|
||||
registerInfo: RegisterInfo): PendingIntent? {
|
||||
fun getPendingIntentForRegistration(
|
||||
context: Context,
|
||||
registerInfo: RegisterInfo
|
||||
): PendingIntent? {
|
||||
try {
|
||||
return PendingIntent.getActivity(
|
||||
context, 0,
|
||||
context,
|
||||
randomRequestCode(),
|
||||
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||
EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
|
||||
putExtra(KEY_REGISTER_INFO, registerInfo)
|
||||
addSpecialMode(SpecialMode.REGISTRATION)
|
||||
addRegisterInfo(registerInfo)
|
||||
},
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
|
||||
@@ -299,11 +235,13 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun launchForRegistration(context: Context,
|
||||
registerInfo: RegisterInfo) {
|
||||
fun launchForRegistration(
|
||||
context: Context,
|
||||
registerInfo: RegisterInfo
|
||||
) {
|
||||
val intent = Intent(context, AutofillLauncherActivity::class.java)
|
||||
EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.REGISTRATION)
|
||||
intent.putExtra(KEY_REGISTER_INFO, registerInfo)
|
||||
intent.addSpecialMode(SpecialMode.REGISTRATION)
|
||||
intent.addRegisterInfo(registerInfo)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
@@ -105,12 +105,11 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
||||
sharedWebDomain: String?,
|
||||
otpString: String?) {
|
||||
// Build domain search param
|
||||
getConcreteWebDomain(this, sharedWebDomain) { concreteWebDomain ->
|
||||
val searchInfo = SearchInfo().apply {
|
||||
this.webDomain = sharedWebDomain
|
||||
this.webDomain = concreteWebDomain
|
||||
this.otpString = otpString
|
||||
}
|
||||
searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
launch(database, searchInfo)
|
||||
}
|
||||
}
|
||||
@@ -212,7 +211,7 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
||||
)
|
||||
} else if (searchShareForMagikeyboard) {
|
||||
FileDatabaseSelectActivity.launchForSelection(
|
||||
activity = this,
|
||||
context = this,
|
||||
typeMode = TypeMode.MAGIKEYBOARD,
|
||||
searchInfo = searchInfo
|
||||
)
|
||||
|
||||
@@ -36,6 +36,8 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addNodeId
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
||||
@@ -44,14 +46,13 @@ import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addNodeId
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.model.AppOrigin
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
|
||||
import com.kunzisoft.keepass.view.toastError
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
@@ -121,10 +122,9 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
GroupActivity.launchForSelection(
|
||||
context = this@PasskeyLauncherActivity,
|
||||
database = uiState.database,
|
||||
typeMode = TypeMode.PASSKEY,
|
||||
typeMode = uiState.typeMode,
|
||||
activityResultLauncher = mPasskeySelectionActivityResultLauncher,
|
||||
searchInfo = null,
|
||||
autoSearch = false
|
||||
searchInfo = uiState.searchInfo
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> {
|
||||
@@ -138,8 +138,8 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> {
|
||||
FileDatabaseSelectActivity.launchForSelection(
|
||||
activity = this@PasskeyLauncherActivity,
|
||||
typeMode = TypeMode.PASSKEY,
|
||||
context = this@PasskeyLauncherActivity,
|
||||
typeMode = uiState.typeMode,
|
||||
activityResultLauncher = mPasskeySelectionActivityResultLauncher,
|
||||
searchInfo = uiState.searchInfo,
|
||||
)
|
||||
@@ -276,7 +276,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
): PendingIntent? {
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
(Math.random() * Integer.MAX_VALUE).toInt(),
|
||||
randomRequestCode(),
|
||||
Intent(context, PasskeyLauncherActivity::class.java).apply {
|
||||
addSpecialMode(specialMode)
|
||||
addTypeMode(TypeMode.PASSKEY)
|
||||
|
||||
@@ -2,5 +2,7 @@ package com.kunzisoft.keepass.credentialprovider.autofill
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
|
||||
data class AutofillComponent(val assistStructure: AssistStructure,
|
||||
val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?)
|
||||
data class AutofillComponent(
|
||||
val assistStructure: AssistStructure,
|
||||
val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?
|
||||
)
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.kunzisoft.keepass.credentialprovider.autofill
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.assist.AssistStructure
|
||||
import android.content.Context
|
||||
@@ -38,7 +37,6 @@ import android.view.autofill.AutofillId
|
||||
import android.view.autofill.AutofillManager
|
||||
import android.view.autofill.AutofillValue
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.Toast
|
||||
import android.widget.inline.InlinePresentationSpec
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.autofill.inline.UiVersions
|
||||
@@ -54,21 +52,32 @@ import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||
import java.io.IOException
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
object AutofillHelper {
|
||||
|
||||
private const val EXTRA_ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
|
||||
private const val EXTRA_BASE_STRUCTURE = "com.kunzisoft.keepass.autofill.BASE_STRUCTURE"
|
||||
private const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST"
|
||||
|
||||
fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? {
|
||||
intent?.getParcelableExtraCompat<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
|
||||
fun Intent.addAutofillComponent(autofillComponent: AutofillComponent) {
|
||||
this.putExtra(EXTRA_BASE_STRUCTURE, autofillComponent.assistStructure)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
autofillComponent.compatInlineSuggestionsRequest?.let {
|
||||
this.putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Intent.retrieveAutofillComponent(): AutofillComponent? {
|
||||
getParcelableExtraCompat<AssistStructure>(EXTRA_BASE_STRUCTURE)?.let { assistStructure ->
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
AutofillComponent(assistStructure,
|
||||
intent.getParcelableExtraCompat(EXTRA_INLINE_SUGGESTIONS_REQUEST))
|
||||
getParcelableExtraCompat(EXTRA_INLINE_SUGGESTIONS_REQUEST))
|
||||
} else {
|
||||
AutofillComponent(assistStructure, null)
|
||||
}
|
||||
@@ -127,11 +136,13 @@ object AutofillHelper {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun buildDatasetForEntry(context: Context,
|
||||
private fun buildDatasetForEntry(
|
||||
context: Context,
|
||||
database: ContextualDatabase,
|
||||
entryInfo: EntryInfo,
|
||||
struct: StructureParser.Result,
|
||||
inlinePresentation: InlinePresentation?): Dataset {
|
||||
inlinePresentation: InlinePresentation?
|
||||
): Dataset {
|
||||
val remoteViews: RemoteViews = newRemoteViews(context, database, makeEntryTitle(entryInfo), entryInfo.icon)
|
||||
|
||||
val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
@@ -291,11 +302,13 @@ object AutofillHelper {
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private fun buildInlinePresentationForEntry(context: Context,
|
||||
private fun buildInlinePresentationForEntry(
|
||||
context: Context,
|
||||
database: ContextualDatabase,
|
||||
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest,
|
||||
positionItem: Int,
|
||||
entryInfo: EntryInfo): InlinePresentation? {
|
||||
entryInfo: EntryInfo
|
||||
): InlinePresentation? {
|
||||
compatInlineSuggestionsRequest.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
|
||||
val maxSuggestion = inlineSuggestionsRequest.maxSuggestionCount
|
||||
@@ -341,9 +354,11 @@ object AutofillHelper {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun buildInlinePresentationForManualSelection(context: Context,
|
||||
private fun buildInlinePresentationForManualSelection(
|
||||
context: Context,
|
||||
inlinePresentationSpec: InlinePresentationSpec,
|
||||
pendingIntent: PendingIntent): InlinePresentation? {
|
||||
pendingIntent: PendingIntent
|
||||
): InlinePresentation? {
|
||||
// Make sure that the IME spec claims support for v1 UI template.
|
||||
val imeStyle = inlinePresentationSpec.style
|
||||
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
|
||||
@@ -360,11 +375,14 @@ object AutofillHelper {
|
||||
}.build().slice, inlinePresentationSpec, false)
|
||||
}
|
||||
|
||||
fun buildResponse(context: Context,
|
||||
fun buildResponse(
|
||||
context: Context,
|
||||
database: ContextualDatabase,
|
||||
entriesInfo: List<EntryInfo>,
|
||||
parseResult: StructureParser.Result,
|
||||
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?): FillResponse? {
|
||||
concreteWebDomain: String?,
|
||||
autofillComponent: AutofillComponent
|
||||
): FillResponse? {
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
// Add Header
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
@@ -385,7 +403,8 @@ object AutofillHelper {
|
||||
// Add inline suggestion for new IME and dataset
|
||||
var numberInlineSuggestions = 0
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
autofillComponent.compatInlineSuggestionsRequest
|
||||
?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
numberInlineSuggestions = minOf(inlineSuggestionsRequest.maxSuggestionCount, entriesInfo.size)
|
||||
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
||||
if (entriesInfo.size >= inlineSuggestionsRequest.maxSuggestionCount) {
|
||||
@@ -401,21 +420,27 @@ object AutofillHelper {
|
||||
var inlinePresentation: InlinePresentation? = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& numberInlineSuggestions > 0
|
||||
&& compatInlineSuggestionsRequest != null) {
|
||||
&& autofillComponent.compatInlineSuggestionsRequest != null) {
|
||||
inlinePresentation = buildInlinePresentationForEntry(
|
||||
context,
|
||||
database,
|
||||
compatInlineSuggestionsRequest,
|
||||
autofillComponent.compatInlineSuggestionsRequest,
|
||||
numberInlineSuggestions--,
|
||||
entry
|
||||
)
|
||||
}
|
||||
// Create dataset for each entry
|
||||
responseBuilder.addDataset(
|
||||
buildDatasetForEntry(context, database, entry, parseResult, inlinePresentation)
|
||||
buildDatasetForEntry(
|
||||
context = context,
|
||||
database = database,
|
||||
entryInfo = entry,
|
||||
struct = parseResult,
|
||||
inlinePresentation = inlinePresentation
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to add dataset")
|
||||
Log.e(TAG, "Unable to add dataset", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,17 +448,24 @@ object AutofillHelper {
|
||||
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.webDomain
|
||||
webDomain = concreteWebDomain
|
||||
webScheme = parseResult.webScheme
|
||||
manualSelection = true
|
||||
}
|
||||
val manualSelectionView = RemoteViews(context.packageName, R.layout.item_autofill_select_entry)
|
||||
AutofillLauncherActivity.getPendingIntentForSelection(context,
|
||||
searchInfo, compatInlineSuggestionsRequest)?.let { pendingIntent ->
|
||||
val manualSelectionView = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.item_autofill_select_entry
|
||||
)
|
||||
AutofillLauncherActivity.getPendingIntentForSelection(
|
||||
context,
|
||||
searchInfo,
|
||||
autofillComponent
|
||||
)?.let { pendingIntent ->
|
||||
|
||||
var inlinePresentation: InlinePresentation? = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
autofillComponent.compatInlineSuggestionsRequest
|
||||
?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
val inlinePresentationSpec =
|
||||
inlineSuggestionsRequest.inlinePresentationSpecs[0]
|
||||
inlinePresentation = buildInlinePresentationForManualSelection(
|
||||
@@ -486,61 +518,34 @@ object AutofillHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Autofill response for one entry
|
||||
* Build the Autofill response
|
||||
*/
|
||||
fun buildResponseAndSetResult(activity: Activity,
|
||||
fun buildResponse(
|
||||
context: Context,
|
||||
autofillComponent: AutofillComponent,
|
||||
database: ContextualDatabase,
|
||||
entryInfo: EntryInfo) {
|
||||
buildResponseAndSetResult(activity, database, ArrayList<EntryInfo>().apply { add(entryInfo) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Autofill response for many entry
|
||||
*/
|
||||
fun buildResponseAndSetResult(activity: Activity,
|
||||
database: ContextualDatabase,
|
||||
entriesInfo: List<EntryInfo>) {
|
||||
entriesInfo: List<EntryInfo>,
|
||||
onIntentCreated: suspend (Intent) -> Unit
|
||||
) {
|
||||
if (entriesInfo.isEmpty()) {
|
||||
activity.setResult(Activity.RESULT_CANCELED)
|
||||
throw IOException("No entries found")
|
||||
} else {
|
||||
var setResultOk = false
|
||||
activity.intent?.getParcelableExtraCompat<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { structure ->
|
||||
StructureParser(structure).parse()?.let { result ->
|
||||
StructureParser(autofillComponent.assistStructure).parse()?.let { result ->
|
||||
getConcreteWebDomain(context, result.webDomain) { concreteWebDomain ->
|
||||
// New Response
|
||||
val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val compatInlineSuggestionsRequest = activity.intent?.getParcelableExtraCompat<CompatInlineSuggestionsRequest>(
|
||||
EXTRA_INLINE_SUGGESTIONS_REQUEST
|
||||
)
|
||||
if (compatInlineSuggestionsRequest != null) {
|
||||
Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
buildResponse(activity, database, entriesInfo, result, compatInlineSuggestionsRequest)
|
||||
} else {
|
||||
buildResponse(activity, database, entriesInfo, result, null)
|
||||
}
|
||||
val mReplyIntent = Intent()
|
||||
Log.d(activity.javaClass.name, "Success Autofill auth.")
|
||||
mReplyIntent.putExtra(
|
||||
onIntentCreated(Intent().putExtra(
|
||||
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
||||
response)
|
||||
setResultOk = true
|
||||
activity.setResult(Activity.RESULT_OK, mReplyIntent)
|
||||
}
|
||||
}
|
||||
if (!setResultOk) {
|
||||
Log.w(activity.javaClass.name, "Failed Autofill auth.")
|
||||
activity.setResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Intent.addAutofillComponent(context: Context, autofillComponent: AutofillComponent) {
|
||||
this.putExtra(EXTRA_ASSIST_STRUCTURE, autofillComponent.assistStructure)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isAutofillInlineSuggestionsEnable(context)) {
|
||||
autofillComponent.compatInlineSuggestionsRequest?.let {
|
||||
this.putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
|
||||
buildResponse(
|
||||
context = context,
|
||||
database = database,
|
||||
entriesInfo = entriesInfo,
|
||||
parseResult = result,
|
||||
concreteWebDomain = concreteWebDomain,
|
||||
autofillComponent = autofillComponent
|
||||
)
|
||||
))
|
||||
}
|
||||
} ?: throw IOException("Unable to parse the structure")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,10 +92,11 @@ class KeeAutofillService : AutofillService() {
|
||||
autofillInlineSuggestionsEnabled = PreferencesUtil.isAutofillInlineSuggestionsEnable(this)
|
||||
}
|
||||
|
||||
override fun onFillRequest(request: FillRequest,
|
||||
override fun onFillRequest(
|
||||
request: FillRequest,
|
||||
cancellationSignal: CancellationSignal,
|
||||
callback: FillCallback) {
|
||||
|
||||
callback: FillCallback
|
||||
) {
|
||||
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
|
||||
|
||||
if (request.flags and FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST != 0) {
|
||||
@@ -115,13 +116,12 @@ class KeeAutofillService : AutofillService() {
|
||||
webDomain = parseResult.webDomain,
|
||||
webDomainBlocklist = webDomainBlocklist)
|
||||
) {
|
||||
getConcreteWebDomain(this, parseResult.webDomain) { webDomainWithoutSubDomain ->
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.webDomain
|
||||
webDomain = webDomainWithoutSubDomain
|
||||
webScheme = parseResult.webScheme
|
||||
}
|
||||
searchInfo.getConcreteWebDomain(this) { webDomainWithoutSubDomain ->
|
||||
searchInfo.webDomain = webDomainWithoutSubDomain
|
||||
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& autofillInlineSuggestionsEnabled) {
|
||||
CompatInlineSuggestionsRequest(request)
|
||||
@@ -131,18 +131,24 @@ class KeeAutofillService : AutofillService() {
|
||||
launchSelection(mDatabase,
|
||||
searchInfo,
|
||||
parseResult,
|
||||
inlineSuggestionsRequest,
|
||||
callback)
|
||||
AutofillComponent(
|
||||
latestStructure,
|
||||
inlineSuggestionsRequest
|
||||
),
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchSelection(database: ContextualDatabase?,
|
||||
private fun launchSelection(
|
||||
database: ContextualDatabase?,
|
||||
searchInfo: SearchInfo,
|
||||
parseResult: StructureParser.Result,
|
||||
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
|
||||
callback: FillCallback) {
|
||||
autofillComponent: AutofillComponent,
|
||||
callback: FillCallback
|
||||
) {
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
database = database,
|
||||
@@ -150,37 +156,46 @@ class KeeAutofillService : AutofillService() {
|
||||
onItemsFound = { openedDatabase, items ->
|
||||
callback.onSuccess(
|
||||
AutofillHelper.buildResponse(
|
||||
this, openedDatabase,
|
||||
items, parseResult, inlineSuggestionsRequest
|
||||
context = this,
|
||||
database = openedDatabase,
|
||||
entriesInfo = items,
|
||||
parseResult = parseResult,
|
||||
concreteWebDomain = searchInfo.webDomain,
|
||||
autofillComponent = autofillComponent
|
||||
)
|
||||
)
|
||||
},
|
||||
onItemNotFound = { openedDatabase ->
|
||||
// Show UI if no search result
|
||||
showUIForEntrySelection(parseResult, openedDatabase,
|
||||
searchInfo, inlineSuggestionsRequest, callback)
|
||||
searchInfo, autofillComponent, callback)
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
// Show UI if database not open
|
||||
showUIForEntrySelection(parseResult, null,
|
||||
searchInfo, inlineSuggestionsRequest, callback)
|
||||
searchInfo, autofillComponent, callback)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
||||
private fun showUIForEntrySelection(
|
||||
parseResult: StructureParser.Result,
|
||||
database: ContextualDatabase?,
|
||||
searchInfo: SearchInfo,
|
||||
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
|
||||
callback: FillCallback) {
|
||||
autofillComponent: AutofillComponent,
|
||||
callback: FillCallback
|
||||
) {
|
||||
var success = false
|
||||
parseResult.allAutofillIds().let { autofillIds ->
|
||||
if (autofillIds.isNotEmpty()) {
|
||||
// If the entire Autofill Response is authenticated, AuthActivity is used
|
||||
// to generate Response.
|
||||
AutofillLauncherActivity.getPendingIntentForSelection(this,
|
||||
searchInfo, inlineSuggestionsRequest)?.intentSender?.let { intentSender ->
|
||||
AutofillLauncherActivity.getPendingIntentForSelection(
|
||||
this,
|
||||
searchInfo,
|
||||
autofillComponent
|
||||
)?.intentSender?.let { intentSender ->
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
val remoteViewsUnlock: RemoteViews = if (database == null) {
|
||||
if (!parseResult.webDomain.isNullOrEmpty()) {
|
||||
@@ -271,7 +286,8 @@ class KeeAutofillService : AutofillService() {
|
||||
&& autofillInlineSuggestionsEnabled
|
||||
) {
|
||||
var inlinePresentation: InlinePresentation? = null
|
||||
inlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
autofillComponent.compatInlineSuggestionsRequest
|
||||
?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
|
||||
val inlinePresentationSpecs =
|
||||
inlineSuggestionsRequest.inlinePresentationSpecs
|
||||
if (inlineSuggestionsRequest.maxSuggestionCount > 0
|
||||
@@ -387,12 +403,14 @@ class KeeAutofillService : AutofillService() {
|
||||
}
|
||||
|
||||
// Show UI to save data
|
||||
val registerInfo = RegisterInfo(
|
||||
searchInfo = SearchInfo().apply {
|
||||
getConcreteWebDomain(applicationContext, parseResult.webDomain) { concreteWebDomain ->
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = parseResult.webDomain
|
||||
webDomain = concreteWebDomain
|
||||
webScheme = parseResult.webScheme
|
||||
},
|
||||
}
|
||||
val registerInfo = RegisterInfo(
|
||||
searchInfo = searchInfo,
|
||||
username = parseResult.usernameValue?.textValue?.toString(),
|
||||
password = parseResult.passwordValue?.textValue?.toString(),
|
||||
creditCard = parseResult.creditCardNumber?.let { cardNumber ->
|
||||
@@ -405,15 +423,20 @@ class KeeAutofillService : AutofillService() {
|
||||
}
|
||||
)
|
||||
|
||||
// TODO Callback in each activity #765
|
||||
//if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// callback.onSuccess(AutofillLauncherActivity.getAuthIntentSenderForRegistration(this,
|
||||
// registerInfo))
|
||||
//} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// TODO Test pending intent
|
||||
AutofillLauncherActivity.getPendingIntentForRegistration(
|
||||
this,
|
||||
registerInfo
|
||||
)?.intentSender?.let { intentSender ->
|
||||
callback.onSuccess(intentSender)
|
||||
} ?: callback.onFailure("Unable to launch registration")
|
||||
} else {
|
||||
AutofillLauncherActivity.launchForRegistration(this, registerInfo)
|
||||
success = true
|
||||
callback.onSuccess()
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.FieldsAdapter
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes
|
||||
import com.kunzisoft.keepass.credentialprovider.activity.EntrySelectionLauncherActivity
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.DatabaseTaskProvider
|
||||
@@ -484,7 +485,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
|
||||
// Populate Magikeyboard with entry
|
||||
addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
|
||||
// Consume the selection mode
|
||||
EntrySelectionHelper.removeModesFromIntent(activity.intent)
|
||||
activity.intent.removeModes()
|
||||
activity.moveTaskToBack(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelUuid
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Log
|
||||
@@ -44,6 +43,7 @@ import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
|
||||
import com.kunzisoft.encrypt.Signature
|
||||
import com.kunzisoft.encrypt.Signature.getApplicationFingerprints
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addNodeId
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAssertionResponse
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorAttestationResponse
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.Cbor
|
||||
@@ -60,7 +60,6 @@ import com.kunzisoft.keepass.model.AndroidOrigin
|
||||
import com.kunzisoft.keepass.model.AppOrigin
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.Passkey
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.utils.AppUtil
|
||||
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||
@@ -88,10 +87,7 @@ object PasskeyHelper {
|
||||
|
||||
private const val HMAC_TYPE = "HmacSHA256"
|
||||
|
||||
|
||||
private const val EXTRA_SEARCH_INFO = "com.kunzisoft.keepass.extra.searchInfo"
|
||||
private const val EXTRA_APP_ORIGIN = "com.kunzisoft.keepass.extra.appOrigin"
|
||||
private const val EXTRA_NODE_ID = "com.kunzisoft.keepass.extra.nodeId"
|
||||
private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp"
|
||||
private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode"
|
||||
|
||||
@@ -110,38 +106,6 @@ object PasskeyHelper {
|
||||
|
||||
private val internalSecureRandom: SecureRandom = SecureRandom()
|
||||
|
||||
/**
|
||||
* Build the Passkey response for one entry
|
||||
*/
|
||||
fun Activity.buildPasskeyResponseAndSetResult(
|
||||
entryInfo: EntryInfo,
|
||||
extras: Bundle? = null
|
||||
) {
|
||||
try {
|
||||
entryInfo.passkey?.let { passkey ->
|
||||
val mReplyIntent = Intent()
|
||||
Log.d(javaClass.name, "Success Passkey manual selection")
|
||||
mReplyIntent.addPasskey(passkey)
|
||||
mReplyIntent.addAppOrigin(entryInfo.appOrigin)
|
||||
mReplyIntent.addNodeId(entryInfo.id)
|
||||
extras?.let {
|
||||
mReplyIntent.putExtras(it)
|
||||
}
|
||||
setResult(Activity.RESULT_OK, mReplyIntent)
|
||||
} ?: run {
|
||||
throw IOException("No passkey found")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(javaClass.name, "Unable to add the passkey as result", e)
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.error_passkey_result),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an authentication code generated by an entry to the intent
|
||||
*/
|
||||
@@ -181,22 +145,6 @@ object PasskeyHelper {
|
||||
return this.removeExtra(EXTRA_PASSKEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the search info to the intent
|
||||
*/
|
||||
fun Intent.addSearchInfo(searchInfo: SearchInfo?) {
|
||||
searchInfo?.let {
|
||||
putExtra(EXTRA_SEARCH_INFO, searchInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the search info from the intent
|
||||
*/
|
||||
fun Intent.retrieveSearchInfo(): SearchInfo? {
|
||||
return this.getParcelableExtraCompat(EXTRA_SEARCH_INFO)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the app origin to the intent
|
||||
*/
|
||||
@@ -221,19 +169,35 @@ object PasskeyHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the node id to the intent, useful for auto passkey selection
|
||||
* Build the Passkey response for one entry
|
||||
*/
|
||||
fun Intent.addNodeId(nodeId: UUID?) {
|
||||
nodeId?.let {
|
||||
putExtra(EXTRA_NODE_ID, ParcelUuid(nodeId))
|
||||
fun Activity.buildPasskeyResponseAndSetResult(
|
||||
entryInfo: EntryInfo,
|
||||
extras: Bundle? = null
|
||||
) {
|
||||
try {
|
||||
entryInfo.passkey?.let { passkey ->
|
||||
val mReplyIntent = Intent()
|
||||
Log.d(javaClass.name, "Success Passkey manual selection")
|
||||
mReplyIntent.addPasskey(passkey)
|
||||
mReplyIntent.addAppOrigin(entryInfo.appOrigin)
|
||||
mReplyIntent.addNodeId(entryInfo.id)
|
||||
extras?.let {
|
||||
mReplyIntent.putExtras(it)
|
||||
}
|
||||
setResult(Activity.RESULT_OK, mReplyIntent)
|
||||
} ?: run {
|
||||
throw IOException("No passkey found")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(javaClass.name, "Unable to add the passkey as result", e)
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.error_passkey_result),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the node id from the intent
|
||||
*/
|
||||
fun Intent.retrieveNodeId(): UUID? {
|
||||
return getParcelableExtraCompat<ParcelUuid>(EXTRA_NODE_ID)?.uuid
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
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.os.Build
|
||||
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.retrieveRegisterInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
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.helper.SearchHelper
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
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
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class AutofillLauncherViewModel(application: Application): CredentialLauncherViewModel(application) {
|
||||
|
||||
private var mAutofillComponent: AutofillComponent? = null
|
||||
private var mSelectionResult: ActivityResult? = null
|
||||
|
||||
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = mUiState
|
||||
|
||||
override fun onResult() {
|
||||
super.onResult()
|
||||
mAutofillComponent = null
|
||||
mSelectionResult = null
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
if (database != null) {
|
||||
mSelectionResult?.let { selectionResult ->
|
||||
manageSelectionResult(database, selectionResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun launchAction(
|
||||
intent: Intent,
|
||||
specialMode: SpecialMode,
|
||||
database: ContextualDatabase?
|
||||
) {
|
||||
// Retrieve selection mode
|
||||
when (intent.retrieveSpecialMode()) {
|
||||
SpecialMode.SELECTION -> {
|
||||
val searchInfo = intent.retrieveSearchInfo()
|
||||
if (searchInfo == null)
|
||||
throw IOException("Search info is null")
|
||||
mAutofillComponent = intent.retrieveAutofillComponent()
|
||||
// Build search param
|
||||
launchSelection(database, mAutofillComponent, searchInfo)
|
||||
}
|
||||
SpecialMode.REGISTRATION -> {
|
||||
// To register info
|
||||
val registerInfo = intent.retrieveRegisterInfo()
|
||||
if (registerInfo == null)
|
||||
throw IOException("Register info is null")
|
||||
launchRegistration(database, registerInfo)
|
||||
}
|
||||
else -> {
|
||||
// Not an autofill call
|
||||
cancelResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun launchSelection(
|
||||
database: ContextualDatabase?,
|
||||
autofillComponent: AutofillComponent?,
|
||||
searchInfo: SearchInfo
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (autofillComponent == null) {
|
||||
throw IOException("Autofill component is null")
|
||||
}
|
||||
if (KeeAutofillService.autofillAllowedFor(
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = getApplication()
|
||||
)
|
||||
) {
|
||||
// If database is open
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = getApplication(),
|
||||
database = database,
|
||||
searchInfo = searchInfo,
|
||||
onItemsFound = { openedDatabase, items ->
|
||||
// Items found
|
||||
if (autofillComponent.compatInlineSuggestionsRequest != null) {
|
||||
mUiState.value = UIState.ShowAutofillSuggestionMessage
|
||||
}
|
||||
AutofillHelper.buildResponse(
|
||||
context = getApplication(),
|
||||
autofillComponent = autofillComponent,
|
||||
database = openedDatabase,
|
||||
entriesInfo = items
|
||||
) { intent ->
|
||||
setResult(intent)
|
||||
}
|
||||
},
|
||||
onItemNotFound = { openedDatabase ->
|
||||
// Show the database UI to select the entry
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection(
|
||||
database = openedDatabase,
|
||||
searchInfo = searchInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
// If database not open
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection(
|
||||
searchInfo = searchInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
mUiState.value = UIState.ShowBlockRestartMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun manageSelectionResult(activityResult: ActivityResult) {
|
||||
mSelectionResult = activityResult
|
||||
// Waiting for the database if needed
|
||||
mDatabase?.let { database ->
|
||||
manageSelectionResult(database, activityResult)
|
||||
}
|
||||
}
|
||||
|
||||
private fun manageSelectionResult(
|
||||
database: ContextualDatabase,
|
||||
activityResult: ActivityResult
|
||||
) {
|
||||
mSelectionResult = null
|
||||
val intent = activityResult.data
|
||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||
Log.e(TAG, "Unable to create selection response for autofill", e)
|
||||
showError(e)
|
||||
}) {
|
||||
when (activityResult.resultCode) {
|
||||
RESULT_OK -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
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 autofillComponent = mAutofillComponent
|
||||
if (autofillComponent == null)
|
||||
throw IOException("Autofill component is null")
|
||||
val entries = nodesIds.mapNotNull { nodeId ->
|
||||
database
|
||||
.getEntryById(NodeIdUUID(nodeId))
|
||||
?.getEntryInfo(database)
|
||||
}
|
||||
AutofillHelper.buildResponse(
|
||||
context = getApplication(),
|
||||
autofillComponent = autofillComponent,
|
||||
database = database,
|
||||
entriesInfo = entries
|
||||
) { intent ->
|
||||
withContext(Dispatchers.Main) {
|
||||
setResult(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
cancelResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Registration
|
||||
// -------------
|
||||
|
||||
private fun launchRegistration(
|
||||
database: ContextualDatabase?,
|
||||
registerInfo: RegisterInfo
|
||||
) {
|
||||
val searchInfo = registerInfo.searchInfo
|
||||
if (KeeAutofillService.autofillAllowedFor(
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = getApplication()
|
||||
)) {
|
||||
val readOnly = database?.isReadOnly != false
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = getApplication(),
|
||||
database = database,
|
||||
searchInfo = searchInfo,
|
||||
onItemsFound = { openedDatabase, _ ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration(
|
||||
database = openedDatabase,
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
} else {
|
||||
mUiState.value = UIState.ShowReadOnlyMessage
|
||||
}
|
||||
},
|
||||
onItemNotFound = { openedDatabase ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration(
|
||||
database = openedDatabase,
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
} else {
|
||||
mUiState.value = UIState.ShowReadOnlyMessage
|
||||
}
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
// If database not open
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration(
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.AUTOFILL
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
mUiState.value = UIState.ShowBlockRestartMessage
|
||||
}
|
||||
}
|
||||
|
||||
fun manageRegistrationResult(
|
||||
activityResult: ActivityResult
|
||||
) {
|
||||
val intent = activityResult.data
|
||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||
Log.e(TAG, "Unable to create registration response for autofill", e)
|
||||
showError(e)
|
||||
}) {
|
||||
val responseIntent = Intent()
|
||||
when (activityResult.resultCode) {
|
||||
RESULT_OK -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Autofill registration result")
|
||||
// TODO Result
|
||||
withContext(Dispatchers.Main) {
|
||||
setResult(responseIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
cancelResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sealed class UIState {
|
||||
object Loading: UIState()
|
||||
object ShowBlockRestartMessage: UIState()
|
||||
object ShowReadOnlyMessage: UIState()
|
||||
object ShowAutofillSuggestionMessage: UIState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = AutofillLauncherViewModel::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
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.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
abstract class CredentialLauncherViewModel(application: Application): AndroidViewModel(application) {
|
||||
|
||||
protected var mDatabase: ContextualDatabase? = null
|
||||
protected var mLockDatabase: Boolean = true
|
||||
|
||||
protected var isResultLauncherRegistered: Boolean = false
|
||||
|
||||
protected val mCredentialUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val credentialUiState: StateFlow<UIState> = mCredentialUiState
|
||||
|
||||
fun showError(error: Throwable) {
|
||||
Log.e(TAG, "Error on credential provider launch", error)
|
||||
mCredentialUiState.value = UIState.ShowError(error)
|
||||
}
|
||||
|
||||
open fun onResult() {
|
||||
isResultLauncherRegistered = false
|
||||
}
|
||||
|
||||
fun setResult(intent: Intent) {
|
||||
// Remove the launcher register
|
||||
onResult()
|
||||
mCredentialUiState.value = UIState.SetActivityResult(
|
||||
lockDatabase = mLockDatabase,
|
||||
resultCode = RESULT_OK,
|
||||
data = intent
|
||||
)
|
||||
}
|
||||
|
||||
fun cancelResult() {
|
||||
onResult()
|
||||
mCredentialUiState.value = UIState.SetActivityResult(
|
||||
lockDatabase = mLockDatabase,
|
||||
resultCode = RESULT_CANCELED
|
||||
)
|
||||
}
|
||||
|
||||
open fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||
mDatabase = database
|
||||
}
|
||||
|
||||
fun launchActionIfNeeded(
|
||||
intent: Intent,
|
||||
specialMode: SpecialMode,
|
||||
database: ContextualDatabase?
|
||||
) {
|
||||
onDatabaseRetrieved(database)
|
||||
if (isResultLauncherRegistered.not()) {
|
||||
isResultLauncherRegistered = true
|
||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||
showError(e)
|
||||
}) {
|
||||
launchAction(intent, specialMode, database)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the main action
|
||||
*/
|
||||
protected abstract suspend fun launchAction(
|
||||
intent: Intent,
|
||||
specialMode: SpecialMode,
|
||||
database: ContextualDatabase?
|
||||
)
|
||||
|
||||
sealed class UIState {
|
||||
object Loading : UIState()
|
||||
data class LaunchGroupActivityForSelection(
|
||||
val database: ContextualDatabase,
|
||||
val searchInfo: SearchInfo?,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchGroupActivityForRegistration(
|
||||
val database: ContextualDatabase,
|
||||
val registerInfo: RegisterInfo?,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchFileDatabaseSelectActivityForSelection(
|
||||
val searchInfo: SearchInfo?,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchFileDatabaseSelectActivityForRegistration(
|
||||
val registerInfo: RegisterInfo?,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class SetActivityResult(
|
||||
val lockDatabase: Boolean,
|
||||
val resultCode: Int,
|
||||
val data: Intent? = null
|
||||
): UIState()
|
||||
data class ShowError(
|
||||
val error: Throwable
|
||||
): UIState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = CredentialLauncherViewModel::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import androidx.credentials.exceptions.GetCredentialUnknownException
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.passkey.data.AndroidPrivilegedApp
|
||||
@@ -25,11 +27,9 @@ import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVe
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeAppOrigin
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveAppOrigin
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveNodeId
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrievePasskey
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrievePasskeyCreationRequestParameters
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrievePasskeyUsageRequestParameters
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.saveCustomPrivilegedApps
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
@@ -261,14 +261,17 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
"launch manual selection in opened database"
|
||||
)
|
||||
_uiState.value = UIState.LaunchGroupActivityForSelection(
|
||||
database = openedDatabase
|
||||
database = openedDatabase,
|
||||
searchInfo = searchInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
)
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
Log.d(TAG, "Manual passkey selection in closed database")
|
||||
_uiState.value =
|
||||
UIState.LaunchFileDatabaseSelectActivityForSelection(
|
||||
searchInfo = searchInfo
|
||||
searchInfo = searchInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -550,7 +553,9 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
val nodeId: UUID
|
||||
): UIState()
|
||||
data class LaunchGroupActivityForSelection(
|
||||
val database: ContextualDatabase
|
||||
val database: ContextualDatabase,
|
||||
val searchInfo: SearchInfo?,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchGroupActivityForRegistration(
|
||||
val database: ContextualDatabase,
|
||||
@@ -558,7 +563,8 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchFileDatabaseSelectActivityForSelection(
|
||||
val searchInfo: SearchInfo
|
||||
val searchInfo: SearchInfo,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchFileDatabaseSelectActivityForRegistration(
|
||||
val registerInfo: RegisterInfo,
|
||||
|
||||
@@ -54,6 +54,7 @@ object SearchHelper {
|
||||
onItemNotFound: (openedDatabase: ContextualDatabase) -> Unit,
|
||||
onDatabaseClosed: () -> Unit
|
||||
) {
|
||||
// TODO suspend
|
||||
if (database == null || !database.loaded) {
|
||||
onDatabaseClosed.invoke()
|
||||
} else if (TimeoutHelper.checkTime(context)) {
|
||||
|
||||
@@ -23,6 +23,10 @@ import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
|
||||
object AppUtil {
|
||||
|
||||
fun randomRequestCode(): Int {
|
||||
return (Math.random() * Integer.MAX_VALUE).toInt()
|
||||
}
|
||||
|
||||
fun Context.isExternalAppInstalled(packageName: String, showError: Boolean = true): Boolean {
|
||||
try {
|
||||
this.applicationContext.packageManager.getPackageInfoCompat(
|
||||
@@ -83,9 +87,10 @@ object AppUtil {
|
||||
/**
|
||||
* Get the concrete web domain AKA without sub domain if needed
|
||||
*/
|
||||
fun SearchInfo.getConcreteWebDomain(
|
||||
fun getConcreteWebDomain(
|
||||
context: Context,
|
||||
concreteWebDomain: (String?) -> Unit
|
||||
webDomain: String?,
|
||||
concreteWebDomain: suspend (String?) -> Unit
|
||||
) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val domain = webDomain
|
||||
|
||||
@@ -39,7 +39,7 @@ inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: Str
|
||||
else -> @Suppress("DEPRECATION") getSerializableExtra(key) as? T
|
||||
}
|
||||
|
||||
inline fun <reified E : Parcelable> Intent.putParcelableList(key: String?, list: MutableList<E>) {
|
||||
inline fun <reified E : Parcelable> Intent.putParcelableList(key: String?, list: List<E>) {
|
||||
putExtra(key, list.toTypedArray())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user