Merge pull request #2429 from element-hq/feature/bma/testRecoveryKey

Test recovery key
This commit is contained in:
Benoit Marty 2024-02-22 17:37:11 +01:00 committed by GitHub
commit 4477328cf2
34 changed files with 143 additions and 88 deletions

View file

@ -55,9 +55,10 @@ jobs:
# app-file should point to an x86 compatible APK file, so upload the x86_64 one (much smaller than the universal APK). # app-file should point to an x86 compatible APK file, so upload the x86_64 one (much smaller than the universal APK).
app-file: app/build/outputs/apk/gplay/debug/app-gplay-x86_64-debug.apk app-file: app/build/outputs/apk/gplay/debug/app-gplay-x86_64-debug.apk
env: | env: |
USERNAME=maestroelement MAESTRO_USERNAME=maestroelement
PASSWORD=${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }} MAESTRO_PASSWORD=${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }}
ROOM_NAME=MyRoom MAESTRO_RECOVERY_KEY=${{ secrets.MATRIX_MAESTRO_ACCOUNT_RECOVERY_KEY }}
INVITEE1_MXID=@maestroelement2:matrix.org MAESTRO_ROOM_NAME=MyRoom
INVITEE2_MXID=@maestroelement3:matrix.org MAESTRO_INVITEE1_MXID=@maestroelement2:matrix.org
APP_ID=io.element.android.x.debug MAESTRO_INVITEE2_MXID=@maestroelement3:matrix.org
MAESTRO_APP_ID=io.element.android.x.debug

View file

@ -22,12 +22,13 @@ From root dir of the project
```shell ```shell
maestro test \ maestro test \
-e APP_ID=io.element.android.x.debug \ -e MAESTRO_APP_ID=io.element.android.x.debug \
-e USERNAME=user1 \ -e MAESTRO_USERNAME=user1 \
-e PASSWORD=123 \ -e MAESTRO_PASSWORD=123 \
-e ROOM_NAME="MyRoom" \ -e MAESTRO_RECOVERY_KEY=ABC \
-e INVITEE1_MXID=user2 \ -e MAESTRO_ROOM_NAME="MyRoom" \
-e INVITEE2_MXID=user3 \ -e MAESTRO_INVITEE1_MXID=user2 \
-e MAESTRO_INVITEE2_MXID=user3 \
.maestro/allTests.yaml .maestro/allTests.yaml
``` ```

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
## Check that all env variables required in the whole test suite are declared (to fail faster) ## Check that all env variables required in the whole test suite are declared (to fail faster)
- runScript: ./scripts/checkEnv.js - runScript: ./scripts/checkEnv.js

View file

