mirror of
https://github.com/bitwarden/android.git
synced 2026-06-08 23:16:33 -05:00
Send autofill selections back to autofill flow (#829)
This commit is contained in:
committed by
Álison Fernandes
parent
b199a67b7d
commit
b3fa33a02c
@@ -12,6 +12,7 @@ import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavScreen
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
@@ -28,6 +29,9 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val mainViewModel: MainViewModel by viewModels()
|
||||
|
||||
@Inject
|
||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
@@ -79,6 +83,10 @@ class MainActivity : AppCompatActivity() {
|
||||
.eventFlow
|
||||
.onEach { event ->
|
||||
when (event) {
|
||||
is MainEvent.CompleteAutofill -> {
|
||||
handleCompleteAutofill(event)
|
||||
}
|
||||
|
||||
is MainEvent.ScreenCaptureSettingChange -> {
|
||||
handleScreenCaptureSettingChange(event)
|
||||
}
|
||||
@@ -87,6 +95,13 @@ class MainActivity : AppCompatActivity() {
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun handleCompleteAutofill(event: MainEvent.CompleteAutofill) {
|
||||
autofillCompletionManager.completeAutofill(
|
||||
activity = this,
|
||||
cipherView = event.cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleScreenCaptureSettingChange(event: MainEvent.ScreenCaptureSettingChange) {
|
||||
if (event.isAllowed) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.Intent
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
@@ -25,6 +27,7 @@ private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
||||
*/
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val autofillSelectionManager: AutofillSelectionManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val intentManager: IntentManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
@@ -49,6 +52,11 @@ class MainViewModel @Inject constructor(
|
||||
.onEach { specialCircumstance = it }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
autofillSelectionManager
|
||||
.autofillSelectionFlow
|
||||
.onEach { trySendAction(MainAction.Internal.AutofillSelectionReceive(it)) }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.appThemeStateFlow
|
||||
.onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) }
|
||||
@@ -64,12 +72,22 @@ class MainViewModel @Inject constructor(
|
||||
|
||||
override fun handleAction(action: MainAction) {
|
||||
when (action) {
|
||||
is MainAction.Internal.AutofillSelectionReceive -> {
|
||||
handleAutofillSelectionReceive(action)
|
||||
}
|
||||
|
||||
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
|
||||
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
|
||||
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAutofillSelectionReceive(
|
||||
action: MainAction.Internal.AutofillSelectionReceive,
|
||||
) {
|
||||
sendEvent(MainEvent.CompleteAutofill(cipherView = action.cipherView))
|
||||
}
|
||||
|
||||
private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) {
|
||||
mutableStateFlow.update { it.copy(theme = action.theme) }
|
||||
}
|
||||
@@ -144,6 +162,13 @@ sealed class MainAction {
|
||||
* Actions for internal use by the ViewModel.
|
||||
*/
|
||||
sealed class Internal : MainAction() {
|
||||
/**
|
||||
* Indicates the user has manually selected the given [cipherView] for autofill.
|
||||
*/
|
||||
data class AutofillSelectionReceive(
|
||||
val cipherView: CipherView,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the app theme has changed.
|
||||
*/
|
||||
@@ -157,6 +182,11 @@ sealed class MainAction {
|
||||
* Represents events that are emitted by the [MainViewModel].
|
||||
*/
|
||||
sealed class MainEvent {
|
||||
/**
|
||||
* Event indicating that the user has chosen the given [cipherView] for autofill and that the
|
||||
* process is ready to complete.
|
||||
*/
|
||||
data class CompleteAutofill(val cipherView: CipherView) : MainEvent()
|
||||
|
||||
/**
|
||||
* Event indicating a change in the screen capture setting.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.x8bit.bitwarden.data.autofill.di
|
||||
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityRetainedComponent
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
|
||||
/**
|
||||
* Provides dependencies in the autofill package that must be scoped to a retained Activity. These
|
||||
* are for dependencies that must operate independently in different application tasks that contain
|
||||
* unique [MainActivity] instances.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(ActivityRetainedComponent::class)
|
||||
object ActivityAutofillModule {
|
||||
|
||||
@ActivityRetainedScoped
|
||||
@Provides
|
||||
fun provideAutofillSelectionManager(): AutofillSelectionManager =
|
||||
AutofillSelectionManagerImpl()
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import com.x8bit.bitwarden.data.autofill.builder.FillResponseBuilder
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FillResponseBuilderImpl
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilder
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilderImpl
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
|
||||
import com.x8bit.bitwarden.data.autofill.parser.AutofillParserImpl
|
||||
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessor
|
||||
@@ -36,6 +38,17 @@ object AutofillModule {
|
||||
@ApplicationContext context: Context,
|
||||
): AutofillManager = context.getSystemService(AutofillManager::class.java)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAutofillCompletionManager(
|
||||
autofillParser: AutofillParser,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): AutofillCompletionManager =
|
||||
AutofillCompletionManagerImpl(
|
||||
autofillParser = autofillParser,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
fun providesAutofillParser(
|
||||
settingsRepository: SettingsRepository,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.x8bit.bitwarden.data.autofill.manager
|
||||
|
||||
import android.app.Activity
|
||||
import com.bitwarden.core.CipherView
|
||||
|
||||
/**
|
||||
* A manager for completing the autofill process after the user has made a selection.
|
||||
*/
|
||||
interface AutofillCompletionManager {
|
||||
|
||||
/**
|
||||
* Completes the autofill flow originating with the given [activity] using the selected
|
||||
* [cipherView].
|
||||
*/
|
||||
fun completeAutofill(
|
||||
activity: Activity,
|
||||
cipherView: CipherView,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.x8bit.bitwarden.data.autofill.manager
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilder
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilderImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillCipher
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
||||
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
|
||||
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
|
||||
import com.x8bit.bitwarden.data.autofill.util.buildDataset
|
||||
import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionResultIntent
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillAssistStructureOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.toAutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.util.subtitle
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Primary implementation of [AutofillCompletionManager].
|
||||
*/
|
||||
class AutofillCompletionManagerImpl(
|
||||
private val autofillParser: AutofillParser,
|
||||
private val dispatcherManager: DispatcherManager,
|
||||
private val filledDataBuilderProvider: (CipherView) -> FilledDataBuilder =
|
||||
{ createSingleItemFilledDataBuilder(cipherView = it) },
|
||||
) : AutofillCompletionManager {
|
||||
private val mainScope = CoroutineScope(dispatcherManager.main)
|
||||
|
||||
override fun completeAutofill(
|
||||
activity: Activity,
|
||||
cipherView: CipherView,
|
||||
) {
|
||||
val autofillAppInfo = activity.toAutofillAppInfo()
|
||||
val assistStructure = activity
|
||||
.intent
|
||||
?.getAutofillAssistStructureOrNull()
|
||||
?: run {
|
||||
activity.cancelAndFinish()
|
||||
return
|
||||
}
|
||||
|
||||
val autofillRequest = autofillParser
|
||||
.parse(
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
assistStructure = assistStructure,
|
||||
)
|
||||
if (autofillRequest !is AutofillRequest.Fillable) {
|
||||
activity.cancelAndFinish()
|
||||
return
|
||||
}
|
||||
|
||||
val fillDataBuilder = filledDataBuilderProvider(cipherView)
|
||||
// We'll launch a coroutine here but this code will technically run synchronously given
|
||||
// how we've constructed a single-item AutofillCipherProvider.
|
||||
mainScope.launch {
|
||||
val dataset = fillDataBuilder
|
||||
.build(autofillRequest)
|
||||
.filledPartitions
|
||||
.firstOrNull()
|
||||
?.buildDataset(autofillAppInfo = autofillAppInfo)
|
||||
?: run {
|
||||
activity.cancelAndFinish()
|
||||
return@launch
|
||||
}
|
||||
val resultIntent = createAutofillSelectionResultIntent(dataset)
|
||||
activity.setResultAndFinish(resultIntent = resultIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSingleItemFilledDataBuilder(
|
||||
cipherView: CipherView,
|
||||
): FilledDataBuilder =
|
||||
FilledDataBuilderImpl(
|
||||
autofillCipherProvider = cipherView.toAutofillCipherProvider(),
|
||||
)
|
||||
|
||||
private fun Activity.cancelAndFinish() {
|
||||
this.setResult(Activity.RESULT_CANCELED)
|
||||
this.finish()
|
||||
}
|
||||
|
||||
private fun Activity.setResultAndFinish(resultIntent: Intent) {
|
||||
this.setResult(Activity.RESULT_OK, resultIntent)
|
||||
this.finish()
|
||||
}
|
||||
|
||||
private fun CipherView.toAutofillCipherProvider(): AutofillCipherProvider =
|
||||
object : AutofillCipherProvider {
|
||||
override suspend fun isVaultLocked(): Boolean = true
|
||||
|
||||
override suspend fun getCardAutofillCiphers(): List<AutofillCipher.Card> {
|
||||
val card = this@toAutofillCipherProvider.card ?: return emptyList()
|
||||
return listOf(
|
||||
AutofillCipher.Card(
|
||||
name = name,
|
||||
subtitle = subtitle.orEmpty(),
|
||||
cardholderName = card.cardholderName.orEmpty(),
|
||||
code = card.code.orEmpty(),
|
||||
expirationMonth = card.expMonth.orEmpty(),
|
||||
expirationYear = card.expYear.orEmpty(),
|
||||
number = card.number.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getLoginAutofillCiphers(
|
||||
uri: String,
|
||||
): List<AutofillCipher.Login> {
|
||||
val login = this@toAutofillCipherProvider.login ?: return emptyList()
|
||||
return listOf(
|
||||
AutofillCipher.Login(
|
||||
name = name,
|
||||
password = login.password.orEmpty(),
|
||||
subtitle = subtitle.orEmpty(),
|
||||
username = login.username.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.data.autofill.manager
|
||||
|
||||
import com.bitwarden.core.CipherView
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Tracks the selection of a [CipherView] during the autofill flow within the app.
|
||||
*/
|
||||
interface AutofillSelectionManager {
|
||||
|
||||
/**
|
||||
* Emits a [CipherView] as a result of calls to [emitAutofillSelection].
|
||||
*/
|
||||
val autofillSelectionFlow: Flow<CipherView>
|
||||
|
||||
/**
|
||||
* Triggers an emission via [autofillSelectionFlow].
|
||||
*/
|
||||
fun emitAutofillSelection(cipherView: CipherView)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.data.autofill.manager
|
||||
|
||||
import com.bitwarden.core.CipherView
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
|
||||
/**
|
||||
* Primary implementation of [AutofillSelectionManager].
|
||||
*/
|
||||
class AutofillSelectionManagerImpl : AutofillSelectionManager {
|
||||
private val autofillSelectionChannel = Channel<CipherView>(capacity = Int.MAX_VALUE)
|
||||
|
||||
override val autofillSelectionFlow: Flow<CipherView> =
|
||||
autofillSelectionChannel.receiveAsFlow()
|
||||
|
||||
override fun emitAutofillSelection(cipherView: CipherView) {
|
||||
autofillSelectionChannel.trySend(cipherView)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.data.autofill.parser
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
import android.service.autofill.FillRequest
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
||||
@@ -19,4 +20,15 @@ interface AutofillParser {
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
fillRequest: FillRequest,
|
||||
): AutofillRequest
|
||||
|
||||
/**
|
||||
* Parse the useful information from [assistStructure] into an [AutofillRequest].
|
||||
*
|
||||
* @param autofillAppInfo Provides app context that is required to properly parse the request.
|
||||
* @param assistStructure The key data from the original request that needs parsing.
|
||||
*/
|
||||
fun parse(
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
assistStructure: AssistStructure,
|
||||
): AutofillRequest
|
||||
}
|
||||
|
||||
@@ -40,13 +40,23 @@ class AutofillParserImpl(
|
||||
}
|
||||
?: AutofillRequest.Unfillable
|
||||
|
||||
override fun parse(
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
assistStructure: AssistStructure,
|
||||
): AutofillRequest =
|
||||
parseInternal(
|
||||
assistStructure = assistStructure,
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
fillRequest = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Parse the [AssistStructure] into an [AutofillRequest].
|
||||
*/
|
||||
private fun parseInternal(
|
||||
assistStructure: AssistStructure,
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
fillRequest: FillRequest,
|
||||
fillRequest: FillRequest?,
|
||||
): AutofillRequest {
|
||||
// Parse the `assistStructure` into internal models.
|
||||
val traversalDataList = assistStructure.traverse()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
@file:OmitFromCoverage
|
||||
|
||||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* Build an [AutofillAppInfo] from the given [Activity].
|
||||
*/
|
||||
fun Activity.toAutofillAppInfo(): AutofillAppInfo =
|
||||
AutofillAppInfo(
|
||||
context = this.applicationContext,
|
||||
packageName = this.packageName,
|
||||
sdkInt = Build.VERSION.SDK_INT,
|
||||
)
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.autofill.Dataset
|
||||
import android.view.autofill.AutofillManager
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.data.platform.util.getSafeParcelableExtra
|
||||
|
||||
private const val AUTOFILL_SELECTION_DATA_KEY = "autofill-selection-data"
|
||||
|
||||
@@ -33,14 +36,28 @@ fun createAutofillSelectionIntent(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [Intent] in order to specify that there is a successful selection during a manual
|
||||
* autofill process.
|
||||
*/
|
||||
fun createAutofillSelectionResultIntent(
|
||||
dataset: Dataset,
|
||||
): Intent =
|
||||
Intent()
|
||||
.apply {
|
||||
putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains an [AssistStructure] related to an ongoing manual autofill
|
||||
* selection process.
|
||||
*/
|
||||
fun Intent.getAutofillAssistStructureOrNull(): AssistStructure? =
|
||||
this.getSafeParcelableExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE)
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains data about an ongoing manual autofill selection process.
|
||||
* The [AutofillSelectionData] will be returned when present.
|
||||
*/
|
||||
fun Intent.getAutofillSelectionDataOrNull(): AutofillSelectionData? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
this.getParcelableExtra(AUTOFILL_SELECTION_DATA_KEY, AutofillSelectionData::class.java)
|
||||
} else {
|
||||
this.getParcelableExtra(AUTOFILL_SELECTION_DATA_KEY)
|
||||
}
|
||||
}
|
||||
fun Intent.getAutofillSelectionDataOrNull(): AutofillSelectionData? =
|
||||
this.getSafeParcelableExtra(AUTOFILL_SELECTION_DATA_KEY)
|
||||
|
||||
@@ -10,11 +10,14 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
* Extract the list of [InlinePresentationSpec]s. If it fails, return an empty list.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun FillRequest.getInlinePresentationSpecs(
|
||||
fun FillRequest?.getInlinePresentationSpecs(
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
isInlineAutofillEnabled: Boolean,
|
||||
): List<InlinePresentationSpec> =
|
||||
if (isInlineAutofillEnabled && autofillAppInfo.sdkInt >= Build.VERSION_CODES.R) {
|
||||
if (this != null &&
|
||||
isInlineAutofillEnabled &&
|
||||
autofillAppInfo.sdkInt >= Build.VERSION_CODES.R
|
||||
) {
|
||||
inlineSuggestionsRequest?.inlinePresentationSpecs ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
@@ -25,11 +28,14 @@ fun FillRequest.getInlinePresentationSpecs(
|
||||
* return 0.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun FillRequest.getMaxInlineSuggestionsCount(
|
||||
fun FillRequest?.getMaxInlineSuggestionsCount(
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
isInlineAutofillEnabled: Boolean,
|
||||
): Int =
|
||||
if (isInlineAutofillEnabled && autofillAppInfo.sdkInt >= Build.VERSION_CODES.R) {
|
||||
if (this != null &&
|
||||
isInlineAutofillEnabled &&
|
||||
autofillAppInfo.sdkInt >= Build.VERSION_CODES.R
|
||||
) {
|
||||
inlineSuggestionsRequest?.maxSuggestionCount ?: 0
|
||||
} else {
|
||||
0
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
@file:OmitFromCoverage
|
||||
|
||||
package com.x8bit.bitwarden.data.platform.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import android.view.autofill.AutofillManager
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* A means of retrieving a [Parcelable] from an [Intent] using the given [name] in a manner that
|
||||
* is safe across SDK versions.
|
||||
*/
|
||||
inline fun <reified T> Intent.getSafeParcelableExtra(name: String): T? =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getParcelableExtra(
|
||||
name,
|
||||
T::class.java,
|
||||
)
|
||||
} else {
|
||||
getParcelableExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
@@ -55,6 +56,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val autofillSelectionManager: AutofillSelectionManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
||||
initialState = run {
|
||||
@@ -180,6 +182,12 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
|
||||
if (state.isAutofill) {
|
||||
val cipherView = getCipherViewOrNull(action.id) ?: return
|
||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||
return
|
||||
}
|
||||
|
||||
val event = when (state.itemListingType) {
|
||||
is VaultItemListingState.ItemListingType.Vault -> {
|
||||
VaultItemListingEvent.NavigateToVaultItem(id = action.id)
|
||||
@@ -518,6 +526,14 @@ class VaultItemListingViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCipherViewOrNull(cipherId: String) =
|
||||
vaultRepository
|
||||
.vaultDataStateFlow
|
||||
.value
|
||||
.data
|
||||
?.cipherViewList
|
||||
?.firstOrNull { it.id == cipherId }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user