Browse Source

LockScreen : refact some code and add secureFlag

pull/1757/head
ganfra 11 months ago
parent
commit
5a417ba498
  1. 2
      app/src/main/kotlin/io/element/android/x/MainActivity.kt
  2. 2
      app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
  3. 2
      features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt
  4. 27
      features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt
  5. 37
      features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt
  6. 23
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt
  7. 3
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt
  8. 4
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt
  9. 7
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt
  10. 20
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt
  11. 5
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt
  12. 4
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt
  13. 8
      features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt
  14. 9
      features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt
  15. 16
      features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt

2
app/src/main/kotlin/io/element/android/x/MainActivity.kt

@ -32,6 +32,7 @@ import androidx.core.view.WindowCompat
import com.bumble.appyx.core.integration.NodeHost import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeActivity import com.bumble.appyx.core.integrationpoint.NodeActivity
import com.bumble.appyx.core.plugin.NodeReadyObserver import com.bumble.appyx.core.plugin.NodeReadyObserver
import io.element.android.features.lockscreen.api.handleSecureFlag
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
@ -53,6 +54,7 @@ class MainActivity : NodeActivity() {
installSplashScreen() installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
appBindings = bindings() appBindings = bindings()
appBindings.lockScreenService().handleSecureFlag(this)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
MainContent(appBindings) MainContent(appBindings)

2
app/src/main/kotlin/io/element/android/x/di/AppBindings.kt

@ -17,6 +17,7 @@
package io.element.android.x.di package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.annotations.ContributesTo
import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
@ -27,4 +28,5 @@ interface AppBindings {
fun snackbarDispatcher(): SnackbarDispatcher fun snackbarDispatcher(): SnackbarDispatcher
fun tracingService(): TracingService fun tracingService(): TracingService
fun bugReporter(): BugReporter fun bugReporter(): BugReporter
fun lockScreenService(): LockScreenService
} }

2
features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt

