From 5cac68d7313ed52c7dea86ad5921ef476725e215 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Fri, 30 Sep 2022 16:31:23 +0200 Subject: [PATCH 01/12] Adding unit tests for the get client info use case --- .../GetMatrixClientInfoUseCaseTest.kt | 78 +++++++++++++++++++ .../im/vector/app/test/fakes/FakeSession.kt | 1 - 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt new file mode 100644 index 0000000000..4a90f90c13 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt @@ -0,0 +1,78 @@ +/* + * 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.extended + +import MATRIX_CLIENT_INFO_KEY_PREFIX +import im.vector.app.test.fakes.FakeActiveSessionHolder +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +private const val A_DEVICE_ID = "device-id" +private const val A_CLIENT_NAME = "client-name" +private const val A_CLIENT_VERSION = "client-version" +private const val A_CLIENT_URL = "client-url" + +class GetMatrixClientInfoUseCaseTest { + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + + private val getMatrixClientInfoUseCase = GetMatrixClientInfoUseCase( + activeSessionHolder = fakeActiveSessionHolder.instance + ) + + @Test + fun `given a device id and existing content when getting the info then result should contain that info`() { + // Given + givenClientInfoContent(A_DEVICE_ID) + val expectedClientInfo = MatrixClientInfoContent( + name = A_CLIENT_NAME, + version = A_CLIENT_VERSION, + url = A_CLIENT_URL, + ) + + // When + val result = getMatrixClientInfoUseCase.execute(A_DEVICE_ID) + + // Then + result shouldBeEqualTo expectedClientInfo + } + + @Test + fun `given no active session when getting the info then result should be null`() { + // Given + fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null) + + // When + val result = getMatrixClientInfoUseCase.execute(A_DEVICE_ID) + + // Then + result shouldBe null + } + + private fun givenClientInfoContent(deviceId: String) { + val type = MATRIX_CLIENT_INFO_KEY_PREFIX + deviceId + val content = mapOf( + Pair("name", A_CLIENT_NAME), + Pair("version", A_CLIENT_VERSION), + Pair("url", A_CLIENT_URL), + ) + fakeActiveSessionHolder.fakeSession + .fakeSessionAccountDataService + .givenGetUserAccountDataEventReturns(type, content) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index c40e4a8fc4..4dfd44025a 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -61,7 +61,6 @@ class FakeSession( override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService override fun eventService() = fakeEventService - override fun pushersService() = fakePushersService override fun accountDataService() = fakeSessionAccountDataService override fun filterService() = fakeFilterService From a640c771410f587c39bc49ccca86397542aeee1a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 5 Oct 2022 11:43:58 +0200 Subject: [PATCH 02/12] Adding changelog entry --- changelog.d/7294.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7294.wip diff --git a/changelog.d/7294.wip b/changelog.d/7294.wip new file mode 100644 index 0000000000..f163f6b680 --- /dev/null +++ b/changelog.d/7294.wip @@ -0,0 +1 @@ +[Device Management] Render extended device info From 93fe22d18ee3ebc6dd1201781e7e2ae955c60cf7 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 5 Oct 2022 11:48:51 +0200 Subject: [PATCH 03/12] Fixing hidden exception in unit tests of DevicesViewModelTest --- .../app/features/settings/devices/v2/DevicesViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt index c68394e7d7..4017d47bba 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt @@ -53,7 +53,7 @@ class DevicesViewModelTest { private val fakeActiveSessionHolder = FakeActiveSessionHolder() private val getCurrentSessionCrossSigningInfoUseCase = mockk() private val getDeviceFullInfoListUseCase = mockk() - private val refreshDevicesUseCase = mockk() + private val refreshDevicesUseCase = mockk(relaxUnitFun = true) private val refreshDevicesOnCryptoDevicesChangeUseCase = mockk() private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk() From acd05a0233158ca7197087cb6c62aeea70357315 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 5 Oct 2022 17:02:57 +0200 Subject: [PATCH 04/12] Exposing the matrix client info into the DeviceFullInfo --- .../settings/devices/v2/DeviceFullInfo.kt | 3 + .../v2/GetDeviceFullInfoListUseCase.kt | 22 ++++++- .../devices/v2/ParseDeviceUserAgentUseCase.kt | 1 + .../extended}/DeviceExtendedInfo.kt | 2 +- .../v2/overview/GetDeviceFullInfoUseCase.kt | 7 +++ .../devices/v2/DevicesViewModelTest.kt | 8 ++- .../v2/GetDeviceFullInfoListUseCaseTest.kt | 26 ++++++++- .../v2/ParseDeviceUserAgentUseCaseTest.kt | 1 + .../v2/filter/FilterDevicesUseCaseTest.kt | 15 +++-- .../overview/GetDeviceFullInfoUseCaseTest.kt | 58 ++++++++++++++----- 10 files changed, 113 insertions(+), 30 deletions(-) rename vector/src/main/java/im/vector/app/features/settings/devices/v2/{ => details/extended}/DeviceExtendedInfo.kt (94%) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt index 445eb6226f..a47ea7e917 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt @@ -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?, ) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt index 0272bea351..42e4cebe4c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt @@ -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> { @@ -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, infoList: List, @@ -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 + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt index f5f1782d82..ef4391c8c1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceExtendedInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/extended/DeviceExtendedInfo.kt similarity index 94% rename from vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceExtendedInfo.kt rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/details/extended/DeviceExtendedInfo.kt index 24e4606ca7..5158ffbb8a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceExtendedInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/extended/DeviceExtendedInfo.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt index 42cd49b072..140b55a30c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt @@ -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 { @@ -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 diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt index 4017d47bba..c5edfb868d 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt @@ -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 @@ -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) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt index efeb7f91b8..ebdb74b74d 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt @@ -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() private val filterDevicesUseCase = mockk() private val parseDeviceUserAgentUseCase = mockk() + private val getMatrixClientInfoUseCase = mockk() 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() + every { getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, deviceId) } returns matrixClientInfo + return matrixClientInfo + } } diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt index 22a5a7614f..60b9f70e56 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt @@ -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 diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt index 3448c7324d..f695030079 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt @@ -16,7 +16,8 @@ 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.details.extended.DeviceExtendedInfo import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.list.DeviceType import org.amshove.kluent.shouldBeEqualTo @@ -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( diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt index a77f8e81fd..2185c295d0 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt @@ -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() private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions() private val parseDeviceUserAgentUseCase = mockk() + private val getMatrixClientInfoUseCase = mockk() 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() + every { getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID) } returns matrixClientInfo + return matrixClientInfo + } } From 9f9f6e14bed448ba4576542c55a6733938c5a8a8 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 5 Oct 2022 17:49:57 +0200 Subject: [PATCH 05/12] Rendering Application section into session details --- .../src/main/res/values/strings.xml | 4 + ...eckIfSectionApplicationIsVisibleUseCase.kt | 31 ++++++++ .../v2/details/SessionDetailsController.kt | 77 ++++++++++++++----- .../v2/details/SessionDetailsFragment.kt | 9 ++- .../v2/details/SessionDetailsViewModel.kt | 2 +- .../v2/details/SessionDetailsViewState.kt | 4 +- .../v2/details/SessionDetailsViewModelTest.kt | 4 +- 7 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 63c1f8a8bb..0b17b64c4a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3309,6 +3309,10 @@ Session name Session ID Last activity + Application + Name + Version + URL IP address Rename session Session name diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt new file mode 100644 index 0000000000..cd93cb73db --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt @@ -0,0 +1,31 @@ +/* + * 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() { + + // TODO add unit tests + fun execute(matrixClientInfoContent: MatrixClientInfoContent?): Boolean { + return matrixClientInfoContent?.name?.isNotEmpty().orFalse() || + matrixClientInfoContent?.version?.isNotEmpty().orFalse() || + matrixClientInfoContent?.url?.isNotEmpty().orFalse() + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt index 1fb5be4d78..eb4f823889 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt @@ -23,17 +23,20 @@ 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 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() { +) : TypedEpoxyController() { var callback: Callback? = null @@ -41,15 +44,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(deviceInfo)) { + buildSectionDevice(deviceInfo, addExtraTopMargin = hasSectionSession || hasApplicationSection) } } } @@ -83,39 +93,64 @@ 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 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: DeviceInfo): Boolean { return checkIfSectionDeviceIsVisibleUseCase.execute(data) } private fun buildSectionDevice(data: DeviceInfo, addExtraTopMargin: Boolean) { - val lastSeenIp = data.lastSeenIp + val lastSeenIp = data.lastSeenIp.orEmpty() buildHeaderItem(R.string.device_manager_device_title, addExtraTopMargin) - lastSeenIp?.let { + 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) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt index 5d7717e5f7..f518ab9382 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt @@ -33,6 +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 im.vector.app.features.settings.devices.v2.DeviceFullInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject @@ -92,16 +93,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() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt index c37858cc54..44e10701a0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModel.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt index 15868d3110..d216bbda51 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewState.kt @@ -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 = Uninitialized, + val deviceFullInfo: Async = Uninitialized, ) : MavericksState { constructor(args: SessionDetailsArgs) : this( deviceId = args.deviceId diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt index 572f39af31..5fdd219226 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt @@ -57,12 +57,10 @@ class SessionDetailsViewModelTest { fun `given the viewModel has been initialized then viewState is updated with session info`() { // Given val deviceFullInfo = mockk() - val deviceInfo = mockk() - 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 From 25a3d831f1689c33fb7740d0ec173e9cbbbaf6c6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 6 Oct 2022 10:08:54 +0200 Subject: [PATCH 06/12] Adding unit tests for application section visibility use case --- ...eckIfSectionApplicationIsVisibleUseCase.kt | 1 - ...fSectionApplicationIsVisibleUseCaseTest.kt | 145 ++++++++++++++++++ ...heckIfSectionDeviceIsVisibleUseCaseTest.kt | 5 +- ...eckIfSectionSessionIsVisibleUseCaseTest.kt | 5 +- 4 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt index cd93cb73db..0c790c2ef0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCase.kt @@ -22,7 +22,6 @@ import javax.inject.Inject class CheckIfSectionApplicationIsVisibleUseCase @Inject constructor() { - // TODO add unit tests fun execute(matrixClientInfoContent: MatrixClientInfoContent?): Boolean { return matrixClientInfoContent?.name?.isNotEmpty().orFalse() || matrixClientInfoContent?.version?.isNotEmpty().orFalse() || diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCaseTest.kt new file mode 100644 index 0000000000..23b02088b0 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionApplicationIsVisibleUseCaseTest.kt @@ -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, + ) +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt index b618c58b7e..e5cd5f1647 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt @@ -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 @@ -30,7 +29,7 @@ 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 { + fun `given device info with Ip address when checking is device section is visible then it returns true`() { // Given val deviceInfo = givenADeviceInfo(AN_IP_ADDRESS) @@ -42,7 +41,7 @@ class CheckIfSectionDeviceIsVisibleUseCaseTest { } @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 info with empty or null Ip address when checking is device section is visible then it returns false`() { // Given val deviceInfo1 = givenADeviceInfo("") val deviceInfo2 = givenADeviceInfo(null) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt index 806c86d175..a6d29f4fe1 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCaseTest.kt @@ -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( From fdb61e26ee2d7aa293765598373d70bb986931f1 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 6 Oct 2022 14:04:17 +0200 Subject: [PATCH 07/12] Rendering Device section with extended info --- .../src/main/res/values/strings.xml | 3 + .../CheckIfSectionDeviceIsVisibleUseCase.kt | 29 ++++- .../v2/details/SessionDetailsController.kt | 75 ++++++++++- ...heckIfSectionDeviceIsVisibleUseCaseTest.kt | 121 +++++++++++++++--- 4 files changed, 203 insertions(+), 25 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 0b17b64c4a..61043a80e3 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3313,6 +3313,9 @@ Name Version URL + Browser + Model + Operating system IP address Rename session Session name diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt index 25b5ddb0e8..471ef05c8d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt @@ -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() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt index eb4f823889..8d780925df 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt @@ -26,6 +26,7 @@ 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 @@ -58,8 +59,8 @@ class SessionDetailsController @Inject constructor( buildSectionApplication(matrixClientInfo, addExtraTopMargin = hasSectionSession) } - if (hasSectionDevice(deviceInfo)) { - buildSectionDevice(deviceInfo, addExtraTopMargin = hasSectionSession || hasApplicationSection) + if (hasSectionDevice(fullInfo)) { + buildSectionDevice(fullInfo, addExtraTopMargin = hasSectionSession || hasApplicationSection) } } } @@ -139,15 +140,77 @@ class SessionDetailsController @Inject constructor( } } - private fun hasSectionDevice(data: DeviceInfo): Boolean { + private fun hasSectionDevice(data: DeviceFullInfo): Boolean { return checkIfSectionDeviceIsVisibleUseCase.execute(data) } - private fun buildSectionDevice(data: DeviceInfo, addExtraTopMargin: Boolean) { - val lastSeenIp = data.lastSeenIp.orEmpty() - + private fun buildSectionDevice(data: DeviceFullInfo, addExtraTopMargin: Boolean) { buildHeaderItem(R.string.device_manager_device_title, addExtraTopMargin) + 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, lastSeenIp, hasDivider) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt index e5cd5f1647..d124f68b1b 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCaseTest.kt @@ -16,6 +16,9 @@ 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 org.amshove.kluent.shouldBeEqualTo @@ -23,36 +26,106 @@ 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`() { - // 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`() { + 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() + every { deviceFullInfo.deviceExtendedInfo } returns deviceExtendedInfo + return deviceFullInfo } private fun givenADeviceInfo(ipAddress: String?): DeviceInfo { @@ -60,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, + ) + } } From a6289d19f434543ce6d2c37b3b8eb493846dadd2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 6 Oct 2022 14:07:39 +0200 Subject: [PATCH 08/12] Making const for client info event prefix as internal --- .../app/core/session/clientinfo/SessionExtendedInfoConstants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/session/clientinfo/SessionExtendedInfoConstants.kt b/vector/src/main/java/im/vector/app/core/session/clientinfo/SessionExtendedInfoConstants.kt index 80f69df1f8..663a715e02 100644 --- a/vector/src/main/java/im/vector/app/core/session/clientinfo/SessionExtendedInfoConstants.kt +++ b/vector/src/main/java/im/vector/app/core/session/clientinfo/SessionExtendedInfoConstants.kt @@ -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." From a5bcbf300be16c0c0326dbebf45db239dd1fa6a3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 6 Oct 2022 14:25:40 +0200 Subject: [PATCH 09/12] Parsing the full version of browsers for Web device type --- .../devices/v2/ParseDeviceUserAgentUseCase.kt | 5 ++--- .../v2/ParseDeviceUserAgentUseCaseTest.kt | 22 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt index ef4391c8c1..17e66815f3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt @@ -75,6 +75,7 @@ class ParseDeviceUserAgentUseCase @Inject constructor() { private fun parseDesktopUserAgent(userAgent: String): DeviceExtendedInfo { val browserSegments = userAgent.split(" ") + // TODO parse the whole version of browser val (browserName, browserVersion) = when { isFirefox(browserSegments) -> { Pair("Firefox", getBrowserVersion(browserSegments, "Firefox")) @@ -140,13 +141,11 @@ class ParseDeviceUserAgentUseCase @Inject constructor() { } private fun getBrowserVersion(browserSegments: List, 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): Boolean { diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt index 60b9f70e56..19caee1db7 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCaseTest.kt @@ -62,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( @@ -78,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( From ef13f6033c4410b051b539dcc43c19fbd07d1985 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 6 Oct 2022 14:28:37 +0200 Subject: [PATCH 10/12] Fixing coding style issues --- .../settings/devices/v2/details/SessionDetailsFragment.kt | 1 - .../settings/devices/v2/filter/FilterDevicesUseCaseTest.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt index f518ab9382..c5296929a6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt @@ -34,7 +34,6 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.databinding.FragmentSessionDetailsBinding import im.vector.app.features.settings.devices.v2.DeviceFullInfo -import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject /** diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt index f695030079..3a418cf2c0 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt @@ -17,8 +17,8 @@ package im.vector.app.features.settings.devices.v2.filter 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.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 From 0ec4ccf5da701bbfb68a1f9b473ec87d7586cfa9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 6 Oct 2022 14:35:50 +0200 Subject: [PATCH 11/12] Removing a completed TODO --- .../features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt index 17e66815f3..20b76fd814 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/ParseDeviceUserAgentUseCase.kt @@ -75,7 +75,6 @@ class ParseDeviceUserAgentUseCase @Inject constructor() { private fun parseDesktopUserAgent(userAgent: String): DeviceExtendedInfo { val browserSegments = userAgent.split(" ") - // TODO parse the whole version of browser val (browserName, browserVersion) = when { isFirefox(browserSegments) -> { Pair("Firefox", getBrowserVersion(browserSegments, "Firefox")) From b7190c2bfe7a93f40c504317cc6a85e81cc110af Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 11 Oct 2022 14:39:40 +0200 Subject: [PATCH 12/12] Fix after rebase --- .../GetMatrixClientInfoUseCaseTest.kt | 78 ------------------- .../im/vector/app/test/fakes/FakeSession.kt | 1 + 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt deleted file mode 100644 index 4a90f90c13..0000000000 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/extended/GetMatrixClientInfoUseCaseTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.extended - -import MATRIX_CLIENT_INFO_KEY_PREFIX -import im.vector.app.test.fakes.FakeActiveSessionHolder -import org.amshove.kluent.shouldBe -import org.amshove.kluent.shouldBeEqualTo -import org.junit.Test - -private const val A_DEVICE_ID = "device-id" -private const val A_CLIENT_NAME = "client-name" -private const val A_CLIENT_VERSION = "client-version" -private const val A_CLIENT_URL = "client-url" - -class GetMatrixClientInfoUseCaseTest { - - private val fakeActiveSessionHolder = FakeActiveSessionHolder() - - private val getMatrixClientInfoUseCase = GetMatrixClientInfoUseCase( - activeSessionHolder = fakeActiveSessionHolder.instance - ) - - @Test - fun `given a device id and existing content when getting the info then result should contain that info`() { - // Given - givenClientInfoContent(A_DEVICE_ID) - val expectedClientInfo = MatrixClientInfoContent( - name = A_CLIENT_NAME, - version = A_CLIENT_VERSION, - url = A_CLIENT_URL, - ) - - // When - val result = getMatrixClientInfoUseCase.execute(A_DEVICE_ID) - - // Then - result shouldBeEqualTo expectedClientInfo - } - - @Test - fun `given no active session when getting the info then result should be null`() { - // Given - fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null) - - // When - val result = getMatrixClientInfoUseCase.execute(A_DEVICE_ID) - - // Then - result shouldBe null - } - - private fun givenClientInfoContent(deviceId: String) { - val type = MATRIX_CLIENT_INFO_KEY_PREFIX + deviceId - val content = mapOf( - Pair("name", A_CLIENT_NAME), - Pair("version", A_CLIENT_VERSION), - Pair("url", A_CLIENT_URL), - ) - fakeActiveSessionHolder.fakeSession - .fakeSessionAccountDataService - .givenGetUserAccountDataEventReturns(type, content) - } -} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index 4dfd44025a..c40e4a8fc4 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -61,6 +61,7 @@ class FakeSession( override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService override fun eventService() = fakeEventService + override fun pushersService() = fakePushersService override fun accountDataService() = fakeSessionAccountDataService override fun filterService() = fakeFilterService