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