@ -1,9 +1,10 @@
// This array contains all the required environment variable. When adding a variable, add it here also. // This array contains all the required environment variable. When adding a variable, add it here also.
// If a variable is missing, an error will occur. // If a variable is missing, an error will occur.
if (APP_ID == null) throw "Fatal: missing env variable APP_ID" if (MAESTRO_APP_ID == null) throw "Fatal: missing env variable MAESTRO_APP_ID"
if (USERNAME == null) throw "Fatal: missing env variable USERNAME" if (MAESTRO_USERNAME == null) throw "Fatal: missing env variable MAESTRO_USERNAME"
if (PASSWORD == null) throw "Fatal: missing env variable PASSWORD" if (MAESTRO_PASSWORD == null) throw "Fatal: missing env variable MAESTRO_PASSWORD"
if (ROOM_NAME == null) throw "Fatal: missing env variable ROOM_NAME" if (MAESTRO_RECOVERY_KEY == null) throw "Fatal: missing env variable MAESTRO_RECOVERY_KEY"
if (INVITEE1_MXID == null) throw "Fatal: missing env variable INVITEE1_MXID" if (MAESTRO_ROOM_NAME == null) throw "Fatal: missing env variable MAESTRO_ROOM_NAME"
if (INVITEE2_MXID == null) throw "Fatal: missing env variable INVITEE2_MXID" if (MAESTRO_INVITEE1_MXID == null) throw "Fatal: missing env variable MAESTRO_INVITEE1_MXID"
if (MAESTRO_INVITEE2_MXID == null) throw "Fatal: missing env variable MAESTRO_INVITEE2_MXID"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- tapOn: - tapOn:
id: "login-change_server" id: "login-change_server"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- tapOn: "Continue" - tapOn: "Continue"
- runFlow: ../assertions/assertLoginDisplayed.yaml - runFlow: ../assertions/assertLoginDisplayed.yaml
@ -9,7 +9,7 @@ appId: ${APP_ID}
id: "login-continue" id: "login-continue"
- tapOn: - tapOn:
id: "login-email_username" id: "login-email_username"
- inputText: ${USERNAME} - inputText: ${MAESTRO_USERNAME}
- pressKey: Enter - pressKey: Enter
- tapOn: - tapOn:
id: "login-password" id: "login-password"
@ -20,7 +20,7 @@ appId: ${APP_ID}
- tapOn: - tapOn:
id: "login-password" id: "login-password"
- eraseText: 20 - eraseText: 20
- inputText: ${PASSWORD} - inputText: ${MAESTRO_PASSWORD}
- pressKey: Enter - pressKey: Enter
- tapOn: "Continue" - tapOn: "Continue"
- runFlow: ../assertions/assertWelcomeScreenDisplayed.yaml - runFlow: ../assertions/assertWelcomeScreenDisplayed.yaml
@ -28,3 +28,4 @@ appId: ${APP_ID}
- runFlow: ../assertions/assertAnalyticsDisplayed.yaml - runFlow: ../assertions/assertAnalyticsDisplayed.yaml
- tapOn: "Not now" - tapOn: "Not now"
- runFlow: ../assertions/assertHomeDisplayed.yaml - runFlow: ../assertions/assertHomeDisplayed.yaml
- runFlow: ./verifySession.yaml

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- tapOn: - tapOn:
id: "home_screen-settings" id: "home_screen-settings"

View file

@ -0,0 +1,11 @@
appId: ${MAESTRO_APP_ID}
---
- tapOn: "Continue"
- takeScreenshot: build/maestro/150-Verify
- tapOn: "Enter recovery key"
- tapOn:
id: "verification-recovery_key"
- inputText: ${MAESTRO_RECOVERY_KEY}
- hideKeyboard
- tapOn: "Confirm"
- runFlow: ../assertions/assertHomeDisplayed.yaml

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- extendedWaitUntil: - extendedWaitUntil:
visible: "Help improve Element X dbg" visible: "Help improve Element X dbg"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- extendedWaitUntil: - extendedWaitUntil:
visible: "All Chats" visible: "All Chats"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- extendedWaitUntil: - extendedWaitUntil:
visible: "Be in your element" visible: "Be in your element"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- extendedWaitUntil: - extendedWaitUntil:
visible: "Change account provider" visible: "Change account provider"

View file

@ -1,5 +1,5 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- extendedWaitUntil: - extendedWaitUntil:
visible: ${ROOM_NAME} visible: ${MAESTRO_ROOM_NAME}
timeout: 10000 timeout: 10000

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- extendedWaitUntil: - extendedWaitUntil:
visible: visible:

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- clearState - clearState
- launchApp: - launchApp:

View file

@ -1,13 +1,14 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
# Purpose: Test the creation and deletion of a DM room. # Purpose: Test the creation and deletion of a DM room.
- tapOn: "Create a new conversation or room" - tapOn: "Create a new conversation or room"
- tapOn: "Search for someone" - tapOn: "Search for someone"
- inputText: ${INVITEE1_MXID} - inputText: ${MAESTRO_INVITEE1_MXID}
- tapOn: - tapOn:
text: ${INVITEE1_MXID} text: ${MAESTRO_INVITEE1_MXID}
index: 1 index: 1
- takeScreenshot: build/maestro/330-createAndDeleteDM - takeScreenshot: build/maestro/330-createAndDeleteDM
- tapOn: "maestroelement2" - tapOn: "maestroelement2"
- scroll
- tapOn: "Leave conversation" - tapOn: "Leave conversation"
- tapOn: "Leave" - tapOn: "Leave"

