Merge pull request #7044 from vector-im/feature/mna/clean-vm-for-dm-v2
[Devices Management] Refactor some code to improve testability (PSG-701)
This commit is contained in:
commit
62dbab907d
|
@ -0,0 +1 @@
|
||||||
|
[Devices Management] Refactor some code to improve testability
|
|
@ -352,6 +352,11 @@ interface MavericksViewModelModule {
|
||||||
@MavericksViewModelKey(DevicesViewModel::class)
|
@MavericksViewModelKey(DevicesViewModel::class)
|
||||||
fun devicesViewModelFactory(factory: DevicesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun devicesViewModelFactory(factory: DevicesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(im.vector.app.features.settings.devices.v2.DevicesViewModel::class)
|
||||||
|
fun devicesViewModelV2Factory(factory: im.vector.app.features.settings.devices.v2.DevicesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(KeyRequestListViewModel::class)
|
@MavericksViewModelKey(KeyRequestListViewModel::class)
|
||||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.PublishDataSource
|
import im.vector.app.core.utils.PublishDataSource
|
||||||
import im.vector.app.features.auth.ReAuthActivity
|
import im.vector.app.features.auth.ReAuthActivity
|
||||||
import im.vector.app.features.login.ReAuthHelper
|
import im.vector.app.features.login.ReAuthHelper
|
||||||
|
import im.vector.app.features.settings.devices.v2.GetEncryptionTrustLevelForDeviceUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
import im.vector.lib.core.utils.flow.throttleFirst
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
|
class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
|
||||||
|
@ -28,7 +29,7 @@ class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
|
||||||
val isCrossSigningInitialized = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
val isCrossSigningInitialized = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
||||||
val isCrossSigningVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
val isCrossSigningVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||||
return CurrentSessionCrossSigningInfo(
|
return CurrentSessionCrossSigningInfo(
|
||||||
deviceId = session.sessionParams.deviceId,
|
deviceId = session.sessionParams.deviceId.orEmpty(),
|
||||||
isCrossSigningInitialized = isCrossSigningInitialized,
|
isCrossSigningInitialized = isCrossSigningInitialized,
|
||||||
isCrossSigningVerified = isCrossSigningVerified
|
isCrossSigningVerified = isCrossSigningVerified
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to hold some info about the cross signing of the current Session.
|
* Used to hold some info about the cross signing of the current Session.
|
||||||
*/
|
*/
|
||||||
data class CurrentSessionCrossSigningInfo(
|
data class CurrentSessionCrossSigningInfo(
|
||||||
val deviceId: String?,
|
val deviceId: String = "",
|
||||||
val isCrossSigningInitialized: Boolean,
|
val isCrossSigningInitialized: Boolean = false,
|
||||||
val isCrossSigningVerified: Boolean,
|
val isCrossSigningVerified: Boolean = false,
|
||||||
)
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
data class DeviceFullInfo(
|
||||||
|
val deviceInfo: DeviceInfo,
|
||||||
|
val cryptoDeviceInfo: CryptoDeviceInfo?,
|
||||||
|
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel,
|
||||||
|
val isInactive: Boolean,
|
||||||
|
)
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
|
||||||
|
sealed class DevicesAction : VectorViewModelAction {
|
||||||
|
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
|
||||||
|
sealed class DevicesViewEvent : VectorViewEvents {
|
||||||
|
data class Loading(val message: CharSequence? = null) : DevicesViewEvent()
|
||||||
|
data class Failure(val throwable: Throwable) : DevicesViewEvent()
|
||||||
|
data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : DevicesViewEvent()
|
||||||
|
data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvent()
|
||||||
|
data class ShowVerifyDevice(val userId: String, val transactionId: String?) : DevicesViewEvent()
|
||||||
|
data class SelfVerification(val session: Session) : DevicesViewEvent()
|
||||||
|
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvent()
|
||||||
|
object PromptResetSecrets : DevicesViewEvent()
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.utils.PublishDataSource
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
class DevicesViewModel @AssistedInject constructor(
|
||||||
|
@Assisted initialState: DevicesViewState,
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||||
|
private val refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||||
|
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
|
) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState), VerificationService.Listener {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
|
||||||
|
override fun create(initialState: DevicesViewState): DevicesViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
private val refreshSource = PublishDataSource<Unit>()
|
||||||
|
private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
|
||||||
|
|
||||||
|
init {
|
||||||
|
addVerificationListener()
|
||||||
|
observeCurrentSessionCrossSigningInfo()
|
||||||
|
observeDevices()
|
||||||
|
observeRefreshSource()
|
||||||
|
refreshDevicesOnCryptoDevicesChange()
|
||||||
|
queryRefreshDevicesList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
removeVerificationListener()
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addVerificationListener() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.cryptoService()
|
||||||
|
?.verificationService()
|
||||||
|
?.addListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeVerificationListener() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.cryptoService()
|
||||||
|
?.verificationService()
|
||||||
|
?.removeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeCurrentSessionCrossSigningInfo() {
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
.onEach { crossSigningInfo ->
|
||||||
|
setState {
|
||||||
|
copy(currentSessionCrossSigningInfo = crossSigningInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeDevices() {
|
||||||
|
getDeviceFullInfoListUseCase.execute()
|
||||||
|
.execute { async ->
|
||||||
|
if (async is Success) {
|
||||||
|
val deviceFullInfoList = async.invoke()
|
||||||
|
val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.isVerified.orFalse() }
|
||||||
|
val inactiveSessionsCount = deviceFullInfoList.count { it.isInactive }
|
||||||
|
copy(
|
||||||
|
devices = async,
|
||||||
|
unverifiedSessionsCount = unverifiedSessionsCount,
|
||||||
|
inactiveSessionsCount = inactiveSessionsCount,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
copy(
|
||||||
|
devices = async
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDevicesOnCryptoDevicesChange() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
refreshDevicesOnCryptoDevicesChangeUseCase.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRefreshSource() {
|
||||||
|
refreshSource.stream()
|
||||||
|
.throttleFirst(refreshThrottleDelayMs)
|
||||||
|
.onEach { refreshDevicesUseCase.execute() }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx.state == VerificationTxState.Verified) {
|
||||||
|
queryRefreshDevicesList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the refresh of the devices list.
|
||||||
|
* The devices list is the list of the devices where the user is logged in.
|
||||||
|
* It can be any mobile devices, and any browsers.
|
||||||
|
*/
|
||||||
|
private fun queryRefreshDevicesList() {
|
||||||
|
refreshSource.post(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: DevicesAction) {
|
||||||
|
when (action) {
|
||||||
|
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMarkAsManuallyVerifiedAction() {
|
||||||
|
// TODO implement when needed
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
|
||||||
|
data class DevicesViewState(
|
||||||
|
val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
|
||||||
|
val devices: Async<List<DeviceFullInfo>> = Uninitialized,
|
||||||
|
val unverifiedSessionsCount: Int = 0,
|
||||||
|
val inactiveSessionsCount: Int = 0,
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
) : MavericksState
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.flow.flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(): Flow<CurrentSessionCrossSigningInfo> {
|
||||||
|
return activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.let { session ->
|
||||||
|
session.flow().liveCrossSigningInfo(session.myUserId)
|
||||||
|
.map { convertToSigningInfo(session.sessionParams.deviceId.orEmpty(), it) }
|
||||||
|
} ?: emptyFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToSigningInfo(deviceId: String, mxCrossSigningInfo: Optional<MXCrossSigningInfo>): CurrentSessionCrossSigningInfo {
|
||||||
|
return CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = deviceId,
|
||||||
|
isCrossSigningInitialized = mxCrossSigningInfo.getOrNull() != null,
|
||||||
|
isCrossSigningVerified = mxCrossSigningInfo.getOrNull()?.isTrusted().orFalse()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.flow.flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetDeviceFullInfoListUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||||
|
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(): Flow<List<DeviceFullInfo>> {
|
||||||
|
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
|
val deviceFullInfoFlow = combine(
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase.execute(),
|
||||||
|
session.flow().liveUserCryptoDevices(session.myUserId),
|
||||||
|
session.flow().liveMyDevicesInfo()
|
||||||
|
) { currentSessionCrossSigningInfo, cryptoList, infoList ->
|
||||||
|
convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceFullInfoFlow.distinctUntilChanged()
|
||||||
|
} ?: emptyFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToDeviceFullInfoList(
|
||||||
|
currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo,
|
||||||
|
cryptoList: List<CryptoDeviceInfo>,
|
||||||
|
infoList: List<DeviceInfo>,
|
||||||
|
): List<DeviceFullInfo> {
|
||||||
|
return infoList
|
||||||
|
.sortedByDescending { it.lastSeenTs }
|
||||||
|
.map { deviceInfo ->
|
||||||
|
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
||||||
|
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
|
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
||||||
|
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.sample
|
||||||
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
|
import org.matrix.android.sdk.flow.flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
class RefreshDevicesOnCryptoDevicesChangeUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
private val samplingPeriodMs = 5.seconds.inWholeMilliseconds
|
||||||
|
|
||||||
|
suspend fun execute() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()
|
||||||
|
?.let { session ->
|
||||||
|
session.flow().liveUserCryptoDevices(session.myUserId)
|
||||||
|
.map { it.size }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.sample(samplingPeriodMs)
|
||||||
|
.onEach {
|
||||||
|
// If we have a new crypto device change, we might want to trigger refresh of device info
|
||||||
|
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||||
|
}
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RefreshDevicesUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
fun execute() {
|
||||||
|
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
|
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||||
|
session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,8 +24,6 @@ import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.Async
|
|
||||||
import com.airbnb.mvrx.Loading
|
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -39,10 +37,6 @@ import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||||
import im.vector.app.features.crypto.recover.SetupMode
|
import im.vector.app.features.crypto.recover.SetupMode
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
|
||||||
import im.vector.app.features.settings.devices.DevicesAction
|
|
||||||
import im.vector.app.features.settings.devices.DevicesViewEvents
|
|
||||||
import im.vector.app.features.settings.devices.DevicesViewModel
|
|
||||||
import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
|
import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
|
||||||
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||||
|
@ -93,27 +87,27 @@ class VectorSettingsDevicesFragment :
|
||||||
private fun observeViewEvents() {
|
private fun observeViewEvents() {
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DevicesViewEvents.Loading -> showLoading(it.message)
|
is DevicesViewEvent.Loading -> showLoading(it.message)
|
||||||
is DevicesViewEvents.Failure -> showFailure(it.throwable)
|
is DevicesViewEvent.Failure -> showFailure(it.throwable)
|
||||||
is DevicesViewEvents.RequestReAuth -> Unit // TODO. Next PR
|
is DevicesViewEvent.RequestReAuth -> Unit // TODO. Next PR
|
||||||
is DevicesViewEvents.PromptRenameDevice -> Unit // TODO. Next PR
|
is DevicesViewEvent.PromptRenameDevice -> Unit // TODO. Next PR
|
||||||
is DevicesViewEvents.ShowVerifyDevice -> {
|
is DevicesViewEvent.ShowVerifyDevice -> {
|
||||||
VerificationBottomSheet.withArgs(
|
VerificationBottomSheet.withArgs(
|
||||||
roomId = null,
|
roomId = null,
|
||||||
otherUserId = it.userId,
|
otherUserId = it.userId,
|
||||||
transactionId = it.transactionId
|
transactionId = it.transactionId
|
||||||
).show(childFragmentManager, "REQPOP")
|
).show(childFragmentManager, "REQPOP")
|
||||||
}
|
}
|
||||||
is DevicesViewEvents.SelfVerification -> {
|
is DevicesViewEvent.SelfVerification -> {
|
||||||
VerificationBottomSheet.forSelfVerification(it.session)
|
VerificationBottomSheet.forSelfVerification(it.session)
|
||||||
.show(childFragmentManager, "REQPOP")
|
.show(childFragmentManager, "REQPOP")
|
||||||
}
|
}
|
||||||
is DevicesViewEvents.ShowManuallyVerify -> {
|
is DevicesViewEvent.ShowManuallyVerify -> {
|
||||||
ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {
|
ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {
|
||||||
viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
|
viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DevicesViewEvents.PromptResetSecrets -> {
|
is DevicesViewEvent.PromptResetSecrets -> {
|
||||||
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,10 +145,11 @@ class VectorSettingsDevicesFragment :
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
if (state.devices is Success) {
|
if (state.devices is Success) {
|
||||||
val devices = state.devices()
|
val devices = state.devices()
|
||||||
|
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
||||||
val currentDeviceInfo = devices?.firstOrNull {
|
val currentDeviceInfo = devices?.firstOrNull {
|
||||||
it.deviceInfo.deviceId == state.myDeviceId
|
it.deviceInfo.deviceId == currentDeviceId
|
||||||
}
|
}
|
||||||
val otherDevices = devices?.filter { it.deviceInfo.deviceId != state.myDeviceId }
|
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
|
||||||
|
|
||||||
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount)
|
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount)
|
||||||
renderCurrentDevice(currentDeviceInfo)
|
renderCurrentDevice(currentDeviceInfo)
|
||||||
|
@ -165,7 +160,7 @@ class VectorSettingsDevicesFragment :
|
||||||
hideOtherSessionsView()
|
hideOtherSessionsView()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRequestStatus(state.request)
|
handleLoadingStatus(state.isLoading)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSecurityRecommendations(inactiveSessionsCount: Int, unverifiedSessionsCount: Int) {
|
private fun renderSecurityRecommendations(inactiveSessionsCount: Int, unverifiedSessionsCount: Int) {
|
||||||
|
@ -254,10 +249,7 @@ class VectorSettingsDevicesFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
|
private fun handleLoadingStatus(isLoading: Boolean) {
|
||||||
views.waitingView.root.isVisible = when (unIgnoreRequest) {
|
views.waitingView.root.isVisible = isLoading
|
||||||
is Loading -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.app.core.epoxy.noResultItem
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class OtherSessionsController @Inject constructor(
|
||||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||||
formattedLastActivityDate
|
formattedLastActivityDate
|
||||||
)
|
)
|
||||||
} else if (device.trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
|
} else if (device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
|
||||||
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
|
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
|
||||||
} else {
|
} else {
|
||||||
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
|
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
|
||||||
|
@ -71,7 +71,7 @@ class OtherSessionsController @Inject constructor(
|
||||||
otherSessionItem {
|
otherSessionItem {
|
||||||
id(device.deviceInfo.deviceId)
|
id(device.deviceInfo.deviceId)
|
||||||
deviceType(DeviceType.UNKNOWN) // TODO. We don't have this info yet. Update accordingly.
|
deviceType(DeviceType.UNKNOWN) // TODO. We don't have this info yet. Update accordingly.
|
||||||
roomEncryptionTrustLevel(device.trustLevelForShield)
|
roomEncryptionTrustLevel(device.roomEncryptionTrustLevel)
|
||||||
sessionName(device.deviceInfo.displayName)
|
sessionName(device.deviceInfo.displayName)
|
||||||
sessionDescription(description)
|
sessionDescription(description)
|
||||||
sessionDescriptionDrawable(descriptionDrawable)
|
sessionDescriptionDrawable(descriptionDrawable)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.databinding.ViewOtherSessionsBinding
|
import im.vector.app.databinding.ViewOtherSessionsBinding
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
|
|
@ -57,7 +57,7 @@ class SessionInfoView @JvmOverloads constructor(
|
||||||
) {
|
) {
|
||||||
renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
|
renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
|
||||||
renderVerificationStatus(
|
renderVerificationStatus(
|
||||||
sessionInfoViewState.deviceFullInfo.trustLevelForShield,
|
sessionInfoViewState.deviceFullInfo.roomEncryptionTrustLevel,
|
||||||
sessionInfoViewState.isCurrentSession,
|
sessionInfoViewState.isCurrentSession,
|
||||||
sessionInfoViewState.isLearnMoreLinkVisible,
|
sessionInfoViewState.isLearnMoreLinkVisible,
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices.v2.list
|
package im.vector.app.features.settings.devices.v2.list
|
||||||
|
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
|
|
||||||
data class SessionInfoViewState(
|
data class SessionInfoViewState(
|
||||||
val isCurrentSession: Boolean,
|
val isCurrentSession: Boolean,
|
||||||
|
|
|
@ -18,9 +18,9 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||||
|
|
||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
|
import im.vector.app.features.settings.devices.v2.GetCurrentSessionCrossSigningInfoUseCase
|
||||||
import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
|
import im.vector.app.features.settings.devices.v2.GetEncryptionTrustLevelForDeviceUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
@ -38,11 +38,11 @@ class GetDeviceFullInfoUseCase @Inject constructor(
|
||||||
|
|
||||||
fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
|
fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
|
||||||
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
|
|
||||||
combine(
|
combine(
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase.execute(),
|
||||||
session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(),
|
session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(),
|
||||||
session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow()
|
session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow()
|
||||||
) { deviceInfo, cryptoDeviceInfo ->
|
) { currentSessionCrossSigningInfo, deviceInfo, cryptoDeviceInfo ->
|
||||||
val info = deviceInfo.getOrNull()
|
val info = deviceInfo.getOrNull()
|
||||||
val cryptoInfo = cryptoDeviceInfo.getOrNull()
|
val cryptoInfo = cryptoDeviceInfo.getOrNull()
|
||||||
val fullInfo = if (info != null && cryptoInfo != null) {
|
val fullInfo = if (info != null && cryptoInfo != null) {
|
||||||
|
@ -51,7 +51,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
|
||||||
DeviceFullInfo(
|
DeviceFullInfo(
|
||||||
deviceInfo = info,
|
deviceInfo = info,
|
||||||
cryptoDeviceInfo = cryptoInfo,
|
cryptoDeviceInfo = cryptoInfo,
|
||||||
trustLevelForShield = roomEncryptionTrustLevel,
|
roomEncryptionTrustLevel = roomEncryptionTrustLevel,
|
||||||
isInactive = isInactive
|
isInactive = isInactive
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.DrawableProvider
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
|
|
||||||
data class SessionOverviewViewState(
|
data class SessionOverviewViewState(
|
||||||
val deviceId: String,
|
val deviceId: String,
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
|
||||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.test.MvRxTestRule
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fakes.FakeVerificationService
|
||||||
|
import im.vector.app.test.test
|
||||||
|
import im.vector.app.test.testDispatcher
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.runs
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
class DevicesViewModelTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher)
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
||||||
|
private val getDeviceFullInfoListUseCase = mockk<GetDeviceFullInfoListUseCase>()
|
||||||
|
private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>()
|
||||||
|
private val refreshDevicesOnCryptoDevicesChangeUseCase = mockk<RefreshDevicesOnCryptoDevicesChangeUseCase>()
|
||||||
|
|
||||||
|
private fun createViewModel(): DevicesViewModel {
|
||||||
|
return DevicesViewModel(
|
||||||
|
DevicesViewState(),
|
||||||
|
fakeActiveSessionHolder.instance,
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
getDeviceFullInfoListUseCase,
|
||||||
|
refreshDevicesUseCase,
|
||||||
|
refreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the viewModel when initializing it then verification listener is added`() {
|
||||||
|
// Given
|
||||||
|
val fakeVerificationService = givenVerificationService()
|
||||||
|
givenCurrentSessionCrossSigningInfo()
|
||||||
|
givenDeviceFullInfoList()
|
||||||
|
givenRefreshDevicesOnCryptoDevicesChange()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify {
|
||||||
|
fakeVerificationService.addListener(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the viewModel when clearing it then verification listener is removed`() {
|
||||||
|
// Given
|
||||||
|
val fakeVerificationService = givenVerificationService()
|
||||||
|
givenCurrentSessionCrossSigningInfo()
|
||||||
|
givenDeviceFullInfoList()
|
||||||
|
givenRefreshDevicesOnCryptoDevicesChange()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.onCleared()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify {
|
||||||
|
fakeVerificationService.removeListener(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the viewModel when initializing it then view state is updated with current session cross signing info`() {
|
||||||
|
// Given
|
||||||
|
givenVerificationService()
|
||||||
|
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
|
||||||
|
givenDeviceFullInfoList()
|
||||||
|
givenRefreshDevicesOnCryptoDevicesChange()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val viewModelTest = createViewModel().test()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
viewModelTest.assertLatestState { it.currentSessionCrossSigningInfo == currentSessionCrossSigningInfo }
|
||||||
|
viewModelTest.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the viewModel when initializing it then view state is updated with current device full info list`() {
|
||||||
|
// Given
|
||||||
|
givenVerificationService()
|
||||||
|
givenCurrentSessionCrossSigningInfo()
|
||||||
|
val deviceFullInfoList = givenDeviceFullInfoList()
|
||||||
|
givenRefreshDevicesOnCryptoDevicesChange()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val viewModelTest = createViewModel().test()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
viewModelTest.assertLatestState {
|
||||||
|
it.devices is Success && it.devices.invoke() == deviceFullInfoList &&
|
||||||
|
it.inactiveSessionsCount == 1 && it.unverifiedSessionsCount == 1
|
||||||
|
}
|
||||||
|
viewModelTest.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the viewModel when initializing it then devices are refreshed on crypto devices change`() {
|
||||||
|
// Given
|
||||||
|
givenVerificationService()
|
||||||
|
givenCurrentSessionCrossSigningInfo()
|
||||||
|
givenDeviceFullInfoList()
|
||||||
|
givenRefreshDevicesOnCryptoDevicesChange()
|
||||||
|
|
||||||
|
// When
|
||||||
|
createViewModel()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify { refreshDevicesOnCryptoDevicesChangeUseCase.execute() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenVerificationService(): FakeVerificationService {
|
||||||
|
val fakeVerificationService = fakeActiveSessionHolder
|
||||||
|
.fakeSession
|
||||||
|
.fakeCryptoService
|
||||||
|
.fakeVerificationService
|
||||||
|
every { fakeVerificationService.addListener(any()) } just runs
|
||||||
|
every { fakeVerificationService.removeListener(any()) } just runs
|
||||||
|
return fakeVerificationService
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
|
||||||
|
val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
|
||||||
|
every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
|
||||||
|
return currentSessionCrossSigningInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate mocked deviceFullInfo list with 1 unverified and inactive + 1 verified and active.
|
||||||
|
*/
|
||||||
|
private fun givenDeviceFullInfoList(): List<DeviceFullInfo> {
|
||||||
|
val verifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||||
|
every { verifiedCryptoDeviceInfo.isVerified } returns true
|
||||||
|
val unverifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||||
|
every { unverifiedCryptoDeviceInfo.isVerified } returns false
|
||||||
|
|
||||||
|
val deviceFullInfo1 = DeviceFullInfo(
|
||||||
|
deviceInfo = mockk(),
|
||||||
|
cryptoDeviceInfo = verifiedCryptoDeviceInfo,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
isInactive = false
|
||||||
|
)
|
||||||
|
val deviceFullInfo2 = DeviceFullInfo(
|
||||||
|
deviceInfo = mockk(),
|
||||||
|
cryptoDeviceInfo = unverifiedCryptoDeviceInfo,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||||
|
isInactive = true
|
||||||
|
)
|
||||||
|
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
||||||
|
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
||||||
|
every { getDeviceFullInfoListUseCase.execute() } returns deviceFullInfoListFlow
|
||||||
|
return deviceFullInfoList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenRefreshDevicesOnCryptoDevicesChange() {
|
||||||
|
coEvery { refreshDevicesOnCryptoDevicesChangeUseCase.execute() } just runs
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import im.vector.app.test.test
|
||||||
|
import im.vector.app.test.testDispatcher
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
|
||||||
|
private const val A_DEVICE_ID = "device-id"
|
||||||
|
|
||||||
|
class GetCurrentSessionCrossSigningInfoUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase = GetCurrentSessionCrossSigningInfoUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the active session and existing cross signing info when getting these info then the result is correct`() = runTest(testDispatcher) {
|
||||||
|
val fakeSession = givenSession(A_DEVICE_ID)
|
||||||
|
val fakeFlowSession = fakeSession.givenFlowSession()
|
||||||
|
val isCrossSigningVerified = true
|
||||||
|
val mxCrossSigningInfo = givenMxCrossSigningInfo(isCrossSigningVerified)
|
||||||
|
every { fakeFlowSession.liveCrossSigningInfo(any()) } returns flowOf(mxCrossSigningInfo.toOptional())
|
||||||
|
val expectedResult = CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
isCrossSigningInitialized = true,
|
||||||
|
isCrossSigningVerified = isCrossSigningVerified
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
.test(this)
|
||||||
|
|
||||||
|
result.assertValues(listOf(expectedResult))
|
||||||
|
.finish()
|
||||||
|
verify { fakeFlowSession.liveCrossSigningInfo(fakeSession.myUserId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the active session and no existing cross signing info when getting these info then the result is correct`() = runTest(testDispatcher) {
|
||||||
|
val fakeSession = givenSession(A_DEVICE_ID)
|
||||||
|
val fakeFlowSession = fakeSession.givenFlowSession()
|
||||||
|
val mxCrossSigningInfo = null
|
||||||
|
every { fakeFlowSession.liveCrossSigningInfo(any()) } returns flowOf(mxCrossSigningInfo.toOptional())
|
||||||
|
val expectedResult = CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
isCrossSigningInitialized = false,
|
||||||
|
isCrossSigningVerified = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
.test(this)
|
||||||
|
|
||||||
|
result.assertValues(listOf(expectedResult))
|
||||||
|
.finish()
|
||||||
|
verify { fakeFlowSession.liveCrossSigningInfo(fakeSession.myUserId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given no active session when getting cross signing info then the result is empty`() = runTest(testDispatcher) {
|
||||||
|
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||||
|
|
||||||
|
val result = getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
.test(this)
|
||||||
|
|
||||||
|
result.assertNoValues()
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenSession(deviceId: String): FakeSession {
|
||||||
|
val sessionParams = mockk<SessionParams>()
|
||||||
|
every { sessionParams.deviceId } returns deviceId
|
||||||
|
|
||||||
|
val fakeSession = fakeActiveSessionHolder.fakeSession
|
||||||
|
fakeSession.givenSessionParams(sessionParams)
|
||||||
|
|
||||||
|
return fakeSession
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenMxCrossSigningInfo(isTrusted: Boolean) = mockk<MXCrossSigningInfo>()
|
||||||
|
.also {
|
||||||
|
every { it.isTrusted() } returns isTrusted
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.test
|
||||||
|
import im.vector.app.test.testDispatcher
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
private const val A_DEVICE_ID_1 = "device-id-1"
|
||||||
|
private const val A_DEVICE_ID_2 = "device-id-2"
|
||||||
|
private const val A_DEVICE_ID_3 = "device-id-3"
|
||||||
|
private const val A_TIMESTAMP_1 = 100L
|
||||||
|
private const val A_TIMESTAMP_2 = 200L
|
||||||
|
private const val A_TIMESTAMP_3 = 300L
|
||||||
|
|
||||||
|
class GetDeviceFullInfoListUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
|
||||||
|
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
||||||
|
|
||||||
|
private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||||
|
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
|
||||||
|
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given active session when getting list of device full info then the list is correct and sorted in descending order`() = runTest(testDispatcher) {
|
||||||
|
// Given
|
||||||
|
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
|
||||||
|
val fakeFlowSession = fakeActiveSessionHolder.fakeSession.givenFlowSession()
|
||||||
|
val cryptoDeviceInfo1 = givenACryptoDeviceInfo(A_DEVICE_ID_1)
|
||||||
|
val cryptoDeviceInfo2 = givenACryptoDeviceInfo(A_DEVICE_ID_2)
|
||||||
|
val cryptoDeviceInfo3 = givenACryptoDeviceInfo(A_DEVICE_ID_3)
|
||||||
|
val cryptoDeviceInfoList = listOf(cryptoDeviceInfo1, cryptoDeviceInfo2, cryptoDeviceInfo3)
|
||||||
|
every { fakeFlowSession.liveUserCryptoDevices(any()) } returns flowOf(cryptoDeviceInfoList)
|
||||||
|
val deviceInfo1 = givenADevicesInfo(
|
||||||
|
deviceId = A_DEVICE_ID_1,
|
||||||
|
lastSeenTs = A_TIMESTAMP_1,
|
||||||
|
isInactive = true,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo1
|
||||||
|
)
|
||||||
|
val deviceInfo2 = givenADevicesInfo(
|
||||||
|
deviceId = A_DEVICE_ID_2,
|
||||||
|
lastSeenTs = A_TIMESTAMP_2,
|
||||||
|
isInactive = false,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo2
|
||||||
|
)
|
||||||
|
val deviceInfo3 = givenADevicesInfo(
|
||||||
|
deviceId = A_DEVICE_ID_3,
|
||||||
|
lastSeenTs = A_TIMESTAMP_3,
|
||||||
|
isInactive = false,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo3
|
||||||
|
)
|
||||||
|
val deviceInfoList = listOf(deviceInfo1, deviceInfo2, deviceInfo3)
|
||||||
|
every { fakeFlowSession.liveMyDevicesInfo() } returns flowOf(deviceInfoList)
|
||||||
|
val expectedResult1 = DeviceFullInfo(
|
||||||
|
deviceInfo = deviceInfo1,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo1,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
isInactive = true
|
||||||
|
)
|
||||||
|
val expectedResult2 = DeviceFullInfo(
|
||||||
|
deviceInfo = deviceInfo2,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo2,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||||
|
isInactive = false
|
||||||
|
)
|
||||||
|
val expectedResult3 = DeviceFullInfo(
|
||||||
|
deviceInfo = deviceInfo3,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo3,
|
||||||
|
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||||
|
isInactive = false
|
||||||
|
)
|
||||||
|
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getDeviceFullInfoListUseCase.execute()
|
||||||
|
.test(this)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.assertValues(expectedResult)
|
||||||
|
.finish()
|
||||||
|
verify {
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
fakeFlowSession.liveUserCryptoDevices(fakeActiveSessionHolder.fakeSession.myUserId)
|
||||||
|
fakeFlowSession.liveMyDevicesInfo()
|
||||||
|
getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo1)
|
||||||
|
getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo2)
|
||||||
|
getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo3)
|
||||||
|
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_1)
|
||||||
|
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_2)
|
||||||
|
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given no active session when getting list then the result is empty`() = runTest(testDispatcher) {
|
||||||
|
// Given
|
||||||
|
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getDeviceFullInfoListUseCase.execute()
|
||||||
|
.test(this)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.assertNoValues()
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
|
||||||
|
val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
|
||||||
|
every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
|
||||||
|
return currentSessionCrossSigningInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenACryptoDeviceInfo(deviceId: String): CryptoDeviceInfo {
|
||||||
|
val cryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||||
|
every { cryptoDeviceInfo.deviceId } returns deviceId
|
||||||
|
return cryptoDeviceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenADevicesInfo(
|
||||||
|
deviceId: String,
|
||||||
|
lastSeenTs: Long,
|
||||||
|
isInactive: Boolean,
|
||||||
|
roomEncryptionTrustLevel: RoomEncryptionTrustLevel,
|
||||||
|
cryptoDeviceInfo: CryptoDeviceInfo,
|
||||||
|
): DeviceInfo {
|
||||||
|
val deviceInfo = mockk<DeviceInfo>()
|
||||||
|
every { deviceInfo.deviceId } returns deviceId
|
||||||
|
every { deviceInfo.lastSeenTs } returns lastSeenTs
|
||||||
|
every { getEncryptionTrustLevelForDeviceUseCase.execute(any(), cryptoDeviceInfo) } returns roomEncryptionTrustLevel
|
||||||
|
every { checkIfSessionIsInactiveUseCase.execute(lastSeenTs) } returns isInactive
|
||||||
|
|
||||||
|
return deviceInfo
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -90,7 +90,7 @@ class GetEncryptionTrustLevelForDeviceUseCaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenCurrentSessionCrossSigningInfo(
|
private fun givenCurrentSessionCrossSigningInfo(
|
||||||
deviceId: String?,
|
deviceId: String,
|
||||||
isCrossSigningInitialized: Boolean,
|
isCrossSigningInitialized: Boolean,
|
||||||
isCrossSigningVerified: Boolean
|
isCrossSigningVerified: Boolean
|
||||||
): CurrentSessionCrossSigningInfo {
|
): CurrentSessionCrossSigningInfo {
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.settings.devices
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.runs
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.flow.FlowSession
|
||||||
|
import org.matrix.android.sdk.flow.flow
|
||||||
|
|
||||||
|
class RefreshDevicesOnCryptoDevicesChangeUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
|
||||||
|
private val refreshDevicesOnCryptoDevicesChangeUseCase = RefreshDevicesOnCryptoDevicesChangeUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the current session when crypto devices list changes then the devices list is refreshed`() = runTest {
|
||||||
|
// Given
|
||||||
|
val device1 = givenACryptoDevice()
|
||||||
|
val devices = listOf(device1)
|
||||||
|
val fakeSession = fakeActiveSessionHolder.fakeSession
|
||||||
|
val flowSession = mockk<FlowSession>()
|
||||||
|
every { fakeSession.flow() } returns flowSession
|
||||||
|
every { flowSession.liveUserCryptoDevices(any()) } returns flowOf(devices)
|
||||||
|
every { fakeSession.cryptoService().fetchDevicesList(any()) } just runs
|
||||||
|
|
||||||
|
// When
|
||||||
|
refreshDevicesOnCryptoDevicesChangeUseCase.execute()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify {
|
||||||
|
flowSession.liveUserCryptoDevices(fakeSession.myUserId)
|
||||||
|
// FIXME the following verification does not work due to the usage of Flow.sample() inside the use case implementation
|
||||||
|
// fakeSession.cryptoService().fetchDevicesList(match { it is NoOpMatrixCallback })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenACryptoDevice(): CryptoDeviceInfo = mockk()
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.runs
|
||||||
|
import io.mockk.verifyAll
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
|
|
||||||
|
class RefreshDevicesUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
|
||||||
|
private val refreshDevicesUseCase = RefreshDevicesUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given current session when refreshing then devices list and keys are fetched`() {
|
||||||
|
val session = fakeActiveSessionHolder.fakeSession
|
||||||
|
every { session.cryptoService().fetchDevicesList(any()) } just runs
|
||||||
|
every { session.cryptoService().downloadKeys(any(), any(), any()) } just runs
|
||||||
|
|
||||||
|
refreshDevicesUseCase.execute()
|
||||||
|
|
||||||
|
verifyAll {
|
||||||
|
session.cryptoService().fetchDevicesList(match { it is NoOpMatrixCallback })
|
||||||
|
session.cryptoService().downloadKeys(listOf(session.myUserId), true, match { it is NoOpMatrixCallback })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,10 +18,10 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
import im.vector.app.features.settings.devices.CurrentSessionCrossSigningInfo
|
import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
|
import im.vector.app.features.settings.devices.v2.GetCurrentSessionCrossSigningInfoUseCase
|
||||||
import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
|
import im.vector.app.features.settings.devices.v2.GetEncryptionTrustLevelForDeviceUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||||
|
@ -31,6 +31,7 @@ import io.mockk.mockk
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
@ -90,7 +91,7 @@ class GetDeviceFullInfoUseCaseTest {
|
||||||
DeviceFullInfo(
|
DeviceFullInfo(
|
||||||
deviceInfo = deviceInfo,
|
deviceInfo = deviceInfo,
|
||||||
cryptoDeviceInfo = cryptoDeviceInfo,
|
cryptoDeviceInfo = cryptoDeviceInfo,
|
||||||
trustLevelForShield = trustLevel,
|
roomEncryptionTrustLevel = trustLevel,
|
||||||
isInactive = isInactive,
|
isInactive = isInactive,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -134,7 +135,7 @@ class GetDeviceFullInfoUseCaseTest {
|
||||||
isCrossSigningInitialized = true,
|
isCrossSigningInitialized = true,
|
||||||
isCrossSigningVerified = false
|
isCrossSigningVerified = false
|
||||||
)
|
)
|
||||||
every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns currentSessionCrossSigningInfo
|
every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
|
||||||
return currentSessionCrossSigningInfo
|
return currentSessionCrossSigningInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||||
|
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.test.MvRxTestRule
|
import com.airbnb.mvrx.test.MvRxTestRule
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
import im.vector.app.test.test
|
import im.vector.app.test.test
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
|
|
@ -24,7 +24,8 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
class FakeCryptoService(
|
class FakeCryptoService(
|
||||||
val fakeCrossSigningService: FakeCrossSigningService = FakeCrossSigningService()
|
val fakeCrossSigningService: FakeCrossSigningService = FakeCrossSigningService(),
|
||||||
|
val fakeVerificationService: FakeVerificationService = FakeVerificationService(),
|
||||||
) : CryptoService by mockk() {
|
) : CryptoService by mockk() {
|
||||||
|
|
||||||
var roomKeysExport = ByteArray(size = 1)
|
var roomKeysExport = ByteArray(size = 1)
|
||||||
|
@ -34,6 +35,8 @@ class FakeCryptoService(
|
||||||
|
|
||||||
override fun crossSigningService() = fakeCrossSigningService
|
override fun crossSigningService() = fakeCrossSigningService
|
||||||
|
|
||||||
|
override fun verificationService() = fakeVerificationService
|
||||||
|
|
||||||
override suspend fun exportRoomKeys(password: String) = roomKeysExport
|
override suspend fun exportRoomKeys(password: String) = roomKeysExport
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
|
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
|
||||||
|
|
|
@ -32,6 +32,8 @@ import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.flow.FlowSession
|
||||||
|
import org.matrix.android.sdk.flow.flow
|
||||||
|
|
||||||
class FakeSession(
|
class FakeSession(
|
||||||
val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
|
val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
|
||||||
|
@ -76,6 +78,15 @@ class FakeSession(
|
||||||
every { this@FakeSession.sessionParams } returns sessionParams
|
every { this@FakeSession.sessionParams } returns sessionParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not forget to call mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") in the setup method of the tests.
|
||||||
|
*/
|
||||||
|
fun givenFlowSession(): FlowSession {
|
||||||
|
val fakeFlowSession = mockk<FlowSession>()
|
||||||
|
every { flow() } returns fakeFlowSession
|
||||||
|
return fakeFlowSession
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply {
|
fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
|
|
||||||
|
class FakeVerificationService : VerificationService by mockk()
|
Loading…
Reference in New Issue