@ -120,7 +120,7 @@ class DefaultFtueState @Inject constructor(
private fun shouldDisplayLockscreenSetup(): Boolean { private fun shouldDisplayLockscreenSetup(): Boolean {
return runBlocking { return runBlocking {
lockScreenService.isSetupRequired() lockScreenService.isSetupRequired().first()
} }
} }

27
features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt

@ -57,6 +57,7 @@ class DefaultFtueStateTests {
val analyticsService = FakeAnalyticsService() val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore() val migrationScreenStore = InMemoryMigrationScreenStore()
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true) val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
val lockScreenService = FakeLockScreenService()
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
val state = createState( val state = createState(
@ -64,13 +65,15 @@ class DefaultFtueStateTests {
welcomeState = welcomeState, welcomeState = welcomeState,
analyticsService = analyticsService, analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore, migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
) )
welcomeState.setWelcomeScreenShown() welcomeState.setWelcomeScreenShown()
analyticsService.setDidAskUserConsent() analyticsService.setDidAskUserConsent()
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
permissionStateProvider.setPermissionGranted() permissionStateProvider.setPermissionGranted()
lockScreenService.setIsPinSetup(true)
state.updateState() state.updateState()
assertThat(state.shouldDisplayFlow.value).isFalse() assertThat(state.shouldDisplayFlow.value).isFalse()
@ -85,6 +88,7 @@ class DefaultFtueStateTests {
val analyticsService = FakeAnalyticsService() val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore() val migrationScreenStore = InMemoryMigrationScreenStore()
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
val lockScreenService = FakeLockScreenService()
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
val state = createState( val state = createState(
@ -92,7 +96,8 @@ class DefaultFtueStateTests {
welcomeState = welcomeState, welcomeState = welcomeState,
analyticsService = analyticsService, analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore, migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
) )
val steps = mutableListOf<FtueStep?>() val steps = mutableListOf<FtueStep?>()
@ -108,7 +113,11 @@ class DefaultFtueStateTests {
steps.add(state.getNextStep(steps.lastOrNull())) steps.add(state.getNextStep(steps.lastOrNull()))
permissionStateProvider.setPermissionGranted() permissionStateProvider.setPermissionGranted()
// Fourth step, analytics opt in // Fourth step, notifications opt in
steps.add(state.getNextStep(steps.lastOrNull()))
lockScreenService.setIsPinSetup(true)
// Fifth step, analytics opt in
steps.add(state.getNextStep(steps.lastOrNull())) steps.add(state.getNextStep(steps.lastOrNull()))
analyticsService.setDidAskUserConsent() analyticsService.setDidAskUserConsent()
@ -119,6 +128,7 @@ class DefaultFtueStateTests {
FtueStep.MigrationScreen, FtueStep.MigrationScreen,
FtueStep.WelcomeScreen, FtueStep.WelcomeScreen,
FtueStep.NotificationsOptIn, FtueStep.NotificationsOptIn,
FtueStep.LockscreenSetup,
FtueStep.AnalyticsOptIn, FtueStep.AnalyticsOptIn,
null, // Final state null, // Final state
) )
@ -133,18 +143,20 @@ class DefaultFtueStateTests {
val analyticsService = FakeAnalyticsService() val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore() val migrationScreenStore = InMemoryMigrationScreenStore()
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
val lockScreenService = FakeLockScreenService()
val state = createState( val state = createState(
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
analyticsService = analyticsService, analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore, migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider, permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
) )
// Skip first 3 steps // Skip first 4 steps
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
state.setWelcomeScreenShown() state.setWelcomeScreenShown()
permissionStateProvider.setPermissionGranted() permissionStateProvider.setPermissionGranted()
lockScreenService.setIsPinSetup(true)
assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn) assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
@ -160,18 +172,21 @@ class DefaultFtueStateTests {
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
val analyticsService = FakeAnalyticsService() val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore() val migrationScreenStore = InMemoryMigrationScreenStore()
val lockScreenService = FakeLockScreenService()
val state = createState( val state = createState(
sdkIntVersion = Build.VERSION_CODES.M, sdkIntVersion = Build.VERSION_CODES.M,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
analyticsService = analyticsService, analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore, migrationScreenStore = migrationScreenStore,
lockScreenService = lockScreenService,
) )
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID) migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
assertThat(state.getNextStep()).isEqualTo(FtueStep.WelcomeScreen) assertThat(state.getNextStep()).isEqualTo(FtueStep.WelcomeScreen)
state.setWelcomeScreenShown() state.setWelcomeScreenShown()
lockScreenService.setIsPinSetup(true)
assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn) assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
analyticsService.setDidAskUserConsent() analyticsService.setDidAskUserConsent()

37
features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt

@ -16,7 +16,14 @@
package io.element.android.features.lockscreen.api package io.element.android.features.lockscreen.api
import android.os.Build
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
interface LockScreenService { interface LockScreenService {
/** /**
@ -28,5 +35,33 @@ interface LockScreenService {
* Check if setting up the lock screen is required. * Check if setting up the lock screen is required.
* @return true if the lock screen is mandatory and not setup yet, false otherwise. * @return true if the lock screen is mandatory and not setup yet, false otherwise.
*/ */
suspend fun isSetupRequired(): Boolean fun isSetupRequired(): Flow<Boolean>
/**
* Check if pin is setup.
* @return true if the pin is setup, false otherwise.
*/
fun isPinSetup(): Flow<Boolean>
}
/**
* Makes sure the secure flag is set on the activity if the pin is setup.
* @param activity the activity to set the flag on.
*/
fun LockScreenService.handleSecureFlag(activity: ComponentActivity) {
isPinSetup()
.onEach { isPinSetup ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activity.setRecentsScreenshotEnabled(!isPinSetup)
} else {
if (isPinSetup) {
activity.window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}.launchIn(activity.lifecycleScope)
} }

23
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt

@ -35,8 +35,12 @@ import io.element.android.services.appnavstate.api.AppForegroundStateService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration import kotlin.time.Duration
@ -113,14 +117,23 @@ class DefaultLockScreenService @Inject constructor(
} }
} }
override suspend fun isSetupRequired(): Boolean { override fun isPinSetup(): Flow<Boolean> {
return lockScreenConfig.isPinMandatory return combine(
&& featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock) featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinUnlock),
&& !pinCodeManager.isPinCodeAvailable() pinCodeManager.hasPinCode()
) { isEnabled, hasPinCode ->
isEnabled && hasPinCode
}
}
override fun isSetupRequired(): Flow<Boolean> {
return isPinSetup().map { isPinSetup ->
!isPinSetup && lockScreenConfig.isPinMandatory
}
} }
private fun CoroutineScope.lockIfNeeded(gracePeriod: Duration = Duration.ZERO) = launch { private fun CoroutineScope.lockIfNeeded(gracePeriod: Duration = Duration.ZERO) = launch {
if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock) && pinCodeManager.isPinCodeAvailable()) { if (isPinSetup().first()) {
delay(gracePeriod) delay(gracePeriod)
_lockScreenState.value = LockScreenLockState.Locked _lockScreenState.value = LockScreenLockState.Locked
} }

3
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt

