diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt b/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt index 16703b7..49d1d86 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt @@ -2,15 +2,20 @@ package com.bitwarden.authenticator import android.content.Intent import android.os.Bundle +import android.util.Log +import android.view.WindowManager import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.getValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.lifecycleScope import com.bitwarden.authenticator.ui.platform.feature.rootnav.RootNavScreen import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -18,10 +23,11 @@ class MainActivity : AppCompatActivity() { private val mainViewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - var shouldShowSplashScreen = true installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen } + super.onCreate(savedInstanceState) + + observeViewModelEvents() if (savedInstanceState == null) { mainViewModel.trySendAction( @@ -51,4 +57,28 @@ class MainActivity : AppCompatActivity() { MainAction.ReceiveNewIntent(intent = intent) ) } + + private fun observeViewModelEvents() { + Log.d("TAG", "observeViewModelEvents() called") + mainViewModel + .eventFlow + .onEach { event -> + Log.d("TAG", "observeViewModelEvents: onEach $event") + when(event) { + is MainEvent.ScreenCaptureSettingChange -> { + handleScreenCaptureSettingChange(event) + } + } + } + .launchIn(lifecycleScope) + } + + private fun handleScreenCaptureSettingChange(event: MainEvent.ScreenCaptureSettingChange) { + Log.d("TAG", "handleScreenCaptureSettingChange() called with: event = $event") + if (event.isAllowed) { + window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt index 30eebdf..67b92cc 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt @@ -13,6 +13,9 @@ import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize import javax.inject.Inject +/** + * A view model that helps launch actions for the [MainActivity]. + */ @HiltViewModel class MainViewModel @Inject constructor( settingsRepository: SettingsRepository, @@ -27,6 +30,13 @@ class MainViewModel @Inject constructor( .appThemeStateFlow .onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) } .launchIn(viewModelScope) + + settingsRepository + .isScreenCaptureAllowedStateFlow + .onEach { isAllowed -> + sendEvent(MainEvent.ScreenCaptureSettingChange(isAllowed)) + } + .launchIn(viewModelScope) } override fun handleAction(action: MainAction) { diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSource.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSource.kt index 16f5904..667483c 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSource.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSource.kt @@ -81,4 +81,19 @@ interface SettingsDiskSource { systemBioIntegrityState: String, value: Boolean?, ) + + /** + * Gets whether or not the user has enabled screen capture. + */ + fun getScreenCaptureAllowed(): Boolean? + + /** + * Emits updates that track [getScreenCaptureAllowed]. + */ + fun getScreenCaptureAllowedFlow(): Flow + + /** + * Stores whether or not [isScreenCaptureAllowed]. + */ + fun storeScreenCaptureAllowed(isScreenCaptureAllowed: Boolean?) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index be97e2e..a003b96 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -6,7 +6,6 @@ import com.bitwarden.authenticator.data.platform.repository.util.bufferedMutable import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.onSubscription private const val APP_THEME_KEY = "$BASE_KEY:theme" @@ -16,6 +15,7 @@ private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "$BASE_KEY:accountBiom private const val ALERT_THRESHOLD_SECONDS_KEY = "$BASE_KEY:alertThresholdSeconds" private const val FIRST_LAUNCH_KEY = "$BASE_KEY:hasSeenWelcomeTutorial" private const val CRASH_LOGGING_ENABLED_KEY = "$BASE_KEY:crashLoggingEnabled" +private const val SCREEN_CAPTURE_ALLOW_KEY = "screenCaptureAllowed" /** * Primary implementation of [SettingsDiskSource]. @@ -27,8 +27,8 @@ class SettingsDiskSourceImpl( private val mutableAppThemeFlow = bufferedMutableSharedFlow(replay = 1) - private val mutableScreenCaptureAllowedFlowMap = - mutableMapOf>() + private val mutableScreenCaptureAllowedFlow = + bufferedMutableSharedFlow() private val mutableAlertThresholdSecondsFlow = bufferedMutableSharedFlow() @@ -127,4 +127,21 @@ class SettingsDiskSourceImpl( value = value, ) } + + override fun getScreenCaptureAllowed(): Boolean? { + return getBoolean(key = SCREEN_CAPTURE_ALLOW_KEY) + } + + override fun getScreenCaptureAllowedFlow(): Flow = mutableScreenCaptureAllowedFlow + .onSubscription { emit(getScreenCaptureAllowed()) } + + override fun storeScreenCaptureAllowed( + isScreenCaptureAllowed: Boolean?, + ) { + putBoolean( + key = SCREEN_CAPTURE_ALLOW_KEY, + value = isScreenCaptureAllowed, + ) + mutableScreenCaptureAllowedFlow.tryEmit(isScreenCaptureAllowed) + } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepository.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepository.kt index 142ee2a..0c48b6b 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepository.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepository.kt @@ -51,6 +51,16 @@ interface SettingsRepository { */ val hasSeenWelcomeTutorialFlow: StateFlow + /** + * Sets whether or not screen capture is allowed for the current user. + */ + var isScreenCaptureAllowed: Boolean + + /** + * Whether or not screen capture is allowed for the current user. + */ + val isScreenCaptureAllowedStateFlow: StateFlow + /** * Clears any previously stored encrypted user key used with biometrics for the current user. */ diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepositoryImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepositoryImpl.kt index ef700a1..866f50a 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/SettingsRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.bitwarden.authenticator.data.platform.repository +import com.bitwarden.authenticator.BuildConfig import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource import com.bitwarden.authenticator.data.authenticator.datasource.sdk.AuthenticatorSdkSource import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource @@ -15,6 +16,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +private val DEFAULT_IS_SCREEN_CAPTURE_ALLOWED = BuildConfig.DEBUG + /** * Primary implementation of [SettingsRepository]. */ @@ -75,6 +78,25 @@ class SettingsRepositoryImpl( initialValue = hasSeenWelcomeTutorial, ) + override var isScreenCaptureAllowed: Boolean + get() = settingsDiskSource.getScreenCaptureAllowed() + ?: DEFAULT_IS_SCREEN_CAPTURE_ALLOWED + set(value) { + settingsDiskSource.storeScreenCaptureAllowed( + isScreenCaptureAllowed = value, + ) + } + + override val isScreenCaptureAllowedStateFlow: StateFlow + get() = settingsDiskSource.getScreenCaptureAllowedFlow() + .map { isAllowed -> isAllowed ?: DEFAULT_IS_SCREEN_CAPTURE_ALLOWED } + .stateIn( + scope = unconfinedScope, + started = SharingStarted.Lazily, + initialValue = settingsDiskSource.getScreenCaptureAllowed() + ?: DEFAULT_IS_SCREEN_CAPTURE_ALLOWED, + ) + override suspend fun setupBiometricsKey(): BiometricsKeyResult { biometricsEncryptionManager.setupBiometrics() return authenticatorSdkSource