View file

@ -1,12 +1,12 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
# Purpose: Test the creation and deletion of a room # Purpose: Test the creation and deletion of a room
- tapOn: "Create a new conversation or room" - tapOn: "Create a new conversation or room"
- tapOn: "New room" - tapOn: "New room"
- tapOn: "Search for someone" - tapOn: "Search for someone"
- inputText: ${INVITEE1_MXID} - inputText: ${MAESTRO_INVITEE1_MXID}
- tapOn: - tapOn:
text: ${INVITEE1_MXID} text: ${MAESTRO_INVITEE1_MXID}
index: 1 index: 1
- tapOn: "Next" - tapOn: "Next"
- tapOn: "e.g. your project name" - tapOn: "e.g. your project name"
@ -19,9 +19,9 @@ appId: ${APP_ID}
- tapOn: "Invite people" - tapOn: "Invite people"
# assert there's 1 member and 1 invitee # assert there's 1 member and 1 invitee
- tapOn: "Search for someone" - tapOn: "Search for someone"
- inputText: ${INVITEE2_MXID} - inputText: ${MAESTRO_INVITEE2_MXID}
- tapOn: - tapOn:
text: ${INVITEE2_MXID} text: ${MAESTRO_INVITEE2_MXID}
index: 1 index: 1
- tapOn: "Invite" - tapOn: "Invite"
- tapOn: "Back" - tapOn: "Back"

View file

@ -1,13 +1,13 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
# Purpose: Test the context menu of a room in the room list # Purpose: Test the context menu of a room in the room list
- longPressOn: ${ROOM_NAME} - longPressOn: ${MAESTRO_ROOM_NAME}
- takeScreenshot: build/maestro/310-RoomList-ContextMenu - takeScreenshot: build/maestro/310-RoomList-ContextMenu
- tapOn: - tapOn:
text: "Settings" text: "Settings"
index: 0 index: 0
- tapOn: "Back" - tapOn: "Back"
- longPressOn: ${ROOM_NAME} - longPressOn: ${MAESTRO_ROOM_NAME}
- tapOn: - tapOn:
text: "Leave room" text: "Leave room"
index: 0 index: 0

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- runFlow: searchRoomList.yaml - runFlow: searchRoomList.yaml
- takeScreenshot: build/maestro/300-RoomList - takeScreenshot: build/maestro/300-RoomList

View file

@ -1,10 +1,10 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- runFlow: ../assertions/assertRoomListSynced.yaml - runFlow: ../assertions/assertRoomListSynced.yaml
- tapOn: "search" - tapOn: "search"
- inputText: ${ROOM_NAME.substring(0, 3)} - inputText: ${MAESTRO_ROOM_NAME.substring(0, 3)}
- takeScreenshot: build/maestro/400-SearchRoom - takeScreenshot: build/maestro/400-SearchRoom
- tapOn: ${ROOM_NAME} - tapOn: ${MAESTRO_ROOM_NAME}
# Back from timeline # Back from timeline
- back - back
- assertVisible: "MyR" - assertVisible: "MyR"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- takeScreenshot: build/maestro/520-Timeline - takeScreenshot: build/maestro/520-Timeline
- tapOn: "Add attachment" - tapOn: "Add attachment"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- takeScreenshot: build/maestro/530-Timeline - takeScreenshot: build/maestro/530-Timeline
- tapOn: "Add attachment" - tapOn: "Add attachment"

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- takeScreenshot: build/maestro/510-Timeline - takeScreenshot: build/maestro/510-Timeline
- tapOn: - tapOn:

View file