@ -23,6 +23,7 @@ import io.element.android.libraries.cryptography.api.EncryptionResult
import io.element.android.libraries.cryptography.api.SecretKeyRepository import io.element.android.libraries.cryptography.api.SecretKeyRepository
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import kotlinx.coroutines.flow.Flow
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject import javax.inject.Inject
@ -46,7 +47,7 @@ class DefaultPinCodeManager @Inject constructor(
callbacks.remove(callback) callbacks.remove(callback)
} }
override suspend fun isPinCodeAvailable(): Boolean { override fun hasPinCode(): Flow<Boolean> {
return lockScreenStore.hasPinCode() return lockScreenStore.hasPinCode()
} }

4
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt

@ -16,6 +16,8 @@
package io.element.android.features.lockscreen.impl.pin package io.element.android.features.lockscreen.impl.pin
import kotlinx.coroutines.flow.Flow
/** /**
* This interface is the main interface to manage the pin code. * This interface is the main interface to manage the pin code.
* Implementation should take care of encrypting the pin code and storing it. * Implementation should take care of encrypting the pin code and storing it.
@ -55,7 +57,7 @@ interface PinCodeManager {
/** /**
* @return true if a pin code is available. * @return true if a pin code is available.
*/ */
suspend fun isPinCodeAvailable(): Boolean fun hasPinCode(): Flow<Boolean>
/** /**
* @return the size of the saved pin code. * @return the size of the saved pin code.

7
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt

@ -42,6 +42,7 @@ import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -90,9 +91,11 @@ class LockScreenSettingsFlowNode @AssistedInject constructor(
} }
} }
init { override fun onBuilt() {
super.onBuilt()
lifecycleScope.launch { lifecycleScope.launch {
if (pinCodeManager.isPinCodeAvailable()) { val hasPinCode = pinCodeManager.hasPinCode().first()
if (hasPinCode) {
backstack.newRoot(NavTarget.Unlock) backstack.newRoot(NavTarget.Unlock)
} else { } else {
backstack.newRoot(NavTarget.Setup) backstack.newRoot(NavTarget.Setup)

20
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt

@ -17,11 +17,10 @@
package io.element.android.features.lockscreen.impl.settings package io.element.android.features.lockscreen.impl.settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.appconfig.LockScreenConfig import io.element.android.appconfig.LockScreenConfig
@ -43,23 +42,15 @@ class LockScreenSettingsPresenter @Inject constructor(
@Composable @Composable
override fun present(): LockScreenSettingsState { override fun present(): LockScreenSettingsState {
var triggerComputation by remember { val showRemovePinOption by produceState(initialValue = false) {
mutableIntStateOf(0) pinCodeManager.hasPinCode().collect { hasPinCode ->
value = !lockScreenConfig.isPinMandatory && hasPinCode
} }
var showRemovePinOption by remember {
mutableStateOf(false)
}
var showToggleBiometric by remember {
mutableStateOf(false)
} }
val isBiometricEnabled by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false) val isBiometricEnabled by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false)
var showRemovePinConfirmation by remember { var showRemovePinConfirmation by remember {
mutableStateOf(false) mutableStateOf(false)
} }
LaunchedEffect(triggerComputation) {
showRemovePinOption = !lockScreenConfig.isPinMandatory && pinCodeManager.isPinCodeAvailable()
showToggleBiometric = biometricUnlockManager.isDeviceSecured
}
fun handleEvents(event: LockScreenSettingsEvents) { fun handleEvents(event: LockScreenSettingsEvents) {
when (event) { when (event) {
@ -69,7 +60,6 @@ class LockScreenSettingsPresenter @Inject constructor(
if (showRemovePinConfirmation) { if (showRemovePinConfirmation) {
showRemovePinConfirmation = false showRemovePinConfirmation = false
pinCodeManager.deletePinCode() pinCodeManager.deletePinCode()
triggerComputation++
} }
} }
} }
@ -86,7 +76,7 @@ class LockScreenSettingsPresenter @Inject constructor(
showRemovePinOption = showRemovePinOption, showRemovePinOption = showRemovePinOption,
isBiometricEnabled = isBiometricEnabled, isBiometricEnabled = isBiometricEnabled,
showRemovePinConfirmation = showRemovePinConfirmation, showRemovePinConfirmation = showRemovePinConfirmation,
showToggleBiometric = showToggleBiometric, showToggleBiometric = biometricUnlockManager.isDeviceSecured,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

5
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt

@ -16,6 +16,8 @@
package io.element.android.features.lockscreen.impl.storage package io.element.android.features.lockscreen.impl.storage
import kotlinx.coroutines.flow.Flow
/** /**
* Should be implemented by any class that provides access to the encrypted PIN code. * Should be implemented by any class that provides access to the encrypted PIN code.
* All methods are suspending in case there are async IO operations involved. * All methods are suspending in case there are async IO operations involved.
@ -39,5 +41,6 @@ interface EncryptedPinCodeStorage {
/** /**
* Returns whether the PIN code is stored or not. * Returns whether the PIN code is stored or not.
*/ */
suspend fun hasPinCode(): Boolean fun hasPinCode(): Flow<Boolean>
} }

