mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Refactor user agent parsing.
This commit is contained in:
parent
3e66a6538e
commit
04a305b403
@ -24,21 +24,13 @@ data class DeviceUserAgent(
|
||||
*/
|
||||
val deviceType: DeviceType,
|
||||
/**
|
||||
* i.e. Google
|
||||
*/
|
||||
val deviceManufacturer: String? = null,
|
||||
/**
|
||||
* i.e. Pixel 6
|
||||
* i.e. Google Pixel 6
|
||||
*/
|
||||
val deviceModel: String? = null,
|
||||
/**
|
||||
* i.e. Android
|
||||
*/
|
||||
val deviceOperatingSystem: String? = null,
|
||||
/**
|
||||
* i.e. Android 11
|
||||
*/
|
||||
val deviceOperatingSystemVersion: String? = null,
|
||||
val deviceOperatingSystem: String? = null,
|
||||
/**
|
||||
* i.e. Element Nightly
|
||||
*/
|
||||
|
@ -38,6 +38,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
||||
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||
private val filterDevicesUseCase: FilterDevicesUseCase,
|
||||
private val parseDeviceUserAgentUseCase: ParseDeviceUserAgentUseCase,
|
||||
) {
|
||||
|
||||
fun execute(filterType: DeviceManagerFilterType, excludeCurrentDevice: Boolean = false): Flow<List<DeviceFullInfo>> {
|
||||
@ -72,7 +73,8 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
||||
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
||||
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoDeviceInfo?.deviceId
|
||||
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive, isCurrentDevice)
|
||||
val deviceUserAgent = parseDeviceUserAgentUseCase.execute(deviceInfo.lastSeenUserAgent)
|
||||
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive, isCurrentDevice, deviceUserAgent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ import javax.inject.Inject
|
||||
|
||||
class ParseDeviceUserAgentUseCase @Inject constructor() {
|
||||
|
||||
fun execute(userAgent: String): DeviceUserAgent {
|
||||
fun execute(userAgent: String?): DeviceUserAgent {
|
||||
if (userAgent == null) return createUnknownUserAgent()
|
||||
|
||||
return when {
|
||||
userAgent.contains(ANDROID_KEYWORD) -> parseAndroidUserAgent(userAgent)
|
||||
userAgent.contains(IOS_KEYWORD) -> parseIosUserAgent(userAgent)
|
||||
@ -35,23 +37,26 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
|
||||
val appName = userAgent.substringBefore("/")
|
||||
val appVersion = userAgent.substringAfter("/").substringBefore(" (")
|
||||
val deviceInfoSegments = userAgent.substringAfter("(").substringBefore(")").split("; ")
|
||||
val deviceManufacturer = deviceInfoSegments.getOrNull(0)
|
||||
val deviceModel = deviceInfoSegments.getOrNull(1)
|
||||
val deviceOsInfo = deviceInfoSegments.getOrNull(2)?.takeIf { it.startsWith("Android") }
|
||||
val deviceOs = deviceOsInfo?.substringBefore(" ")
|
||||
val deviceOsVersion = deviceOsInfo?.substringAfter(" ")
|
||||
return DeviceUserAgent(DeviceType.MOBILE, deviceManufacturer, deviceModel, deviceOs, deviceOsVersion, appName, appVersion)
|
||||
val deviceModel: String?
|
||||
val deviceOperatingSystem: String?
|
||||
if (deviceInfoSegments.firstOrNull() == "Linux") {
|
||||
val deviceOperatingSystemIndex = deviceInfoSegments.indexOfFirst { it.startsWith("Android") }
|
||||
deviceOperatingSystem = deviceInfoSegments.getOrNull(deviceOperatingSystemIndex)
|
||||
deviceModel = deviceInfoSegments.getOrNull(deviceOperatingSystemIndex + 1)
|
||||
} else {
|
||||
deviceModel = deviceInfoSegments.getOrNull(0)
|
||||
deviceOperatingSystem = deviceInfoSegments.getOrNull(1)
|
||||
}
|
||||
return DeviceUserAgent(DeviceType.MOBILE, deviceModel, deviceOperatingSystem, appName, appVersion)
|
||||
}
|
||||
|
||||
private fun parseIosUserAgent(userAgent: String): DeviceUserAgent {
|
||||
val appName = userAgent.substringBefore("/")
|
||||
val appVersion = userAgent.substringAfter("/").substringBefore(" (")
|
||||
val deviceInfoSegments = userAgent.substringAfter("(").substringBefore(")").split("; ")
|
||||
val deviceManufacturer = "Apple"
|
||||
val deviceModel = deviceInfoSegments.getOrNull(0)
|
||||
val deviceOs = deviceInfoSegments.getOrNull(1)?.substringBefore(" ")
|
||||
val deviceOsVersion = deviceInfoSegments.getOrNull(1)?.substringAfter(" ")
|
||||
return DeviceUserAgent(DeviceType.MOBILE, deviceManufacturer, deviceModel, deviceOs, deviceOsVersion, appName, appVersion)
|
||||
val deviceOperatingSystem = deviceInfoSegments.getOrNull(1)
|
||||
return DeviceUserAgent(DeviceType.MOBILE, deviceModel, deviceOperatingSystem, appName, appVersion)
|
||||
}
|
||||
|
||||
private fun parseDesktopUserAgent(userAgent: String): DeviceUserAgent {
|
||||
@ -59,9 +64,8 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
|
||||
val appName = appInfoSegments.getOrNull(0)
|
||||
val appVersion = appInfoSegments.getOrNull(1)
|
||||
val deviceInfoSegments = userAgent.substringAfter("(").substringBefore(")").split("; ")
|
||||
val deviceOs = deviceInfoSegments.getOrNull(1)?.substringBeforeLast(" ")
|
||||
val deviceOsVersion = deviceInfoSegments.getOrNull(1)?.substringAfterLast(" ")
|
||||
return DeviceUserAgent(DeviceType.DESKTOP, null, null, deviceOs, deviceOsVersion, appName, appVersion)
|
||||
val deviceOperatingSystem = deviceInfoSegments.getOrNull(1)
|
||||
return DeviceUserAgent(DeviceType.DESKTOP, null, deviceOperatingSystem, appName, appVersion)
|
||||
}
|
||||
|
||||
private fun parseWebUserAgent(userAgent: String): DeviceUserAgent {
|
||||
@ -76,6 +80,7 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
|
||||
|
||||
companion object {
|
||||
// Element dbg/1.5.0-dev (Xiaomi; Mi 9T; Android 11; RKQ1.200826.002 test-keys; Flavour GooglePlay; MatrixAndroidSdk2 1.5.0)
|
||||
// Legacy : Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0)
|
||||
private val ANDROID_KEYWORD = "; MatrixAndroidSdk2"
|
||||
|
||||
// Element/1.8.21 (iPhone XS Max; iOS 15.2; Scale/3.00)
|
||||
|
@ -19,12 +19,14 @@ package im.vector.app.features.settings.devices.v2.overview
|
||||
import androidx.lifecycle.asFlow
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
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
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetEncryptionTrustLevelForDeviceUseCase
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import okhttp3.internal.userAgent
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.flow.unwrap
|
||||
import javax.inject.Inject
|
||||
@ -34,6 +36,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
|
||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||
private val parseDeviceUserAgentUseCase: ParseDeviceUserAgentUseCase,
|
||||
) {
|
||||
|
||||
fun execute(deviceId: String): Flow<DeviceFullInfo> {
|
||||
@ -49,12 +52,14 @@ class GetDeviceFullInfoUseCase @Inject constructor(
|
||||
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
|
||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
|
||||
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoInfo.deviceId
|
||||
val deviceUserAgent = parseDeviceUserAgentUseCase.execute(info.lastSeenUserAgent)
|
||||
DeviceFullInfo(
|
||||
deviceInfo = info,
|
||||
cryptoDeviceInfo = cryptoInfo,
|
||||
roomEncryptionTrustLevel = roomEncryptionTrustLevel,
|
||||
isInactive = isInactive,
|
||||
isCurrentDevice = isCurrentDevice,
|
||||
deviceUserAgent = deviceUserAgent,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2
|
||||
import android.os.SystemClock
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.test.MvRxTestRule
|
||||
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
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||
@ -36,6 +37,7 @@ import io.mockk.runs
|
||||
import io.mockk.unmockkAll
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import okhttp3.internal.userAgent
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -242,14 +244,16 @@ class DevicesViewModelTest {
|
||||
cryptoDeviceInfo = verifiedCryptoDeviceInfo,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = false,
|
||||
isCurrentDevice = true
|
||||
isCurrentDevice = true,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
val deviceFullInfo2 = DeviceFullInfo(
|
||||
deviceInfo = mockk(),
|
||||
cryptoDeviceInfo = unverifiedCryptoDeviceInfo,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = true,
|
||||
isCurrentDevice = false
|
||||
isCurrentDevice = false,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
||||
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
||||
|
@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2
|
||||
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
|
||||
import im.vector.app.features.settings.devices.v2.list.DeviceType
|
||||
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetEncryptionTrustLevelForDeviceUseCase
|
||||
@ -53,6 +54,7 @@ class GetDeviceFullInfoListUseCaseTest {
|
||||
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
||||
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
||||
private val filterDevicesUseCase = mockk<FilterDevicesUseCase>()
|
||||
private val parseDeviceUserAgentUseCase = mockk<ParseDeviceUserAgentUseCase>()
|
||||
|
||||
private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
@ -60,6 +62,7 @@ class GetDeviceFullInfoListUseCaseTest {
|
||||
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
||||
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
||||
filterDevicesUseCase = filterDevicesUseCase,
|
||||
parseDeviceUserAgentUseCase = parseDeviceUserAgentUseCase,
|
||||
)
|
||||
|
||||
@Before
|
||||
@ -110,21 +113,24 @@ class GetDeviceFullInfoListUseCaseTest {
|
||||
cryptoDeviceInfo = cryptoDeviceInfo1,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = true,
|
||||
isCurrentDevice = true
|
||||
isCurrentDevice = true,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
val expectedResult2 = DeviceFullInfo(
|
||||
deviceInfo = deviceInfo2,
|
||||
cryptoDeviceInfo = cryptoDeviceInfo2,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = false,
|
||||
isCurrentDevice = false
|
||||
isCurrentDevice = false,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
val expectedResult3 = DeviceFullInfo(
|
||||
deviceInfo = deviceInfo3,
|
||||
cryptoDeviceInfo = cryptoDeviceInfo3,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = false,
|
||||
isCurrentDevice = false
|
||||
isCurrentDevice = false,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
|
||||
every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult
|
||||
|
@ -17,6 +17,8 @@
|
||||
package im.vector.app.features.settings.devices.v2.filter
|
||||
|
||||
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.DeviceUserAgent
|
||||
import im.vector.app.features.settings.devices.v2.list.DeviceType
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.amshove.kluent.shouldContainAll
|
||||
import org.junit.Test
|
||||
@ -34,7 +36,8 @@ private val activeVerifiedDevice = DeviceFullInfo(
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = false,
|
||||
isCurrentDevice = true
|
||||
isCurrentDevice = true,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
private val inactiveVerifiedDevice = DeviceFullInfo(
|
||||
deviceInfo = DeviceInfo(deviceId = "INACTIVE_VERIFIED_DEVICE"),
|
||||
@ -45,7 +48,8 @@ private val inactiveVerifiedDevice = DeviceFullInfo(
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = true,
|
||||
isCurrentDevice = false
|
||||
isCurrentDevice = false,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
private val activeUnverifiedDevice = DeviceFullInfo(
|
||||
deviceInfo = DeviceInfo(deviceId = "ACTIVE_UNVERIFIED_DEVICE"),
|
||||
@ -56,7 +60,8 @@ private val activeUnverifiedDevice = DeviceFullInfo(
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = false,
|
||||
isCurrentDevice = false
|
||||
isCurrentDevice = false,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
private val inactiveUnverifiedDevice = DeviceFullInfo(
|
||||
deviceInfo = DeviceInfo(deviceId = "INACTIVE_UNVERIFIED_DEVICE"),
|
||||
@ -67,7 +72,8 @@ private val inactiveUnverifiedDevice = DeviceFullInfo(
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = true,
|
||||
isCurrentDevice = false
|
||||
isCurrentDevice = false,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
|
||||
private val devices = listOf(
|
||||
|
@ -19,7 +19,10 @@ 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.DeviceFullInfo
|
||||
import im.vector.app.features.settings.devices.v2.DeviceUserAgent
|
||||
import im.vector.app.features.settings.devices.v2.ParseDeviceUserAgentUseCase
|
||||
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
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetEncryptionTrustLevelForDeviceUseCase
|
||||
@ -53,12 +56,14 @@ class GetDeviceFullInfoUseCaseTest {
|
||||
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
||||
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
|
||||
private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
|
||||
private val parseDeviceUserAgentUseCase = mockk<ParseDeviceUserAgentUseCase>()
|
||||
|
||||
private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase(
|
||||
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
||||
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
||||
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
|
||||
parseDeviceUserAgentUseCase = parseDeviceUserAgentUseCase,
|
||||
)
|
||||
|
||||
@Before
|
||||
@ -97,7 +102,8 @@ class GetDeviceFullInfoUseCaseTest {
|
||||
cryptoDeviceInfo = cryptoDeviceInfo,
|
||||
roomEncryptionTrustLevel = trustLevel,
|
||||
isInactive = isInactive,
|
||||
isCurrentDevice = isCurrentDevice
|
||||
isCurrentDevice = isCurrentDevice,
|
||||
deviceUserAgent = DeviceUserAgent(DeviceType.MOBILE)
|
||||
)
|
||||
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
|
||||
verify { getCurrentSessionCrossSigningInfoUseCase.execute() }
|
||||
|
Loading…
Reference in New Issue
Block a user