@ -1,7 +1,7 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
# This is the name of one room # This is the name of one room
- tapOn: ${ROOM_NAME} - tapOn: ${MAESTRO_ROOM_NAME}
- takeScreenshot: build/maestro/500-Timeline - takeScreenshot: build/maestro/500-Timeline
- runFlow: messages/text.yaml - runFlow: messages/text.yaml
- runFlow: messages/location.yaml - runFlow: messages/location.yaml

View file

@ -1,4 +1,4 @@
appId: ${APP_ID} appId: ${MAESTRO_APP_ID}
--- ---
- tapOn: - tapOn:
id: "home_screen-settings" id: "home_screen-settings"

View file

@ -338,6 +338,10 @@ class LoggedInFlowNode @AssistedInject constructor(
) )
) )
} }
override fun onDone() {
backstack.pop()
}
} }
verifySessionEntryPoint verifySessionEntryPoint
.nodeBuilder(this, buildContext) .nodeBuilder(this, buildContext)

View file

@ -42,6 +42,7 @@ dependencies {
implementation(projects.libraries.matrixui) implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.testtags)
api(libs.statemachine) api(libs.statemachine)
api(projects.features.securebackup.api) api(projects.features.securebackup.api)

View file

@ -53,6 +53,8 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.autofill import io.element.android.libraries.designsystem.theme.components.autofill
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
@ -169,6 +171,7 @@ private fun RecoveryKeyFormContent(
OutlinedTextField( OutlinedTextField(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.testTag(TestTags.recoveryKey)
.autofill( .autofill(
autofillTypes = listOf(AutofillType.Password), autofillTypes = listOf(AutofillType.Password),
onFill = { onChange(it) }, onFill = { onChange(it) },

View file

@ -31,5 +31,6 @@ interface VerifySessionEntryPoint : FeatureEntryPoint {
interface Callback : Plugin { interface Callback : Plugin {
fun onEnterRecoveryKey() fun onEnterRecoveryKey()
fun onDone()
} }
} }

View file

@ -40,14 +40,20 @@ class VerifySelfSessionNode @AssistedInject constructor(
} }
} }
private fun onDone() {
plugins<VerifySessionEntryPoint.Callback>().forEach {
it.onDone()
}
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
VerifySelfSessionView( VerifySelfSessionView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onEnterRecoveryKey = { onEnterRecoveryKey() }, onEnterRecoveryKey = ::onEnterRecoveryKey,
goBack = { navigateUp() } goBack = ::onDone,
) )
} }
} }

View file

@ -138,7 +138,7 @@ class RustMatrixClient(
syncService = rustSyncService, syncService = rustSyncService,
sessionCoroutineScope = sessionCoroutineScope, sessionCoroutineScope = sessionCoroutineScope,
dispatchers = dispatchers, dispatchers = dispatchers,
).apply { start() } )
private val sessionDirectoryNameProvider = SessionDirectoryNameProvider() private val sessionDirectoryNameProvider = SessionDirectoryNameProvider()
private val isLoggingOut = AtomicBoolean(false) private val isLoggingOut = AtomicBoolean(false)

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.flow.Flow
import org.matrix.rustcomponents.sdk.BackupStateListener
import org.matrix.rustcomponents.sdk.EncryptionInterface
import org.matrix.rustcomponents.sdk.RecoveryStateListener
import org.matrix.rustcomponents.sdk.BackupState as RustBackupState
import org.matrix.rustcomponents.sdk.RecoveryState as RustRecoveryState
internal fun EncryptionInterface.backupStateFlow(): Flow<BackupState> = mxCallbackFlow {
val backupStateMapper = BackupStateMapper()
trySend(backupStateMapper.map(backupState()))
val listener = object : BackupStateListener {
override fun onUpdate(status: RustBackupState) {
trySend(backupStateMapper.map(status))
}
}
backupStateListener(listener)
}
internal fun EncryptionInterface.recoveryStateFlow(): Flow<RecoveryState> = mxCallbackFlow {
val recoveryStateMapper = RecoveryStateMapper()
trySend(recoveryStateMapper.map(recoveryState()))
val listener = object : RecoveryStateListener {
override fun onUpdate(status: RustRecoveryState) {
trySend(recoveryStateMapper.map(status))
}
}
recoveryStateListener(listener)
}