4
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt

@ -85,10 +85,10 @@ class PreferencesLockScreenStore @Inject constructor(
} }
} }
override suspend fun hasPinCode(): Boolean { override fun hasPinCode(): Flow<Boolean> {
return context.dataStore.data.map { preferences -> return context.dataStore.data.map { preferences ->
preferences[pinCodeKey] != null preferences[pinCodeKey] != null
}.first() }
} }
override fun isBiometricUnlockAllowed(): Flow<Boolean> { override fun isBiometricUnlockAllowed(): Flow<Boolean> {

8
features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt

@ -16,6 +16,7 @@
package io.element.android.features.lockscreen.impl.pin package io.element.android.features.lockscreen.impl.pin
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.lockscreen.impl.pin.storage.InMemoryLockScreenStore import io.element.android.features.lockscreen.impl.pin.storage.InMemoryLockScreenStore
import io.element.android.libraries.cryptography.impl.AESEncryptionDecryptionService import io.element.android.libraries.cryptography.impl.AESEncryptionDecryptionService
@ -32,10 +33,13 @@ class DefaultPinCodeManagerTest {
@Test @Test
fun `given a pin code when create and delete assert no pin code left`() = runTest { fun `given a pin code when create and delete assert no pin code left`() = runTest {
pinCodeManager.hasPinCode().test {
assertThat(awaitItem()).isFalse()
pinCodeManager.createPinCode("1234") pinCodeManager.createPinCode("1234")
assertThat(pinCodeManager.isPinCodeAvailable()).isTrue() assertThat(awaitItem()).isTrue()
pinCodeManager.deletePinCode() pinCodeManager.deletePinCode()
assertThat(pinCodeManager.isPinCodeAvailable()).isFalse() assertThat(awaitItem()).isFalse()
}
} }
@Test @Test

9
features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt

@ -24,7 +24,12 @@ private const val DEFAULT_REMAINING_ATTEMPTS = 3
class InMemoryLockScreenStore : LockScreenStore { class InMemoryLockScreenStore : LockScreenStore {
private val hasPinCode = MutableStateFlow(false)
private var pinCode: String? = null private var pinCode: String? = null
set(value) {
field = value
hasPinCode.value = value != null
}
private var remainingAttempts: Int = DEFAULT_REMAINING_ATTEMPTS private var remainingAttempts: Int = DEFAULT_REMAINING_ATTEMPTS
private var isBiometricUnlockAllowed = MutableStateFlow(false) private var isBiometricUnlockAllowed = MutableStateFlow(false)
@ -52,8 +57,8 @@ class InMemoryLockScreenStore : LockScreenStore {
pinCode = null pinCode = null
} }
override suspend fun hasPinCode(): Boolean { override fun hasPinCode(): Flow<Boolean> {
return pinCode != null return hasPinCode
} }
override fun isBiometricUnlockAllowed(): Flow<Boolean> { override fun isBiometricUnlockAllowed(): Flow<Boolean> {

16
features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt

@ -18,21 +18,27 @@ package io.element.android.features.lockscreen.test
import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenLockState
import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.api.LockScreenService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
class FakeLockScreenService : LockScreenService { class FakeLockScreenService : LockScreenService {
private var isSetupRequired: Boolean = false private var isPinSetup = MutableStateFlow(false)
private val _lockState: MutableStateFlow<LockScreenLockState> = MutableStateFlow(LockScreenLockState.Locked) private val _lockState: MutableStateFlow<LockScreenLockState> = MutableStateFlow(LockScreenLockState.Locked)
override val lockState: StateFlow<LockScreenLockState> = _lockState override val lockState: StateFlow<LockScreenLockState> = _lockState
override suspend fun isSetupRequired(): Boolean { override fun isSetupRequired(): Flow<Boolean> {
return isSetupRequired return isPinSetup.map { !it }
} }
fun setIsSetupRequired(isSetupRequired: Boolean) { fun setIsPinSetup(isPinSetup: Boolean) {
this.isSetupRequired = isSetupRequired this.isPinSetup.value = isPinSetup
}
override fun isPinSetup(): Flow<Boolean> {
return isPinSetup
} }
fun setLockState(lockState: LockScreenLockState) { fun setLockState(lockState: LockScreenLockState) {

Loading…
Cancel
Save