Merge pull request #7306 from vector-im/feature/mna/device-manager-extended-details

[Device Management] Render extended device info (PSG-773)
This commit is contained in:
Maxime NATUREL 2022-10-12 18:09:33 +02:00 committed by GitHub
commit f8f416e979
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 574 additions and 104 deletions

1
changelog.d/7294.wip Normal file
View file

@ -0,0 +1 @@
[Device Management] Render extended device info

View file

@ -3314,6 +3314,13 @@
<string name="device_manager_session_details_session_name">Session name</string>
<string name="device_manager_session_details_session_id">Session ID</string>
<string name="device_manager_session_details_session_last_activity">Last activity</string>
<string name="device_manager_session_details_application">Application</string>
<string name="device_manager_session_details_application_name">Name</string>
<string name="device_manager_session_details_application_version">Version</string>
<string name="device_manager_session_details_application_url">URL</string>
<string name="device_manager_session_details_device_browser">Browser</string>
<string name="device_manager_session_details_device_model">Model</string>
<string name="device_manager_session_details_device_operating_system">Operating system</string>
<string name="device_manager_session_details_device_ip_address">IP address</string>
<string name="device_manager_session_rename">Rename session</string>
<string name="device_manager_session_rename_edit_hint">Session name</string>

View file

@ -19,4 +19,4 @@ package im.vector.app.core.session.clientinfo
/**
* Prefix for the key account data event which holds client info.
*/
const val MATRIX_CLIENT_INFO_KEY_PREFIX = "io.element.matrix_client_information."
internal const val MATRIX_CLIENT_INFO_KEY_PREFIX = "io.element.matrix_client_information."

View file

@ -16,6 +16,8 @@
package im.vector.app.features.settings.devices.v2
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
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
@ -27,4 +29,5 @@ data class DeviceFullInfo(
val isInactive: Boolean,
val isCurrentDevice: Boolean,
val deviceExtendedInfo: DeviceExtendedInfo,
val matrixClientInfo: MatrixClientInfoContent?,
)

View file

@ -17,6 +17,7 @@
package im.vector.app.features.settings.devices.v2
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.session.clientinfo.GetMatrixClientInfoUseCase
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
@ -27,6 +28,7 @@ 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.Session
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
@ -39,6 +41,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
private val filterDevicesUseCase: FilterDevicesUseCase,
private val parseDeviceUserAgentUseCase: ParseDeviceUserAgentUseCase,
private val getMatrixClientInfoUseCase: GetMatrixClientInfoUseCase,
) {
fun execute(filterType: DeviceManagerFilterType, excludeCurrentDevice: Boolean = false): Flow<List<DeviceFullInfo>> {
@ -48,7 +51,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
session.flow().liveUserCryptoDevices(session.myUserId),
session.flow().liveMyDevicesInfo()
) { currentSessionCrossSigningInfo, cryptoList, infoList ->
val deviceFullInfoList = convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
val deviceFullInfoList = convertToDeviceFullInfoList(session, currentSessionCrossSigningInfo, cryptoList, infoList)
val excludedDeviceIds = if (excludeCurrentDevice) {
listOf(currentSessionCrossSigningInfo.deviceId)
} else {
@ -62,6 +65,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
}
private fun convertToDeviceFullInfoList(
session: Session,
currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo,
cryptoList: List<CryptoDeviceInfo>,
infoList: List<DeviceInfo>,
@ -73,8 +77,20 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoDeviceInfo?.deviceId
val deviceUserAgent = parseDeviceUserAgentUseCase.execute(deviceInfo.getBestLastSeenUserAgent())
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive, isCurrentDevice, deviceUserAgent)
val deviceExtendedInfo = parseDeviceUserAgentUseCase.execute(deviceInfo.getBestLastSeenUserAgent())
val matrixClientInfo = deviceInfo.deviceId
?.takeIf { it.isNotEmpty() }
?.let { getMatrixClientInfoUseCase.execute(session, it) }
DeviceFullInfo(
deviceInfo = deviceInfo,
cryptoDeviceInfo = cryptoDeviceInfo,
roomEncryptionTrustLevel = roomEncryptionTrustLevel,
isInactive = isInactive,
isCurrentDevice = isCurrentDevice,
deviceExtendedInfo = deviceExtendedInfo,
matrixClientInfo = matrixClientInfo
)
}
}
}

View file