View file

@ -25,7 +25,6 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.sync.RustSyncService
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
@ -40,17 +39,12 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.BackupStateListener
import org.matrix.rustcomponents.sdk.BackupSteadyStateListener import org.matrix.rustcomponents.sdk.BackupSteadyStateListener
import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener
import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.Encryption
import org.matrix.rustcomponents.sdk.RecoveryStateListener
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.BackupState as RustBackupState
import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState
import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress
import org.matrix.rustcomponents.sdk.RecoveryState as RustRecoveryState
import org.matrix.rustcomponents.sdk.SteadyStateException as RustSteadyStateException import org.matrix.rustcomponents.sdk.SteadyStateException as RustSteadyStateException
internal class RustEncryptionService( internal class RustEncryptionService(
@ -61,18 +55,12 @@ internal class RustEncryptionService(
) : EncryptionService { ) : EncryptionService {
private val service: Encryption = client.encryption() private val service: Encryption = client.encryption()
private val backupStateMapper = BackupStateMapper()
private val recoveryStateMapper = RecoveryStateMapper()
private val enableRecoveryProgressMapper = EnableRecoveryProgressMapper() private val enableRecoveryProgressMapper = EnableRecoveryProgressMapper()
private val backupUploadStateMapper = BackupUploadStateMapper() private val backupUploadStateMapper = BackupUploadStateMapper()
private val steadyStateExceptionMapper = SteadyStateExceptionMapper() private val steadyStateExceptionMapper = SteadyStateExceptionMapper()
private var backupStateListenerTaskHandle: TaskHandle? = null
private var recoveryStateListenerTaskHandle: TaskHandle? = null
private val backupStateFlow = MutableStateFlow(service.backupState().let(backupStateMapper::map))
override val backupStateStateFlow = combine( override val backupStateStateFlow = combine(
backupStateFlow, service.backupStateFlow(),
syncService.syncState, syncService.syncState,
) { backupState, syncState -> ) { backupState, syncState ->
if (syncState == SyncState.Running) { if (syncState == SyncState.Running) {
@ -82,10 +70,8 @@ internal class RustEncryptionService(
} }
}.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, BackupState.WAITING_FOR_SYNC) }.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, BackupState.WAITING_FOR_SYNC)
private val recoveryStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(service.recoveryState().let(recoveryStateMapper::map))
override val recoveryStateStateFlow = combine( override val recoveryStateStateFlow = combine(
recoveryStateFlow, service.recoveryStateFlow(),
syncService.syncState, syncService.syncState,
) { recoveryState, syncState -> ) { recoveryState, syncState ->
if (syncState == SyncState.Running) { if (syncState == SyncState.Running) {
@ -111,23 +97,7 @@ internal class RustEncryptionService(
} }
.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false)
fun start() {
backupStateListenerTaskHandle = service.backupStateListener(object : BackupStateListener {
override fun onUpdate(status: RustBackupState) {
backupStateFlow.value = backupStateMapper.map(status)
}
})
recoveryStateListenerTaskHandle = service.recoveryStateListener(object : RecoveryStateListener {
override fun onUpdate(status: RustRecoveryState) {
recoveryStateFlow.value = recoveryStateMapper.map(status)
}
})
}
fun destroy() { fun destroy() {
backupStateListenerTaskHandle?.cancelAndDestroy()
recoveryStateListenerTaskHandle?.cancelAndDestroy()
service.destroy() service.destroy()
} }

View file

@ -33,6 +33,11 @@ object TestTags {
val loginPassword = TestTag("login-password") val loginPassword = TestTag("login-password")
val loginContinue = TestTag("login-continue") val loginContinue = TestTag("login-continue")
/**
* Verification screen.
*/
val recoveryKey = TestTag("verification-recovery_key")
/** /**
* Sign out screen. * Sign out screen.
*/ */