Get autofill enabled information more reliably (#867)

This commit is contained in:
Brian Yencho
2024-01-30 09:43:55 -06:00
committed by Álison Fernandes
parent 2de2ade7a6
commit f93db195c0
14 changed files with 281 additions and 92 deletions

View File

@@ -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.AutofillActivityManager
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
@@ -29,6 +30,9 @@ class MainActivity : AppCompatActivity() {
private val mainViewModel: MainViewModel by viewModels()
@Inject
lateinit var autofillActivityManager: AutofillActivityManager
@Inject
lateinit var autofillCompletionManager: AutofillCompletionManager

View File

@@ -1,25 +1,49 @@
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 android.app.Activity
import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.scopes.ActivityRetainedScoped
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
/**
* 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.
* Provides dependencies in the autofill package that must be scoped to a single Activity. These
* are for dependencies that require a very specific Activity's context to operate.
*/
@Module
@InstallIn(ActivityRetainedComponent::class)
@InstallIn(ActivityComponent::class)
object ActivityAutofillModule {
@ActivityRetainedScoped
@ActivityScoped
@Provides
fun provideAutofillSelectionManager(): AutofillSelectionManager =
AutofillSelectionManagerImpl()
fun provideAutofillActivityManager(
@ActivityScopedManager autofillManager: AutofillManager,
appForegroundManager: AppForegroundManager,
autofillEnabledManager: AutofillEnabledManager,
dispatcherManager: DispatcherManager,
): AutofillActivityManager =
AutofillActivityManagerImpl(
autofillManager = autofillManager,
appForegroundManager = appForegroundManager,
autofillEnabledManager = autofillEnabledManager,
dispatcherManager = dispatcherManager,
)
/**
* An AutofillManager specific to the given Activity. This wll give more accurate results
* compared to the global manager.
*/
@ActivityScoped
@ActivityScopedManager
@Provides
fun provideActivityScopedAutofillManager(
activity: Activity,
): AutofillManager = activity.getSystemService(AutofillManager::class.java)
}

View File

@@ -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 ActivityRetainedAutofillModule {
@ActivityRetainedScoped
@Provides
fun provideAutofillSelectionManager(): AutofillSelectionManager =
AutofillSelectionManagerImpl()
}

View File

@@ -0,0 +1,10 @@
package com.x8bit.bitwarden.data.autofill.di
import javax.inject.Qualifier
/**
* Used to denote that the particular manager is scoped to a single Activity instance.
*/
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScopedManager

View File

@@ -9,6 +9,8 @@ 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.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManagerImpl
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
import com.x8bit.bitwarden.data.autofill.parser.AutofillParserImpl
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessor
@@ -39,6 +41,11 @@ object AutofillModule {
@ApplicationContext context: Context,
): AutofillManager = context.getSystemService(AutofillManager::class.java)
@Singleton
@Provides
fun providesAutofillEnabledManager(): AutofillEnabledManager =
AutofillEnabledManagerImpl()
@Singleton
@Provides
fun provideAutofillCompletionManager(

View File

@@ -0,0 +1,10 @@
package com.x8bit.bitwarden.data.autofill.manager
import android.app.Activity
/**
* A helper for dealing with autofill configuration that must be scoped to a specific [Activity].
* In particular, this should be injected into an [Activity] to ensure that an
* [AutofillEnabledManager] reports correct values.
*/
interface AutofillActivityManager

View File

@@ -0,0 +1,34 @@
package com.x8bit.bitwarden.data.autofill.manager
import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
/**
* Primary implementation of [AutofillActivityManager].
*/
class AutofillActivityManagerImpl(
private val autofillManager: AutofillManager,
private val appForegroundManager: AppForegroundManager,
private val autofillEnabledManager: AutofillEnabledManager,
private val dispatcherManager: DispatcherManager,
) : AutofillActivityManager {
private val isAutofillEnabledAndSupported: Boolean
get() = autofillManager.isEnabled &&
autofillManager.hasEnabledAutofillServices() &&
autofillManager.isAutofillSupported
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
init {
appForegroundManager
.appForegroundStateFlow
.onEach {
autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported
}
.launchIn(unconfinedScope)
}
}

View File

@@ -0,0 +1,22 @@
package com.x8bit.bitwarden.data.autofill.manager
import kotlinx.coroutines.flow.StateFlow
/**
* A container for values specifying whether or not autofill is enabled. These values should be
* filled by an [AutofillActivityManager].
*/
interface AutofillEnabledManager {
/**
* Whether or not autofill should be considered enabled.
*
* Note that changing this does not enable or disable autofill; it is only an indicator that
* this has occurred elsewhere.
*/
var isAutofillEnabled: Boolean
/**
* Emits updates that track [isAutofillEnabled] values.
*/
val isAutofillEnabledStateFlow: StateFlow<Boolean>
}

View File

@@ -0,0 +1,21 @@
package com.x8bit.bitwarden.data.autofill.manager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* Primary implementation of [AutofillEnabledManager].
*/
class AutofillEnabledManagerImpl : AutofillEnabledManager {
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(false)
override var isAutofillEnabled: Boolean
get() = mutableIsAutofillEnabledStateFlow.value
set(value) {
mutableIsAutofillEnabledStateFlow.value = value
}
override val isAutofillEnabledStateFlow: StateFlow<Boolean>
get() = mutableIsAutofillEnabledStateFlow.asStateFlow()
}

View File

@@ -4,8 +4,8 @@ import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.repository.model.UserFingerprintResult
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.BiometricsKeyResult
@@ -21,13 +21,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.time.Instant
@@ -40,7 +36,7 @@ private val DEFAULT_IS_SCREEN_CAPTURE_ALLOWED = BuildConfig.DEBUG
@Suppress("TooManyFunctions", "LongParameterList")
class SettingsRepositoryImpl(
private val autofillManager: AutofillManager,
private val appForegroundManager: AppForegroundManager,
private val autofillEnabledManager: AutofillEnabledManager,
private val authDiskSource: AuthDiskSource,
private val settingsDiskSource: SettingsDiskSource,
private val vaultSdkSource: VaultSdkSource,
@@ -51,13 +47,6 @@ class SettingsRepositoryImpl(
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
private val isAutofillEnabledAndSupported: Boolean
get() = autofillManager.isEnabled &&
autofillManager.hasEnabledAutofillServices() &&
autofillManager.isAutofillSupported
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(isAutofillEnabledAndSupported)
override var appLanguage: AppLanguage
get() = settingsDiskSource.appLanguage ?: AppLanguage.DEFAULT
set(value) {
@@ -231,7 +220,7 @@ class SettingsRepositoryImpl(
)
}
override val isAutofillEnabledStateFlow: StateFlow<Boolean> =
mutableIsAutofillEnabledStateFlow.asStateFlow()
autofillEnabledManager.isAutofillEnabledStateFlow
override var isScreenCaptureAllowed: Boolean
get() = activeUserId?.let {
@@ -266,16 +255,12 @@ class SettingsRepositoryImpl(
?: DEFAULT_IS_SCREEN_CAPTURE_ALLOWED,
)
init {
observeAutofillEnabledChanges()
}
override fun disableAutofill() {
autofillManager.disableAutofillServices()
// Manually indicate that autofill is no longer supported without needing a foreground state
// change.
mutableIsAutofillEnabledStateFlow.value = false
autofillEnabledManager.isAutofillEnabled = false
}
@Suppress("ReturnCount")
@@ -435,21 +420,6 @@ class SettingsRepositoryImpl(
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun observeAutofillEnabledChanges() {
mutableIsAutofillEnabledStateFlow
// Only observe when subscribed to.
.subscriptionCount.map { it > 0 }
.filter { hasSubscribers -> hasSubscribers }
.flatMapLatest {
appForegroundManager.appForegroundStateFlow
}
.onEach {
mutableIsAutofillEnabledStateFlow.value = isAutofillEnabledAndSupported
}
.launchIn(unconfinedScope)
}
}
/**

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.repository.di
import android.view.autofill.AutofillManager
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
@@ -42,6 +43,7 @@ object PlatformRepositoryModule {
@Singleton
fun provideSettingsRepository(
autofillManager: AutofillManager,
autofillEnabledManager: AutofillEnabledManager,
appForegroundManager: AppForegroundManager,
authDiskSource: AuthDiskSource,
settingsDiskSource: SettingsDiskSource,
@@ -51,7 +53,7 @@ object PlatformRepositoryModule {
): SettingsRepository =
SettingsRepositoryImpl(
autofillManager = autofillManager,
appForegroundManager = appForegroundManager,
autofillEnabledManager = autofillEnabledManager,
authDiskSource = authDiskSource,
settingsDiskSource = settingsDiskSource,
vaultSdkSource = vaultSdkSource,