@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject
@ -139,13 +140,11 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
}
private fun getBrowserVersion(browserSegments: List<String>, browserName: String): String? {
// Chrome/104.0.3497.100 -> 104
// e.g Chrome/104.0.3497.100 -> 104.0.3497.100
return browserSegments
.find { it.startsWith(browserName) }
?.split("/")
?.getOrNull(1)
?.split(".")
?.firstOrNull()
}
private fun isEdge(browserSegments: List<String>): Boolean {

View file

@ -0,0 +1,30 @@
/*
* 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.details
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject
class CheckIfSectionApplicationIsVisibleUseCase @Inject constructor() {
fun execute(matrixClientInfoContent: MatrixClientInfoContent?): Boolean {
return matrixClientInfoContent?.name?.isNotEmpty().orFalse() ||
matrixClientInfoContent?.version?.isNotEmpty().orFalse() ||
matrixClientInfoContent?.url?.isNotEmpty().orFalse()
}
}

View file

@ -16,13 +16,36 @@
package im.vector.app.features.settings.devices.v2.details
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
class CheckIfSectionDeviceIsVisibleUseCase @Inject constructor() {
fun execute(deviceInfo: DeviceInfo): Boolean {
return deviceInfo.lastSeenIp?.isNotEmpty().orFalse()
fun execute(deviceFullInfo: DeviceFullInfo): Boolean {
val hasExtendedInfo = when (deviceFullInfo.deviceExtendedInfo.deviceType) {
DeviceType.MOBILE -> hasAnyDeviceExtendedInfoMobile(deviceFullInfo.deviceExtendedInfo)
DeviceType.WEB -> hasAnyDeviceExtendedInfoWeb(deviceFullInfo.deviceExtendedInfo)
DeviceType.DESKTOP -> hasAnyDeviceExtendedInfoDesktop(deviceFullInfo.deviceExtendedInfo)
DeviceType.UNKNOWN -> false
}
return hasExtendedInfo || deviceFullInfo.deviceInfo.lastSeenIp?.isNotEmpty().orFalse()
}
private fun hasAnyDeviceExtendedInfoMobile(deviceExtendedInfo: DeviceExtendedInfo): Boolean {
return deviceExtendedInfo.deviceModel?.isNotEmpty().orFalse() ||
deviceExtendedInfo.deviceOperatingSystem?.isNotEmpty().orFalse()
}
private fun hasAnyDeviceExtendedInfoWeb(deviceExtendedInfo: DeviceExtendedInfo): Boolean {
return deviceExtendedInfo.clientName?.isNotEmpty().orFalse() ||
deviceExtendedInfo.deviceOperatingSystem?.isNotEmpty().orFalse()
}
private fun hasAnyDeviceExtendedInfoDesktop(deviceExtendedInfo: DeviceExtendedInfo): Boolean {
return deviceExtendedInfo.deviceOperatingSystem?.isNotEmpty().orFalse()
}
}

View file

@ -23,17 +23,21 @@ import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
class SessionDetailsController @Inject constructor(
private val checkIfSectionSessionIsVisibleUseCase: CheckIfSectionSessionIsVisibleUseCase,
private val checkIfSectionDeviceIsVisibleUseCase: CheckIfSectionDeviceIsVisibleUseCase,
private val checkIfSectionApplicationIsVisibleUseCase: CheckIfSectionApplicationIsVisibleUseCase,
private val stringProvider: StringProvider,
private val dateFormatter: VectorDateFormatter,
private val dimensionConverter: DimensionConverter,
) : TypedEpoxyController<DeviceInfo>() {
) : TypedEpoxyController<DeviceFullInfo>() {
var callback: Callback? = null
@ -41,15 +45,22 @@ class SessionDetailsController @Inject constructor(
fun onItemLongClicked(content: String)
}
override fun buildModels(data: DeviceInfo?) {
data?.let { info ->
val hasSectionSession = hasSectionSession(data)
override fun buildModels(data: DeviceFullInfo?) {
data?.let { fullInfo ->
val deviceInfo = fullInfo.deviceInfo
val matrixClientInfo = fullInfo.matrixClientInfo
val hasSectionSession = hasSectionSession(deviceInfo)
if (hasSectionSession) {
buildSectionSession(info)
buildSectionSession(deviceInfo)
}
if (hasSectionDevice(data)) {
buildSectionDevice(info, addExtraTopMargin = hasSectionSession)
val hasApplicationSection = hasSectionApplication(matrixClientInfo)
if (hasApplicationSection && matrixClientInfo != null) {
buildSectionApplication(matrixClientInfo, addExtraTopMargin = hasSectionSession)
}
if (hasSectionDevice(fullInfo)) {
buildSectionDevice(fullInfo, addExtraTopMargin = hasSectionSession || hasApplicationSection)
}
}
}
@ -83,39 +94,126 @@ class SessionDetailsController @Inject constructor(
}
private fun buildSectionSession(data: DeviceInfo) {
val sessionName = data.displayName
val sessionId = data.deviceId
val sessionLastSeenTs = data.lastSeenTs
val sessionName = data.displayName.orEmpty()
val sessionId = data.deviceId.orEmpty()
val sessionLastSeenTs = data.lastSeenTs ?: -1
buildHeaderItem(R.string.device_manager_session_title)
sessionName?.let {
val hasDivider = sessionId != null || sessionLastSeenTs != null
buildContentItem(R.string.device_manager_session_details_session_name, it, hasDivider)
if (sessionName.isNotEmpty()) {
val hasDivider = sessionId.isNotEmpty() || sessionLastSeenTs > 0
buildContentItem(R.string.device_manager_session_details_session_name, sessionName, hasDivider)
}
sessionId?.let {
val hasDivider = sessionLastSeenTs != null
buildContentItem(R.string.device_manager_session_details_session_id, it, hasDivider)
if (sessionId.isNotEmpty()) {
val hasDivider = sessionLastSeenTs > 0
buildContentItem(R.string.device_manager_session_details_session_id, sessionId, hasDivider)
}
sessionLastSeenTs?.let {
val formattedDate = dateFormatter.format(it, DateFormatKind.MESSAGE_DETAIL)
if (sessionLastSeenTs > 0) {
val formattedDate = dateFormatter.format(sessionLastSeenTs, DateFormatKind.MESSAGE_DETAIL)
val hasDivider = false
buildContentItem(R.string.device_manager_session_details_session_last_activity, formattedDate, hasDivider)
}
}
private fun hasSectionDevice(data: DeviceInfo): Boolean {
private fun hasSectionApplication(matrixClientInfoContent: MatrixClientInfoContent?): Boolean {
return checkIfSectionApplicationIsVisibleUseCase.execute(matrixClientInfoContent)
}
private fun buildSectionApplication(matrixClientInfoContent: MatrixClientInfoContent, addExtraTopMargin: Boolean) {
val name = matrixClientInfoContent.name.orEmpty()
val version = matrixClientInfoContent.version.orEmpty()
val url = matrixClientInfoContent.url.orEmpty()
buildHeaderItem(R.string.device_manager_session_details_application, addExtraTopMargin)
if (name.isNotEmpty()) {
val hasDivider = version.isNotEmpty() || url.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_application_name, name, hasDivider)
}
if (version.isNotEmpty()) {
val hasDivider = url.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_application_version, version, hasDivider)
}
if (url.isNotEmpty()) {
val hasDivider = false
buildContentItem(R.string.device_manager_session_details_application_url, url, hasDivider)
}
}
private fun hasSectionDevice(data: DeviceFullInfo): Boolean {
return checkIfSectionDeviceIsVisibleUseCase.execute(data)
}
private fun buildSectionDevice(data: DeviceInfo, addExtraTopMargin: Boolean) {
val lastSeenIp = data.lastSeenIp
private fun buildSectionDevice(data: DeviceFullInfo, addExtraTopMargin: Boolean) {
buildHeaderItem(R.string.device_manager_device_title, addExtraTopMargin)
lastSeenIp?.let {
when (data.deviceExtendedInfo.deviceType) {
DeviceType.MOBILE -> buildSectionDeviceMobile(data)
DeviceType.WEB -> buildSectionDeviceWeb(data)
DeviceType.DESKTOP -> buildSectionDeviceDesktop(data)
DeviceType.UNKNOWN -> buildSectionDeviceUnknown(data)
}
}
private fun buildSectionDeviceWeb(data: DeviceFullInfo) {
val browserName = data.deviceExtendedInfo.clientName.orEmpty()
val browserVersion = data.deviceExtendedInfo.clientVersion.orEmpty()
val browser = "$browserName $browserVersion"
val operatingSystem = data.deviceExtendedInfo.deviceOperatingSystem.orEmpty()
val lastSeenIp = data.deviceInfo.lastSeenIp.orEmpty()
if (browser.isNotEmpty()) {
val hasDivider = operatingSystem.isNotEmpty() || lastSeenIp.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_device_browser, browser, hasDivider)
}
if (operatingSystem.isNotEmpty()) {
val hasDivider = lastSeenIp.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_device_operating_system, operatingSystem, hasDivider)
}
buildIpAddressContentItem(lastSeenIp)
}
private fun buildSectionDeviceDesktop(data: DeviceFullInfo) {
val operatingSystem = data.deviceExtendedInfo.deviceOperatingSystem.orEmpty()
val lastSeenIp = data.deviceInfo.lastSeenIp.orEmpty()
if (operatingSystem.isNotEmpty()) {
val hasDivider = lastSeenIp.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_device_operating_system, operatingSystem, hasDivider)
}
buildIpAddressContentItem(lastSeenIp)
}
private fun buildSectionDeviceMobile(data: DeviceFullInfo) {
val model = data.deviceExtendedInfo.deviceModel.orEmpty()
val operatingSystem = data.deviceExtendedInfo.deviceOperatingSystem.orEmpty()
val lastSeenIp = data.deviceInfo.lastSeenIp.orEmpty()
if (model.isNotEmpty()) {
val hasDivider = operatingSystem.isNotEmpty() || lastSeenIp.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_device_model, model, hasDivider)
}
if (operatingSystem.isNotEmpty()) {
val hasDivider = lastSeenIp.isNotEmpty()
buildContentItem(R.string.device_manager_session_details_device_operating_system, operatingSystem, hasDivider)
}
buildIpAddressContentItem(lastSeenIp)
}
private fun buildSectionDeviceUnknown(data: DeviceFullInfo) {
val lastSeenIp = data.deviceInfo.lastSeenIp.orEmpty()
buildIpAddressContentItem(lastSeenIp)
}
private fun buildIpAddressContentItem(lastSeenIp: String) {
if (lastSeenIp.isNotEmpty()) {
val hasDivider = false
buildContentItem(R.string.device_manager_session_details_device_ip_address, it, hasDivider)
buildContentItem(R.string.device_manager_session_details_device_ip_address, lastSeenIp, hasDivider)
}
}
}

View file

@ -33,7 +33,7 @@ import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.showOptimizedSnackbar
import im.vector.app.databinding.FragmentSessionDetailsBinding
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import javax.inject.Inject
/**
@ -92,16 +92,16 @@ class SessionDetailsFragment :
}
override fun invalidate() = withState(viewModel) { state ->
if (state.deviceInfo is Success) {
renderSessionDetails(state.deviceInfo.invoke())
if (state.deviceFullInfo is Success) {
renderSessionDetails(state.deviceFullInfo.invoke())
} else {
hideSessionDetails()
}
}
private fun renderSessionDetails(deviceInfo: DeviceInfo) {
private fun renderSessionDetails(deviceFullInfo: DeviceFullInfo) {
views.sessionDetails.isVisible = true
sessionDetailsController.setData(deviceInfo)
sessionDetailsController.setData(deviceFullInfo)
}
private fun hideSessionDetails() {

View file

@ -48,7 +48,7 @@ class SessionDetailsViewModel @AssistedInject constructor(
private fun observeSessionInfo(deviceId: String) {
getDeviceFullInfoUseCase.execute(deviceId)
.onEach { setState { copy(deviceInfo = Success(it.deviceInfo)) } }
.onEach { setState { copy(deviceFullInfo = Success(it)) } }
.launchIn(viewModelScope)
}

View file

@ -19,11 +19,11 @@ package im.vector.app.features.settings.devices.v2.details
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
data class SessionDetailsViewState(
val deviceId: String,
val deviceInfo: Async<DeviceInfo> = Uninitialized,
val deviceFullInfo: Async<DeviceFullInfo> = Uninitialized,
) : MavericksState {
constructor(args: SessionDetailsArgs) : this(
deviceId = args.deviceId

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.app.features.settings.devices.v2
package im.vector.app.features.settings.devices.v2.details.extended
import im.vector.app.features.settings.devices.v2.list.DeviceType

View file

@ -18,6 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
import androidx.lifecycle.asFlow
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.session.clientinfo.GetMatrixClientInfoUseCase
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.ParseDeviceUserAgentUseCase
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
@ -36,6 +37,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
private val parseDeviceUserAgentUseCase: ParseDeviceUserAgentUseCase,
private val getMatrixClientInfoUseCase: GetMatrixClientInfoUseCase,
) {
fun execute(deviceId: String): Flow<DeviceFullInfo> {
@ -52,6 +54,10 @@ class GetDeviceFullInfoUseCase @Inject constructor(
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoInfo.deviceId
val deviceUserAgent = parseDeviceUserAgentUseCase.execute(info.getBestLastSeenUserAgent())
val matrixClientInfo = info.deviceId
?.takeIf { it.isNotEmpty() }
?.let { getMatrixClientInfoUseCase.execute(session, it) }
DeviceFullInfo(
deviceInfo = info,
cryptoDeviceInfo = cryptoInfo,
@ -59,6 +65,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
isInactive = isInactive,
isCurrentDevice = isCurrentDevice,
deviceExtendedInfo = deviceUserAgent,
matrixClientInfo = matrixClientInfo
)
} else {
null

View file

@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2
import android.os.SystemClock
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MavericksTestRule
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
@ -53,7 +55,7 @@ class DevicesViewModelTest {
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
private val getDeviceFullInfoListUseCase = mockk<GetDeviceFullInfoListUseCase>()
private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>()
private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>(relaxUnitFun = true)
private val refreshDevicesOnCryptoDevicesChangeUseCase = mockk<RefreshDevicesOnCryptoDevicesChangeUseCase>()
private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>()
@ -245,7 +247,8 @@ class DevicesViewModelTest {
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
isInactive = false,
isCurrentDevice = true,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = MatrixClientInfoContent(),
)
val deviceFullInfo2 = DeviceFullInfo(
deviceInfo = mockk(),
@ -253,7 +256,8 @@ class DevicesViewModelTest {
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
isInactive = true,
isCurrentDevice = false,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = MatrixClientInfoContent(),
)
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)

View file

@ -16,6 +16,9 @@
package im.vector.app.features.settings.devices.v2
import im.vector.app.core.session.clientinfo.GetMatrixClientInfoUseCase
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
@ -55,6 +58,7 @@ class GetDeviceFullInfoListUseCaseTest {
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
private val filterDevicesUseCase = mockk<FilterDevicesUseCase>()
private val parseDeviceUserAgentUseCase = mockk<ParseDeviceUserAgentUseCase>()
private val getMatrixClientInfoUseCase = mockk<GetMatrixClientInfoUseCase>()
private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
activeSessionHolder = fakeActiveSessionHolder.instance,
@ -63,6 +67,7 @@ class GetDeviceFullInfoListUseCaseTest {
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
filterDevicesUseCase = filterDevicesUseCase,
parseDeviceUserAgentUseCase = parseDeviceUserAgentUseCase,
getMatrixClientInfoUseCase = getMatrixClientInfoUseCase,
)
@Before
@ -108,13 +113,17 @@ class GetDeviceFullInfoListUseCaseTest {
)
val deviceInfoList = listOf(deviceInfo1, deviceInfo2, deviceInfo3)
every { fakeFlowSession.liveMyDevicesInfo() } returns flowOf(deviceInfoList)
val matrixClientInfo1 = givenAMatrixClientInfo(A_DEVICE_ID_1)
val matrixClientInfo2 = givenAMatrixClientInfo(A_DEVICE_ID_2)
val matrixClientInfo3 = givenAMatrixClientInfo(A_DEVICE_ID_3)
val expectedResult1 = DeviceFullInfo(
deviceInfo = deviceInfo1,
cryptoDeviceInfo = cryptoDeviceInfo1,
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
isInactive = true,
isCurrentDevice = true,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = matrixClientInfo1,
)
val expectedResult2 = DeviceFullInfo(
deviceInfo = deviceInfo2,
@ -122,7 +131,8 @@ class GetDeviceFullInfoListUseCaseTest {
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
isInactive = false,
isCurrentDevice = false,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = matrixClientInfo2,
)
val expectedResult3 = DeviceFullInfo(
deviceInfo = deviceInfo3,
@ -130,7 +140,8 @@ class GetDeviceFullInfoListUseCaseTest {
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
isInactive = false,
isCurrentDevice = false,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = matrixClientInfo3,
)
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult
@ -152,6 +163,9 @@ class GetDeviceFullInfoListUseCaseTest {
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_1)
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_2)
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_3)
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_1)
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_2)
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_3)
}
}
@ -201,4 +215,10 @@ class GetDeviceFullInfoListUseCaseTest {
return deviceInfo
}
private fun givenAMatrixClientInfo(deviceId: String): MatrixClientInfoContent {
val matrixClientInfo = mockk<MatrixClientInfoContent>()
every { getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, deviceId) } returns matrixClientInfo
return matrixClientInfo
}
}

View file

@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
@ -61,8 +62,8 @@ private val A_USER_AGENT_LIST_FOR_DESKTOP = listOf(
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36",
)
private val AN_EXPECTED_RESULT_LIST_FOR_DESKTOP = listOf(
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Macintosh", "Electron", "20"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Windows NT 10.0", "Electron", "20"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Macintosh", "Electron", "20.1.1"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Windows NT 10.0", "Electron", "20.1.1"),
)
private val A_USER_AGENT_LIST_FOR_WEB = listOf(
@ -77,15 +78,15 @@ private val A_USER_AGENT_LIST_FOR_WEB = listOf(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
)
private val AN_EXPECTED_RESULT_LIST_FOR_WEB = listOf(
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Chrome", "104"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Chrome", "104"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Firefox", "39"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Safari", "8"),
DeviceExtendedInfo(DeviceType.WEB, null, "Android 9", "Chrome", "69"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPad", "Safari", "8"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPhone", "Safari", "8"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 6.0", "Firefox", "40"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Edge", "12"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Firefox", "39.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Safari", "8.0.3"),
DeviceExtendedInfo(DeviceType.WEB, null, "Android 9", "Chrome", "69.0.3497.100"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPad", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPhone", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 6.0", "Firefox", "40.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Edge", "12.246"),
)
private val AN_UNKNOWN_USER_AGENT_LIST = listOf(

View file

@ -0,0 +1,145 @@
/*
* 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.details
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
private const val AN_APP_NAME = "app-name"
private const val AN_APP_VERSION = "app-version"
private const val AN_APP_URL = "app-url"
class CheckIfSectionApplicationIsVisibleUseCaseTest {
private val checkIfSectionApplicationIsVisibleUseCase = CheckIfSectionApplicationIsVisibleUseCase()
@Test
fun `given client info with name, version or url when checking is application section is visible then it returns true`() {
// Given
val clientInfoList = listOf(
givenAClientInfo(
name = AN_APP_NAME,
version = null,
url = null,
),
givenAClientInfo(
name = null,
version = AN_APP_VERSION,
url = null,
),
givenAClientInfo(
name = null,
version = null,
url = AN_APP_URL,
),
givenAClientInfo(
name = AN_APP_NAME,
version = AN_APP_VERSION,
url = null,
),
givenAClientInfo(
name = AN_APP_NAME,
version = null,
url = AN_APP_URL,
),
givenAClientInfo(
name = null,
version = AN_APP_VERSION,
url = AN_APP_URL,
),
givenAClientInfo(
name = AN_APP_NAME,
version = AN_APP_VERSION,
url = AN_APP_URL,
),
)
clientInfoList.forEach { clientInfo ->
// When
val result = checkIfSectionApplicationIsVisibleUseCase.execute(clientInfo)
// Then
result shouldBeEqualTo true
}
}
@Test
fun `given client info with missing application info when checking is application section is visible then it returns false`() {
// Given
val clientInfoList = listOf(
givenAClientInfo(
name = null,
version = null,
url = null,
),
givenAClientInfo(
name = "",
version = null,
url = null,
),
givenAClientInfo(
name = null,
version = "",
url = null,
),
givenAClientInfo(
name = null,
version = null,
url = "",
),
givenAClientInfo(
name = "",
version = "",
url = null,
),
givenAClientInfo(
name = "",
version = null,
url = "",
),
givenAClientInfo(
name = null,
version = "",
url = "",
),
givenAClientInfo(
name = "",
version = "",
url = "",
),
)
clientInfoList.forEach { clientInfo ->
// When
val result = checkIfSectionApplicationIsVisibleUseCase.execute(clientInfo)
// Then
result shouldBeEqualTo false
}
}
private fun givenAClientInfo(
name: String?,
version: String?,
url: String?,
) = MatrixClientInfoContent(
name = name,
version = version,
url = url,
)
}

View file

@ -16,44 +16,116 @@
package im.vector.app.features.settings.devices.v2.details
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
private const val AN_IP_ADDRESS = "ip-address"
private const val A_DEVICE_MODEL = "device-model"
private const val A_DEVICE_OPERATING_SYSTEM = "device-operating-system"
private const val A_CLIENT_NAME = "client-name"
class CheckIfSectionDeviceIsVisibleUseCaseTest {
private val checkIfSectionDeviceIsVisibleUseCase = CheckIfSectionDeviceIsVisibleUseCase()
@Test
fun `given device info with Ip address when checking is device section is visible then it returns true`() = runTest {
// Given
val deviceInfo = givenADeviceInfo(AN_IP_ADDRESS)
fun `given device of any type with Ip address when checking if device section is visible then it returns true`() {
DeviceType.values().forEach { deviceType ->
// Given
val deviceExtendedInfo = givenAnExtendedDeviceInfo(deviceType)
val deviceFullInfo = givenADeviceFullInfo(deviceExtendedInfo)
val deviceInfo = givenADeviceInfo(ipAddress = AN_IP_ADDRESS)
every { deviceFullInfo.deviceInfo } returns deviceInfo
// When
val result = checkIfSectionDeviceIsVisibleUseCase.execute(deviceInfo)
// When
val result = checkIfSectionDeviceIsVisibleUseCase.execute(deviceFullInfo)
// Then
result shouldBeEqualTo true
// Then
result shouldBeEqualTo true
}
}
@Test
fun `given device info with empty or null Ip address when checking is device section is visible then it returns false`() = runTest {
fun `given device of any type with empty or null Ip address and no extended info when checking if device section is visible then it returns false`() {
DeviceType.values().forEach { deviceType ->
// Given
val deviceExtendedInfo = givenAnExtendedDeviceInfo(deviceType)
val deviceFullInfo1 = givenADeviceFullInfo(deviceExtendedInfo)
val deviceFullInfo2 = givenADeviceFullInfo(deviceExtendedInfo)
val deviceInfo1 = givenADeviceInfo(ipAddress = "")
val deviceInfo2 = givenADeviceInfo(ipAddress = null)
every { deviceFullInfo1.deviceInfo } returns deviceInfo1
every { deviceFullInfo2.deviceInfo } returns deviceInfo2
// When
val result1 = checkIfSectionDeviceIsVisibleUseCase.execute(deviceFullInfo1)
val result2 = checkIfSectionDeviceIsVisibleUseCase.execute(deviceFullInfo2)
// Then
result1 shouldBeEqualTo false
result2 shouldBeEqualTo false
}
}
@Test
fun `given device of any type with extended info when checking if device section is visible then it returns true`() {
// Given
val deviceInfo1 = givenADeviceInfo("")
val deviceInfo2 = givenADeviceInfo(null)
val deviceExtendedInfoList = listOf(
givenAnExtendedDeviceInfo(
DeviceType.MOBILE,
deviceModel = A_DEVICE_MODEL,
),
givenAnExtendedDeviceInfo(
DeviceType.MOBILE,
deviceOperatingSystem = A_DEVICE_OPERATING_SYSTEM,
),
givenAnExtendedDeviceInfo(
DeviceType.MOBILE,
deviceModel = A_DEVICE_MODEL,
deviceOperatingSystem = A_DEVICE_OPERATING_SYSTEM,
),
givenAnExtendedDeviceInfo(
DeviceType.DESKTOP,
deviceOperatingSystem = A_DEVICE_OPERATING_SYSTEM,
),
givenAnExtendedDeviceInfo(
DeviceType.WEB,
clientName = A_CLIENT_NAME,
),
givenAnExtendedDeviceInfo(
DeviceType.WEB,
deviceOperatingSystem = A_DEVICE_OPERATING_SYSTEM,
),
givenAnExtendedDeviceInfo(
DeviceType.WEB,
clientName = A_CLIENT_NAME,
deviceOperatingSystem = A_DEVICE_OPERATING_SYSTEM,
),
)
// When
val result1 = checkIfSectionDeviceIsVisibleUseCase.execute(deviceInfo1)
val result2 = checkIfSectionDeviceIsVisibleUseCase.execute(deviceInfo2)
deviceExtendedInfoList.forEach { deviceExtendedInfo ->
val deviceFullInfo = givenADeviceFullInfo(deviceExtendedInfo)
val deviceInfo = givenADeviceInfo(ipAddress = null)
every { deviceFullInfo.deviceInfo } returns deviceInfo
// Then
result1 shouldBeEqualTo false
result2 shouldBeEqualTo false
// When
val result = checkIfSectionDeviceIsVisibleUseCase.execute(deviceFullInfo)
// Then
result shouldBeEqualTo true
}
}
private fun givenADeviceFullInfo(deviceExtendedInfo: DeviceExtendedInfo): DeviceFullInfo {
val deviceFullInfo = mockk<DeviceFullInfo>()
every { deviceFullInfo.deviceExtendedInfo } returns deviceExtendedInfo
return deviceFullInfo
}
private fun givenADeviceInfo(ipAddress: String?): DeviceInfo {
@ -61,4 +133,20 @@ class CheckIfSectionDeviceIsVisibleUseCaseTest {
every { info.lastSeenIp } returns ipAddress
return info
}
private fun givenAnExtendedDeviceInfo(
deviceType: DeviceType,
clientName: String? = null,
clientVersion: String? = null,
deviceOperatingSystem: String? = null,
deviceModel: String? = null,
): DeviceExtendedInfo {
return DeviceExtendedInfo(
deviceType = deviceType,
clientName = clientName,
clientVersion = clientVersion,
deviceOperatingSystem = deviceOperatingSystem,
deviceModel = deviceModel,
)
}
}

View file

@ -18,7 +18,6 @@ package im.vector.app.features.settings.devices.v2.details
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
@ -32,7 +31,7 @@ class CheckIfSectionSessionIsVisibleUseCaseTest {
private val checkIfSectionSessionIsVisibleUseCase = CheckIfSectionSessionIsVisibleUseCase()
@Test
fun `given device info with name, id or lastSeenTs when checking is session section is visible then it returns true`() = runTest {
fun `given device info with name, id or lastSeenTs when checking is session section is visible then it returns true`() {
// Given
val deviceInfoList = listOf(
givenADeviceInfo(
@ -82,7 +81,7 @@ class CheckIfSectionSessionIsVisibleUseCaseTest {
}
@Test
fun `given device info with missing session info when checking is session section is visible then it returns true`() = runTest {
fun `given device info with missing session info when checking is session section is visible then it returns false`() {
// Given
val deviceInfoList = listOf(
givenADeviceInfo(

View file

@ -57,12 +57,10 @@ class SessionDetailsViewModelTest {
fun `given the viewModel has been initialized then viewState is updated with session info`() {
// Given
val deviceFullInfo = mockk<DeviceFullInfo>()
val deviceInfo = mockk<DeviceInfo>()
every { deviceFullInfo.deviceInfo } returns deviceInfo
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(deviceFullInfo)
val expectedState = SessionDetailsViewState(
deviceId = A_SESSION_ID,
deviceInfo = Success(deviceInfo)
deviceFullInfo = Success(deviceFullInfo)
)
// When

View file

@ -16,8 +16,9 @@
package im.vector.app.features.settings.devices.v2.filter
import im.vector.app.features.settings.devices.v2.DeviceExtendedInfo
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldContainAll
@ -37,7 +38,8 @@ private val activeVerifiedDevice = DeviceFullInfo(
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
isInactive = false,
isCurrentDevice = true,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = MatrixClientInfoContent(),
)
private val inactiveVerifiedDevice = DeviceFullInfo(
deviceInfo = DeviceInfo(deviceId = "INACTIVE_VERIFIED_DEVICE"),
@ -49,7 +51,8 @@ private val inactiveVerifiedDevice = DeviceFullInfo(
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
isInactive = true,
isCurrentDevice = false,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = MatrixClientInfoContent(),
)
private val activeUnverifiedDevice = DeviceFullInfo(
deviceInfo = DeviceInfo(deviceId = "ACTIVE_UNVERIFIED_DEVICE"),
@ -61,7 +64,8 @@ private val activeUnverifiedDevice = DeviceFullInfo(
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
isInactive = false,
isCurrentDevice = false,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = MatrixClientInfoContent(),
)
private val inactiveUnverifiedDevice = DeviceFullInfo(
deviceInfo = DeviceInfo(deviceId = "INACTIVE_UNVERIFIED_DEVICE"),
@ -73,7 +77,8 @@ private val inactiveUnverifiedDevice = DeviceFullInfo(
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
isInactive = true,
isCurrentDevice = false,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = MatrixClientInfoContent(),
)
private val devices = listOf(

View file

@ -18,9 +18,11 @@ package im.vector.app.features.settings.devices.v2.overview
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import im.vector.app.features.settings.devices.v2.DeviceExtendedInfo
import im.vector.app.core.session.clientinfo.GetMatrixClientInfoUseCase
import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.ParseDeviceUserAgentUseCase
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
import im.vector.app.features.settings.devices.v2.list.DeviceType
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
@ -57,6 +59,7 @@ class GetDeviceFullInfoUseCaseTest {
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
private val parseDeviceUserAgentUseCase = mockk<ParseDeviceUserAgentUseCase>()
private val getMatrixClientInfoUseCase = mockk<GetMatrixClientInfoUseCase>()
private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase(
activeSessionHolder = fakeActiveSessionHolder.instance,
@ -64,6 +67,7 @@ class GetDeviceFullInfoUseCaseTest {
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
parseDeviceUserAgentUseCase = parseDeviceUserAgentUseCase,
getMatrixClientInfoUseCase = getMatrixClientInfoUseCase,
)
@Before
@ -80,19 +84,14 @@ class GetDeviceFullInfoUseCaseTest {
fun `given current session and info for device when getting device info then the result is correct`() = runTest {
// Given
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
val deviceInfo = DeviceInfo(
lastSeenTs = A_TIMESTAMP,
)
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(deviceInfo))
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
val cryptoDeviceInfo = CryptoDeviceInfo(deviceId = A_DEVICE_ID, userId = "")
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(cryptoDeviceInfo))
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
val deviceInfo = givenADeviceInfo()
val cryptoDeviceInfo = givenACryptoDeviceInfo()
val trustLevel = givenTrustLevel(currentSessionCrossSigningInfo, cryptoDeviceInfo)
val isInactive = false
val isCurrentDevice = true
every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive
every { parseDeviceUserAgentUseCase.execute(any()) } returns DeviceExtendedInfo(DeviceType.MOBILE)
val matrixClientInfo = givenAMatrixClientInfo()
// When
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
@ -104,14 +103,18 @@ class GetDeviceFullInfoUseCaseTest {
roomEncryptionTrustLevel = trustLevel,
isInactive = isInactive,
isCurrentDevice = isCurrentDevice,
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE)
deviceExtendedInfo = DeviceExtendedInfo(DeviceType.MOBILE),
matrixClientInfo = matrixClientInfo,
)
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
verify { getCurrentSessionCrossSigningInfoUseCase.execute() }
verify { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) }
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
verify { checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP) }
verify {
fakeActiveSessionHolder.instance.getSafeActiveSession()
getCurrentSessionCrossSigningInfoUseCase.execute()
getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow()
fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow()
checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP)
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID)
}
}
@Test
@ -161,4 +164,27 @@ class GetDeviceFullInfoUseCaseTest {
every { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) } returns trustLevel
return trustLevel
}
private fun givenADeviceInfo(): DeviceInfo {
val deviceInfo = DeviceInfo(
deviceId = A_DEVICE_ID,
lastSeenTs = A_TIMESTAMP,
)
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(deviceInfo))
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
return deviceInfo
}
private fun givenACryptoDeviceInfo(): CryptoDeviceInfo {
val cryptoDeviceInfo = CryptoDeviceInfo(deviceId = A_DEVICE_ID, userId = "")
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(cryptoDeviceInfo))
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
return cryptoDeviceInfo
}
private fun givenAMatrixClientInfo(): MatrixClientInfoContent {
val matrixClientInfo = mockk<MatrixClientInfoContent>()
every { getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID) } returns matrixClientInfo
return matrixClientInfo
}
}