Merge pull request #7005 from vector-im/feature/mna/session-overview-screen
[Devices Management] Session overview screen (PSG-691, PSG-693)
This commit is contained in:
commit
6c2bf35d60
1
changelog.d/6961.wip
Normal file
1
changelog.d/6961.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Devices Management] Session overview screen
|
@ -3227,13 +3227,22 @@
|
|||||||
<string name="a11y_device_manager_device_type_unknown">Unknown device type</string>
|
<string name="a11y_device_manager_device_type_unknown">Unknown device type</string>
|
||||||
<string name="device_manager_verification_status_verified">Verified session</string>
|
<string name="device_manager_verification_status_verified">Verified session</string>
|
||||||
<string name="device_manager_verification_status_unverified">Unverified session</string>
|
<string name="device_manager_verification_status_unverified">Unverified session</string>
|
||||||
<string name="device_manager_verification_status_detail_verified">Your current session is ready for secure messaging.</string>
|
<!-- TODO TO BE REMOVED: replaced by device_manager_verification_status_detail_current_session_verified -->
|
||||||
<string name="device_manager_verification_status_detail_unverified">Verify your current session for enhanced secure messaging.</string>
|
<string name="device_manager_verification_status_detail_verified" tools:ignore="UnusedResources">Your current session is ready for secure messaging.</string>
|
||||||
|
<!-- TODO TO BE REMOVED: replaced by device_manager_verification_status_detail_current_session_unverified -->
|
||||||
|
<string name="device_manager_verification_status_detail_unverified" tools:ignore="UnusedResources">Verify your current session for enhanced secure messaging.</string>
|
||||||
|
<string name="device_manager_verification_status_detail_current_session_verified">Your current session is ready for secure messaging.</string>
|
||||||
|
<string name="device_manager_verification_status_detail_other_session_verified">This session is ready for secure messaging.</string>
|
||||||
|
<string name="device_manager_verification_status_detail_current_session_unverified">Verify your current session for enhanced secure messaging.</string>
|
||||||
|
<string name="device_manager_verification_status_detail_other_session_unverified">Verify or sign out from this session for best security and reliability.</string>
|
||||||
<string name="device_manager_verify_session">Verify Session</string>
|
<string name="device_manager_verify_session">Verify Session</string>
|
||||||
<string name="device_manager_view_details">View Details</string>
|
<string name="device_manager_view_details">View Details</string>
|
||||||
<string name="device_manager_header_section_current_session">Current Session</string>
|
<!-- TODO TO BE REMOVED: replaced by device_manager_current_session_title -->
|
||||||
|
<string name="device_manager_header_section_current_session" tools:ignore="UnusedResources">Current Session</string>
|
||||||
<string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
|
<string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
|
||||||
|
<!-- Examples: Verified · Last activity Yesterday at 6PM, Verified · Last activity Aug 31 at 5:47PM -->
|
||||||
<string name="device_manager_other_sessions_description_verified">Verified · Last activity %1$s</string>
|
<string name="device_manager_other_sessions_description_verified">Verified · Last activity %1$s</string>
|
||||||
|
<!-- Examples: Unverified · Last activity Yesterday at 6PM, Unverified · Last activity Aug 31 at 5:47PM -->
|
||||||
<string name="device_manager_other_sessions_description_unverified">Unverified · Last activity %1$s</string>
|
<string name="device_manager_other_sessions_description_unverified">Unverified · Last activity %1$s</string>
|
||||||
<!-- Example: Inactive for 90+ days (Dec 25, 2021) -->
|
<!-- Example: Inactive for 90+ days (Dec 25, 2021) -->
|
||||||
<plurals name="device_manager_other_sessions_description_inactive">
|
<plurals name="device_manager_other_sessions_description_inactive">
|
||||||
@ -3249,6 +3258,10 @@
|
|||||||
<item quantity="one">Consider signing out from old sessions (%1$d day or more) that you don’t use anymore.</item>
|
<item quantity="one">Consider signing out from old sessions (%1$d day or more) that you don’t use anymore.</item>
|
||||||
<item quantity="other">Consider signing out from old sessions (%1$d days or more) that you don’t use anymore.</item>
|
<item quantity="other">Consider signing out from old sessions (%1$d days or more) that you don’t use anymore.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="device_manager_current_session_title">Current Session</string>
|
||||||
|
<string name="device_manager_session_title">Session</string>
|
||||||
|
<!-- Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
|
||||||
|
<string name="device_manager_session_last_activity">Last activity %1$s</string>
|
||||||
|
|
||||||
<!-- Note to translators: %s will be replaces with selected space name -->
|
<!-- Note to translators: %s will be replaces with selected space name -->
|
||||||
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
|
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<declare-styleable name="DevicesListHeaderView">
|
<declare-styleable name="SessionsListHeaderView">
|
||||||
<attr name="devicesListHeaderTitle" format="string" />
|
<attr name="devicesListHeaderTitle" format="string" />
|
||||||
<attr name="devicesListHeaderDescription" format="string" />
|
<attr name="devicesListHeaderDescription" format="string" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<style name="TextAppearance.Vector.Body.DevicesManagement">
|
<style name="TextAppearance.Vector.Body.DevicesManagement">
|
||||||
<item name="android:textColor">?vctr_content_secondary</item>
|
<item name="android:textColor">?vctr_content_secondary</item>
|
||||||
|
<item name="android:drawablePadding">12dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -72,7 +72,7 @@ class FlowSession(private val session: Session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun liveMyDevicesInfo(): Flow<List<DeviceInfo>> {
|
fun liveMyDevicesInfo(): Flow<List<DeviceInfo>> {
|
||||||
return session.cryptoService().getLiveMyDevicesInfo().asFlow()
|
return session.cryptoService().getMyDevicesInfoLive().asFlow()
|
||||||
.startWith(session.coroutineDispatchers.io) {
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
session.cryptoService().getMyDevicesInfo()
|
session.cryptoService().getMyDevicesInfo()
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
|||||||
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.util.time.DefaultClock
|
import org.matrix.android.sdk.internal.util.time.DefaultClock
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -37,6 +38,7 @@ internal class CryptoStoreHelper {
|
|||||||
userId = "userId_" + Random.nextInt(),
|
userId = "userId_" + Random.nextInt(),
|
||||||
deviceId = "deviceId_sample",
|
deviceId = "deviceId_sample",
|
||||||
clock = DefaultClock(),
|
clock = DefaultClock(),
|
||||||
|
myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -676,8 +676,8 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
assertEquals("Decimal code should have matched", oldCode, newCode)
|
assertEquals("Decimal code should have matched", oldCode, newCode)
|
||||||
|
|
||||||
// Assert that devices are verified
|
// Assert that devices are verified
|
||||||
val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||||
val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||||
|
|
||||||
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
|
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
|
||||||
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
|
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
|
||||||
|
@ -193,7 +193,7 @@ class XSigningTest : InstrumentedTest {
|
|||||||
fail("Bob should see the new device")
|
fail("Bob should see the new device")
|
||||||
}
|
}
|
||||||
|
|
||||||
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
|
val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId)
|
||||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||||
|
|
||||||
// Manually mark it as trusted from first session
|
// Manually mark it as trusted from first session
|
||||||
|
@ -521,9 +521,9 @@ class SASTest : InstrumentedTest {
|
|||||||
testHelper.await(bobSASLatch)
|
testHelper.await(bobSASLatch)
|
||||||
|
|
||||||
// Assert that devices are verified
|
// Assert that devices are verified
|
||||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
|
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
|
||||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
|
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
|
||||||
bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
||||||
|
|
||||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||||
|
@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
|
|||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
||||||
|
|
||||||
interface CryptoService {
|
interface CryptoService {
|
||||||
@ -113,7 +114,19 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
fun setRoomBlacklistUnverifiedDevices(roomId: String)
|
||||||
|
|
||||||
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||||
|
|
||||||
|
fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||||
|
|
||||||
|
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
fun requestRoomKeyForEvent(event: Event)
|
fun requestRoomKeyForEvent(event: Event)
|
||||||
|
|
||||||
@ -127,9 +140,9 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun getMyDevicesInfo(): List<DeviceInfo>
|
fun getMyDevicesInfo(): List<DeviceInfo>
|
||||||
|
|
||||||
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
|
fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>>
|
||||||
|
|
||||||
fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>>
|
||||||
|
|
||||||
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
|
||||||
|
|
||||||
@ -156,14 +169,6 @@ interface CryptoService {
|
|||||||
|
|
||||||
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
|
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
|
||||||
|
|
||||||
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
|
||||||
|
|
||||||
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
|
|
||||||
|
|
||||||
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
|
|
||||||
|
|
||||||
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
|
||||||
|
|
||||||
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
fun addNewSessionListener(newSessionListener: NewSessionListener)
|
||||||
fun removeSessionListener(listener: NewSessionListener)
|
fun removeSessionListener(listener: NewSessionListener)
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityConten
|
|||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
||||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
|
||||||
@ -273,23 +274,18 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
|
override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
|
||||||
return cryptoStore.getLiveMyDevicesInfo()
|
return cryptoStore.getLiveMyDevicesInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
|
||||||
|
return cryptoStore.getLiveMyDevicesInfo(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMyDevicesInfo(): List<DeviceInfo> {
|
override fun getMyDevicesInfo(): List<DeviceInfo> {
|
||||||
return cryptoStore.getMyDevicesInfo()
|
return cryptoStore.getMyDevicesInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
|
||||||
getDeviceInfoTask
|
|
||||||
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
|
||||||
this.executionThread = TaskThread.CRYPTO
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
}
|
}
|
||||||
@ -513,7 +509,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
*/
|
*/
|
||||||
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
||||||
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||||
cryptoStore.getUserDevice(userId, deviceId)
|
cryptoStore.getUserDevice(userId, deviceId)
|
||||||
} else {
|
} else {
|
||||||
@ -521,6 +517,15 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||||
|
getDeviceInfoTask
|
||||||
|
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||||
|
this.executionThread = TaskThread.CRYPTO
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
||||||
}
|
}
|
||||||
@ -529,6 +534,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return cryptoStore.getLiveDeviceList()
|
return cryptoStore.getLiveDeviceList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> {
|
||||||
|
return cryptoStore.getLiveDeviceWithId(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
|
||||||
return cryptoStore.getLiveDeviceList(userId)
|
return cryptoStore.getLiveDeviceList(userId)
|
||||||
}
|
}
|
||||||
|
@ -238,10 +238,14 @@ internal interface IMXCryptoStore {
|
|||||||
// TODO temp
|
// TODO temp
|
||||||
fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>>
|
fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
|
||||||
|
|
||||||
fun getMyDevicesInfo(): List<DeviceInfo>
|
fun getMyDevicesInfo(): List<DeviceInfo>
|
||||||
|
|
||||||
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
|
fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
|
||||||
|
|
||||||
|
fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>>
|
||||||
|
|
||||||
fun saveMyDevicesInfo(info: List<DeviceInfo>)
|
fun saveMyDevicesInfo(info: List<DeviceInfo>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
|||||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
|
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailMapper
|
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailMapper
|
||||||
@ -68,6 +69,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
|
|||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
|
||||||
@ -112,6 +114,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@DeviceId private val deviceId: String?,
|
@DeviceId private val deviceId: String?,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
|
private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper,
|
||||||
) : IMXCryptoStore {
|
) : IMXCryptoStore {
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
@ -578,6 +581,12 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> {
|
||||||
|
return Transformations.map(getLiveDeviceList()) { devices ->
|
||||||
|
devices.firstOrNull { it.deviceId == deviceId }.toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMyDevicesInfo(): List<DeviceInfo> {
|
override fun getMyDevicesInfo(): List<DeviceInfo> {
|
||||||
return monarchy.fetchAllCopiedSync {
|
return monarchy.fetchAllCopiedSync {
|
||||||
it.where<MyDeviceLastSeenInfoEntity>()
|
it.where<MyDeviceLastSeenInfoEntity>()
|
||||||
@ -596,17 +605,24 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
{ realm: Realm ->
|
{ realm: Realm ->
|
||||||
realm.where<MyDeviceLastSeenInfoEntity>()
|
realm.where<MyDeviceLastSeenInfoEntity>()
|
||||||
},
|
},
|
||||||
{ entity ->
|
{ entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
|
||||||
DeviceInfo(
|
|
||||||
deviceId = entity.deviceId,
|
|
||||||
lastSeenIp = entity.lastSeenIp,
|
|
||||||
lastSeenTs = entity.lastSeenTs,
|
|
||||||
displayName = entity.displayName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>> {
|
||||||
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm: Realm ->
|
||||||
|
realm.where<MyDeviceLastSeenInfoEntity>()
|
||||||
|
.equalTo(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, deviceId)
|
||||||
|
},
|
||||||
|
{ entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
|
||||||
|
)
|
||||||
|
|
||||||
|
return Transformations.map(liveData) {
|
||||||
|
it.firstOrNull().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun saveMyDevicesInfo(info: List<DeviceInfo>) {
|
override fun saveMyDevicesInfo(info: List<DeviceInfo>) {
|
||||||
val entities = info.map {
|
val entities = info.map {
|
||||||
MyDeviceLastSeenInfoEntity(
|
MyDeviceLastSeenInfoEntity(
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.crypto.store.db.mapper
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() {
|
||||||
|
|
||||||
|
fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo {
|
||||||
|
return DeviceInfo(
|
||||||
|
deviceId = entity.deviceId,
|
||||||
|
lastSeenIp = entity.lastSeenIp,
|
||||||
|
lastSeenTs = entity.lastSeenTs,
|
||||||
|
displayName = entity.displayName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.crypto.store.db.mapper
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
|
||||||
|
|
||||||
|
private const val A_DEVICE_ID = "device-id"
|
||||||
|
private const val AN_IP_ADDRESS = "ip-address"
|
||||||
|
private const val A_TIMESTAMP = 123L
|
||||||
|
private const val A_DISPLAY_NAME = "display-name"
|
||||||
|
|
||||||
|
class MyDeviceLastSeenInfoEntityMapperTest {
|
||||||
|
|
||||||
|
private val myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an entity when mapping to model then all fields are correctly mapped`() {
|
||||||
|
val entity = MyDeviceLastSeenInfoEntity(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
lastSeenIp = AN_IP_ADDRESS,
|
||||||
|
lastSeenTs = A_TIMESTAMP,
|
||||||
|
displayName = A_DISPLAY_NAME
|
||||||
|
)
|
||||||
|
val expectedDeviceInfo = DeviceInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
lastSeenIp = AN_IP_ADDRESS,
|
||||||
|
lastSeenTs = A_TIMESTAMP,
|
||||||
|
displayName = A_DISPLAY_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
val deviceInfo = myDeviceLastSeenInfoEntityMapper.map(entity)
|
||||||
|
|
||||||
|
deviceInfo shouldBeEqualTo expectedDeviceInfo
|
||||||
|
}
|
||||||
|
}
|
@ -339,6 +339,7 @@
|
|||||||
<activity android:name=".features.call.dialpad.PstnDialActivity" />
|
<activity android:name=".features.call.dialpad.PstnDialActivity" />
|
||||||
<activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
|
<activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
|
||||||
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
|
<activity android:name=".features.home.room.list.home.release.ReleaseNotesActivity"/>
|
||||||
|
<activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
|
||||||
|
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
|
@ -88,6 +88,7 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie
|
|||||||
import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
|
import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
|
||||||
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
|
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
|
||||||
import im.vector.app.features.settings.devices.DevicesViewModel
|
import im.vector.app.features.settings.devices.DevicesViewModel
|
||||||
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
|
||||||
import im.vector.app.features.settings.devtools.AccountDataViewModel
|
import im.vector.app.features.settings.devtools.AccountDataViewModel
|
||||||
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
|
||||||
import im.vector.app.features.settings.devtools.KeyRequestListViewModel
|
import im.vector.app.features.settings.devtools.KeyRequestListViewModel
|
||||||
@ -630,4 +631,9 @@ interface MavericksViewModelModule {
|
|||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(ReleaseNotesViewModel::class)
|
@MavericksViewModelKey(ReleaseNotesViewModel::class)
|
||||||
fun releaseNotesViewModel(factory: ReleaseNotesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun releaseNotesViewModel(factory: ReleaseNotesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(SessionOverviewViewModel::class)
|
||||||
|
fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ class MessageInformationDataFactory @Inject constructor(
|
|||||||
.toModel<EncryptedEventContent>()
|
.toModel<EncryptedEventContent>()
|
||||||
?.deviceId
|
?.deviceId
|
||||||
?.let { deviceId ->
|
?.let { deviceId ->
|
||||||
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
|
session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId)
|
||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
sendingDevice == null -> {
|
sendingDevice == null -> {
|
||||||
|
@ -585,7 +585,7 @@ class VectorSettingsSecurityPrivacyFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// crypto section: device key (fingerprint)
|
// crypto section: device key (fingerprint)
|
||||||
val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId)
|
val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId)
|
||||||
|
|
||||||
val fingerprint = deviceInfo?.fingerprint()
|
val fingerprint = deviceInfo?.fingerprint()
|
||||||
if (fingerprint?.isNotEmpty() == true) {
|
if (fingerprint?.isNotEmpty() == true) {
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to hold some info about the cross signing of the current Session.
|
||||||
|
*/
|
||||||
|
data class CurrentSessionCrossSigningInfo(
|
||||||
|
val deviceId: String?,
|
||||||
|
val isCrossSigningInitialized: Boolean,
|
||||||
|
val isCrossSigningVerified: Boolean,
|
||||||
|
)
|
@ -101,6 +101,8 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val matrix: Matrix,
|
private val matrix: Matrix,
|
||||||
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||||
) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener {
|
) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener {
|
||||||
|
|
||||||
var uiaContinuation: Continuation<UIABaseAuth>? = null
|
var uiaContinuation: Continuation<UIABaseAuth>? = null
|
||||||
@ -116,8 +118,9 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
private val refreshSource = PublishDataSource<Unit>()
|
private val refreshSource = PublishDataSource<Unit>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
val hasAccountCrossSigning = currentSessionCrossSigningInfo.isCrossSigningInitialized
|
||||||
|
val accountCrossSigningIsTrusted = currentSessionCrossSigningInfo.isCrossSigningVerified
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
@ -143,12 +146,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
.sortedByDescending { it.lastSeenTs }
|
.sortedByDescending { it.lastSeenTs }
|
||||||
.map { deviceInfo ->
|
.map { deviceInfo ->
|
||||||
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
||||||
val trustLevelForShield = computeTrustLevelForShield(
|
val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
currentSessionCrossTrusted = accountCrossSigningIsTrusted,
|
|
||||||
legacyMode = !hasAccountCrossSigning,
|
|
||||||
deviceTrustLevel = cryptoDeviceInfo?.trustLevel,
|
|
||||||
isCurrentDevice = deviceInfo.deviceId == session.sessionParams.deviceId
|
|
||||||
)
|
|
||||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
||||||
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
|
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
|
||||||
}
|
}
|
||||||
@ -268,20 +266,6 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeTrustLevelForShield(
|
|
||||||
currentSessionCrossTrusted: Boolean,
|
|
||||||
legacyMode: Boolean,
|
|
||||||
deviceTrustLevel: DeviceTrustLevel?,
|
|
||||||
isCurrentDevice: Boolean,
|
|
||||||
): RoomEncryptionTrustLevel {
|
|
||||||
return TrustUtils.shieldForTrust(
|
|
||||||
currentDevice = isCurrentDevice,
|
|
||||||
trustMSK = currentSessionCrossTrusted,
|
|
||||||
legacyMode = legacyMode,
|
|
||||||
deviceTrustLevel = deviceTrustLevel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
|
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
|
||||||
val txID = session.cryptoService()
|
val txID = session.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(): CurrentSessionCrossSigningInfo {
|
||||||
|
val session = activeSessionHolder.getActiveSession()
|
||||||
|
val isCrossSigningInitialized = session.cryptoService().crossSigningService().isCrossSigningInitialized()
|
||||||
|
val isCrossSigningVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||||
|
return CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = session.sessionParams.deviceId,
|
||||||
|
isCrossSigningInitialized = isCrossSigningInitialized,
|
||||||
|
isCrossSigningVerified = isCrossSigningVerified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetEncryptionTrustLevelForCurrentDeviceUseCase @Inject constructor() {
|
||||||
|
|
||||||
|
fun execute(trustMSK: Boolean, legacyMode: Boolean): RoomEncryptionTrustLevel {
|
||||||
|
return if (legacyMode) {
|
||||||
|
// In legacy, current session is always trusted
|
||||||
|
RoomEncryptionTrustLevel.Trusted
|
||||||
|
} else {
|
||||||
|
// If current session doesn't trust MSK, show red shield for current device
|
||||||
|
if (trustMSK) {
|
||||||
|
RoomEncryptionTrustLevel.Trusted
|
||||||
|
} else {
|
||||||
|
RoomEncryptionTrustLevel.Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetEncryptionTrustLevelForDeviceUseCase @Inject constructor(
|
||||||
|
private val getEncryptionTrustLevelForCurrentDeviceUseCase: GetEncryptionTrustLevelForCurrentDeviceUseCase,
|
||||||
|
private val getEncryptionTrustLevelForOtherDeviceUseCase: GetEncryptionTrustLevelForOtherDeviceUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel {
|
||||||
|
val legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized
|
||||||
|
val trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified
|
||||||
|
val isCurrentDevice = !cryptoDeviceInfo?.deviceId.isNullOrEmpty() && cryptoDeviceInfo?.deviceId == currentSessionCrossSigningInfo.deviceId
|
||||||
|
val deviceTrustLevel = cryptoDeviceInfo?.trustLevel
|
||||||
|
|
||||||
|
return when {
|
||||||
|
isCurrentDevice -> getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK, legacyMode)
|
||||||
|
else -> getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK, legacyMode, deviceTrustLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.settings.devices
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetEncryptionTrustLevelForOtherDeviceUseCase @Inject constructor() {
|
||||||
|
|
||||||
|
fun execute(trustMSK: Boolean, legacyMode: Boolean, deviceTrustLevel: DeviceTrustLevel?): RoomEncryptionTrustLevel {
|
||||||
|
return if (legacyMode) {
|
||||||
|
// use local trust
|
||||||
|
if (deviceTrustLevel?.locallyVerified == true) {
|
||||||
|
RoomEncryptionTrustLevel.Trusted
|
||||||
|
} else {
|
||||||
|
RoomEncryptionTrustLevel.Warning
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (trustMSK) {
|
||||||
|
// use cross sign trust, put locally trusted in black
|
||||||
|
when {
|
||||||
|
deviceTrustLevel?.crossSigningVerified == true -> RoomEncryptionTrustLevel.Trusted
|
||||||
|
deviceTrustLevel?.locallyVerified == true -> RoomEncryptionTrustLevel.Default
|
||||||
|
else -> RoomEncryptionTrustLevel.Warning
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The current session is untrusted, so displays others in black
|
||||||
|
// as we can't know the cross-signing state
|
||||||
|
RoomEncryptionTrustLevel.Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices
|
|||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
// TODO Replace usage by the use case GetEncryptionTrustLevelForDeviceUseCase
|
||||||
object TrustUtils {
|
object TrustUtils {
|
||||||
|
|
||||||
fun shieldForTrust(
|
fun shieldForTrust(
|
||||||
|
@ -31,8 +31,11 @@ import com.airbnb.mvrx.fragmentViewModel
|
|||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.dialogs.ManuallyVerifyDialog
|
import im.vector.app.core.dialogs.ManuallyVerifyDialog
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||||
import im.vector.app.features.crypto.recover.SetupMode
|
import im.vector.app.features.crypto.recover.SetupMode
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
@ -40,8 +43,11 @@ import im.vector.app.features.settings.devices.DeviceFullInfo
|
|||||||
import im.vector.app.features.settings.devices.DevicesAction
|
import im.vector.app.features.settings.devices.DevicesAction
|
||||||
import im.vector.app.features.settings.devices.DevicesViewEvents
|
import im.vector.app.features.settings.devices.DevicesViewEvents
|
||||||
import im.vector.app.features.settings.devices.DevicesViewModel
|
import im.vector.app.features.settings.devices.DevicesViewModel
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
|
||||||
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
|
||||||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the list of the user's devices and sessions.
|
* Display the list of the user's devices and sessions.
|
||||||
@ -50,6 +56,14 @@ import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationVie
|
|||||||
class VectorSettingsDevicesFragment :
|
class VectorSettingsDevicesFragment :
|
||||||
VectorBaseFragment<FragmentSettingsDevicesBinding>() {
|
VectorBaseFragment<FragmentSettingsDevicesBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
|
||||||
|
|
||||||
|
@Inject lateinit var dateFormatter: VectorDateFormatter
|
||||||
|
|
||||||
|
@Inject lateinit var drawableProvider: DrawableProvider
|
||||||
|
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
private val viewModel: DevicesViewModel by fragmentViewModel()
|
private val viewModel: DevicesViewModel by fragmentViewModel()
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding {
|
||||||
@ -72,10 +86,11 @@ class VectorSettingsDevicesFragment :
|
|||||||
|
|
||||||
initLearnMoreButtons()
|
initLearnMoreButtons()
|
||||||
initWaitingView()
|
initWaitingView()
|
||||||
observerViewEvents()
|
initOtherSessionsView()
|
||||||
|
observeViewEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observerViewEvents() {
|
private fun observeViewEvents() {
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DevicesViewEvents.Loading -> showLoading(it.message)
|
is DevicesViewEvents.Loading -> showLoading(it.message)
|
||||||
@ -110,6 +125,14 @@ class VectorSettingsDevicesFragment :
|
|||||||
views.waitingView.waitingStatusText.isVisible = true
|
views.waitingView.waitingStatusText.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initOtherSessionsView() {
|
||||||
|
views.deviceListOtherSessions.setCallback(object : OtherSessionsController.Callback {
|
||||||
|
override fun onItemClicked(deviceId: String) {
|
||||||
|
navigateToSessionOverview(deviceId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
cleanUpLearnMoreButtonsListeners()
|
cleanUpLearnMoreButtonsListeners()
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
@ -196,16 +219,39 @@ class VectorSettingsDevicesFragment :
|
|||||||
currentDeviceInfo?.let {
|
currentDeviceInfo?.let {
|
||||||
views.deviceListHeaderCurrentSession.isVisible = true
|
views.deviceListHeaderCurrentSession.isVisible = true
|
||||||
views.deviceListCurrentSession.isVisible = true
|
views.deviceListCurrentSession.isVisible = true
|
||||||
views.deviceListCurrentSession.render(it)
|
val viewState = SessionInfoViewState(
|
||||||
|
isCurrentSession = true,
|
||||||
|
deviceFullInfo = it
|
||||||
|
)
|
||||||
|
views.deviceListCurrentSession.render(viewState, dateFormatter, drawableProvider, colorProvider)
|
||||||
|
views.deviceListCurrentSession.debouncedClicks {
|
||||||
|
currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
|
||||||
|
}
|
||||||
|
views.deviceListCurrentSession.viewDetailsButton.debouncedClicks {
|
||||||
|
currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
|
||||||
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
hideCurrentSessionView()
|
hideCurrentSessionView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToSessionOverview(deviceId: String) {
|
||||||
|
viewNavigator.navigateToSessionOverview(
|
||||||
|
context = requireActivity(),
|
||||||
|
deviceId = deviceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun hideCurrentSessionView() {
|
private fun hideCurrentSessionView() {
|
||||||
views.deviceListHeaderCurrentSession.isVisible = false
|
views.deviceListHeaderCurrentSession.isVisible = false
|
||||||
views.deviceListCurrentSession.isVisible = false
|
views.deviceListCurrentSession.isVisible = false
|
||||||
views.deviceListDividerCurrentSession.isVisible = false
|
views.deviceListDividerCurrentSession.isVisible = false
|
||||||
|
views.deviceListCurrentSession.debouncedClicks {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
views.deviceListCurrentSession.viewDetailsButton.debouncedClicks {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
|
private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
||||||
|
|
||||||
|
fun navigateToSessionOverview(context: Context, deviceId: String) {
|
||||||
|
context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +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.list
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.databinding.ViewCurrentSessionBinding
|
|
||||||
import im.vector.app.features.settings.devices.DeviceFullInfo
|
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
|
||||||
|
|
||||||
class CurrentSessionView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0
|
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
private val views: ViewCurrentSessionBinding
|
|
||||||
|
|
||||||
init {
|
|
||||||
inflate(context, R.layout.view_current_session, this)
|
|
||||||
views = ViewCurrentSessionBinding.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun render(currentDeviceInfo: DeviceFullInfo) {
|
|
||||||
renderDeviceInfo(currentDeviceInfo.deviceInfo.displayName.orEmpty())
|
|
||||||
renderVerificationStatus(currentDeviceInfo.trustLevelForShield)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) {
|
|
||||||
views.currentSessionVerificationStatusImageView.render(trustLevelForShield)
|
|
||||||
if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
|
|
||||||
renderCrossSigningVerified()
|
|
||||||
} else {
|
|
||||||
renderCrossSigningUnverified()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderCrossSigningVerified() {
|
|
||||||
views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified)
|
|
||||||
views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
|
|
||||||
views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified)
|
|
||||||
views.currentSessionVerifySessionButton.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderCrossSigningUnverified() {
|
|
||||||
views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified)
|
|
||||||
views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError))
|
|
||||||
views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified)
|
|
||||||
views.currentSessionVerifySessionButton.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO. We don't have this info yet. Update later accordingly.
|
|
||||||
private fun renderDeviceInfo(sessionName: String) {
|
|
||||||
views.currentSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
|
|
||||||
views.currentSessionDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile)
|
|
||||||
views.currentSessionNameTextView.text = sessionName
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,8 +22,10 @@ import android.widget.TextView
|
|||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.ui.views.ShieldImageView
|
import im.vector.app.core.ui.views.ShieldImageView
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
@ -49,8 +51,16 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
|
|||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var stringProvider: StringProvider
|
lateinit var stringProvider: StringProvider
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var clickListener: ClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
|
holder.view.onClick(clickListener)
|
||||||
|
if (clickListener == null) {
|
||||||
|
holder.view.isClickable = false
|
||||||
|
}
|
||||||
|
|
||||||
when (deviceType) {
|
when (deviceType) {
|
||||||
DeviceType.MOBILE -> {
|
DeviceType.MOBILE -> {
|
||||||
holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
|
holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
|
||||||
|
@ -35,6 +35,12 @@ class OtherSessionsController @Inject constructor(
|
|||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
) : TypedEpoxyController<List<DeviceFullInfo>>() {
|
) : TypedEpoxyController<List<DeviceFullInfo>>() {
|
||||||
|
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onItemClicked(deviceId: String)
|
||||||
|
}
|
||||||
|
|
||||||
override fun buildModels(data: List<DeviceFullInfo>?) {
|
override fun buildModels(data: List<DeviceFullInfo>?) {
|
||||||
val host = this
|
val host = this
|
||||||
|
|
||||||
@ -70,6 +76,7 @@ class OtherSessionsController @Inject constructor(
|
|||||||
sessionDescription(description)
|
sessionDescription(description)
|
||||||
sessionDescriptionDrawable(descriptionDrawable)
|
sessionDescriptionDrawable(descriptionDrawable)
|
||||||
stringProvider(this@OtherSessionsController.stringProvider)
|
stringProvider(this@OtherSessionsController.stringProvider)
|
||||||
|
clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,12 @@ class OtherSessionsView @JvmOverloads constructor(
|
|||||||
otherSessionsController.setData(devices)
|
otherSessionsController.setData(devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCallback(callback: OtherSessionsController.Callback) {
|
||||||
|
otherSessionsController.callback = callback
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
|
otherSessionsController.callback = null
|
||||||
views.otherSessionsRecyclerView.cleanup()
|
views.otherSessionsRecyclerView.cleanup()
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* 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.list
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
|
import im.vector.app.databinding.ViewSessionInfoBinding
|
||||||
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
class SessionInfoView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
private val views: ViewSessionInfoBinding
|
||||||
|
|
||||||
|
var onLearnMoreClickListener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.view_session_info, this)
|
||||||
|
views = ViewSessionInfoBinding.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
val viewDetailsButton = views.sessionInfoViewDetailsButton
|
||||||
|
|
||||||
|
fun render(
|
||||||
|
sessionInfoViewState: SessionInfoViewState,
|
||||||
|
dateFormatter: VectorDateFormatter,
|
||||||
|
drawableProvider: DrawableProvider,
|
||||||
|
colorProvider: ColorProvider,
|
||||||
|
) {
|
||||||
|
renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
|
||||||
|
renderVerificationStatus(
|
||||||
|
sessionInfoViewState.deviceFullInfo.trustLevelForShield,
|
||||||
|
sessionInfoViewState.isCurrentSession,
|
||||||
|
sessionInfoViewState.isLearnMoreLinkVisible,
|
||||||
|
)
|
||||||
|
renderDeviceLastSeenDetails(
|
||||||
|
sessionInfoViewState.deviceFullInfo.isInactive,
|
||||||
|
sessionInfoViewState.deviceFullInfo.deviceInfo,
|
||||||
|
sessionInfoViewState.isLastSeenDetailsVisible,
|
||||||
|
dateFormatter,
|
||||||
|
drawableProvider,
|
||||||
|
colorProvider,
|
||||||
|
)
|
||||||
|
renderDetailsButton(sessionInfoViewState.isDetailsButtonVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderVerificationStatus(
|
||||||
|
encryptionTrustLevel: RoomEncryptionTrustLevel,
|
||||||
|
isCurrentSession: Boolean,
|
||||||
|
hasLearnMoreLink: Boolean,
|
||||||
|
) {
|
||||||
|
views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
|
||||||
|
if (encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
|
||||||
|
renderCrossSigningVerified(isCurrentSession)
|
||||||
|
} else {
|
||||||
|
renderCrossSigningUnverified(isCurrentSession)
|
||||||
|
}
|
||||||
|
if (hasLearnMoreLink) {
|
||||||
|
appendLearnMoreToVerificationStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendLearnMoreToVerificationStatus() {
|
||||||
|
val status = views.sessionInfoVerificationStatusDetailTextView.text
|
||||||
|
val learnMore = context.getString(R.string.action_learn_more)
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
stringBuilder.append(status)
|
||||||
|
stringBuilder.append(" ")
|
||||||
|
stringBuilder.append(learnMore)
|
||||||
|
|
||||||
|
views.sessionInfoVerificationStatusDetailTextView.setTextWithColoredPart(
|
||||||
|
fullText = stringBuilder.toString(),
|
||||||
|
coloredPart = learnMore,
|
||||||
|
underline = false
|
||||||
|
) {
|
||||||
|
onLearnMoreClickListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCrossSigningVerified(isCurrentSession: Boolean) {
|
||||||
|
views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified)
|
||||||
|
views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
|
||||||
|
val statusResId = if (isCurrentSession) {
|
||||||
|
R.string.device_manager_verification_status_detail_current_session_verified
|
||||||
|
} else {
|
||||||
|
R.string.device_manager_verification_status_detail_other_session_verified
|
||||||
|
}
|
||||||
|
views.sessionInfoVerificationStatusDetailTextView.text = context.getString(statusResId)
|
||||||
|
views.sessionInfoVerifySessionButton.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCrossSigningUnverified(isCurrentSession: Boolean) {
|
||||||
|
views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified)
|
||||||
|
views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError))
|
||||||
|
val statusResId = if (isCurrentSession) {
|
||||||
|
R.string.device_manager_verification_status_detail_current_session_unverified
|
||||||
|
} else {
|
||||||
|
R.string.device_manager_verification_status_detail_other_session_unverified
|
||||||
|
}
|
||||||
|
views.sessionInfoVerificationStatusDetailTextView.text = context.getString(statusResId)
|
||||||
|
views.sessionInfoVerifySessionButton.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO. We don't have this info yet. Update later accordingly.
|
||||||
|
private fun renderDeviceInfo(sessionName: String) {
|
||||||
|
views.sessionInfoDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
|
||||||
|
views.sessionInfoDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile)
|
||||||
|
views.sessionInfoNameTextView.text = sessionName
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderDeviceLastSeenDetails(
|
||||||
|
isInactive: Boolean,
|
||||||
|
deviceInfo: DeviceInfo,
|
||||||
|
isLastSeenDetailsVisible: Boolean,
|
||||||
|
dateFormatter: VectorDateFormatter,
|
||||||
|
drawableProvider: DrawableProvider,
|
||||||
|
colorProvider: ColorProvider,
|
||||||
|
) {
|
||||||
|
deviceInfo.lastSeenTs
|
||||||
|
?.takeIf { isLastSeenDetailsVisible }
|
||||||
|
?.let { timestamp ->
|
||||||
|
views.sessionInfoLastActivityTextView.isVisible = true
|
||||||
|
views.sessionInfoLastActivityTextView.text = if (isInactive) {
|
||||||
|
val formattedTs = dateFormatter.format(timestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)
|
||||||
|
context.resources.getQuantityString(
|
||||||
|
R.plurals.device_manager_other_sessions_description_inactive,
|
||||||
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||||
|
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||||
|
formattedTs
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
|
context.getString(R.string.device_manager_session_last_activity, formattedTs)
|
||||||
|
}
|
||||||
|
val drawable = if (isInactive) {
|
||||||
|
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||||
|
drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
views.sessionInfoLastActivityTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
views.sessionInfoLastActivityTextView.isGone = true
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceInfo.lastSeenIp
|
||||||
|
?.takeIf { isLastSeenDetailsVisible }
|
||||||
|
?.let { ipAddress ->
|
||||||
|
views.sessionInfoLastIPAddressTextView.isVisible = true
|
||||||
|
views.sessionInfoLastIPAddressTextView.text = ipAddress
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
views.sessionInfoLastIPAddressTextView.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {
|
||||||
|
views.sessionInfoViewDetailsButton.isVisible = isDetailsButtonVisible
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.list
|
||||||
|
|
||||||
|
import im.vector.app.features.settings.devices.DeviceFullInfo
|
||||||
|
|
||||||
|
data class SessionInfoViewState(
|
||||||
|
val isCurrentSession: Boolean,
|
||||||
|
val deviceFullInfo: DeviceFullInfo,
|
||||||
|
val isDetailsButtonVisible: Boolean = true,
|
||||||
|
val isLearnMoreLinkVisible: Boolean = false,
|
||||||
|
val isLastSeenDetailsVisible: Boolean = false,
|
||||||
|
)
|
@ -25,15 +25,15 @@ import androidx.core.content.res.use
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.setTextWithColoredPart
|
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||||
import im.vector.app.databinding.ViewDevicesListHeaderBinding
|
import im.vector.app.databinding.ViewSessionsListHeaderBinding
|
||||||
|
|
||||||
class DevicesListHeaderView @JvmOverloads constructor(
|
class SessionsListHeaderView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private val binding = ViewDevicesListHeaderBinding.inflate(
|
private val binding = ViewSessionsListHeaderBinding.inflate(
|
||||||
LayoutInflater.from(context),
|
LayoutInflater.from(context),
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
@ -43,7 +43,7 @@ class DevicesListHeaderView @JvmOverloads constructor(
|
|||||||
init {
|
init {
|
||||||
context.obtainStyledAttributes(
|
context.obtainStyledAttributes(
|
||||||
attrs,
|
attrs,
|
||||||
R.styleable.DevicesListHeaderView,
|
R.styleable.SessionsListHeaderView,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
).use {
|
).use {
|
||||||
@ -53,14 +53,14 @@ class DevicesListHeaderView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setTitle(typedArray: TypedArray) {
|
private fun setTitle(typedArray: TypedArray) {
|
||||||
val title = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderTitle)
|
val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle)
|
||||||
binding.devicesListHeaderTitle.text = title
|
binding.sessionsListHeaderTitle.text = title
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setDescription(typedArray: TypedArray) {
|
private fun setDescription(typedArray: TypedArray) {
|
||||||
val description = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderDescription)
|
val description = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderDescription)
|
||||||
if (description.isNullOrEmpty()) {
|
if (description.isNullOrEmpty()) {
|
||||||
binding.devicesListHeaderDescription.isVisible = false
|
binding.sessionsListHeaderDescription.isVisible = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +70,8 @@ class DevicesListHeaderView @JvmOverloads constructor(
|
|||||||
stringBuilder.append(" ")
|
stringBuilder.append(" ")
|
||||||
stringBuilder.append(learnMore)
|
stringBuilder.append(learnMore)
|
||||||
|
|
||||||
binding.devicesListHeaderDescription.isVisible = true
|
binding.sessionsListHeaderDescription.isVisible = true
|
||||||
binding.devicesListHeaderDescription.setTextWithColoredPart(
|
binding.sessionsListHeaderDescription.setTextWithColoredPart(
|
||||||
fullText = stringBuilder.toString(),
|
fullText = stringBuilder.toString(),
|
||||||
coloredPart = learnMore,
|
coloredPart = learnMore,
|
||||||
underline = false
|
underline = false
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.overview
|
||||||
|
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.settings.devices.DeviceFullInfo
|
||||||
|
import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
|
||||||
|
import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetDeviceFullInfoUseCase @Inject constructor(
|
||||||
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
|
||||||
|
private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
|
||||||
|
return activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
|
val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
combine(
|
||||||
|
session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(),
|
||||||
|
session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow()
|
||||||
|
) { deviceInfo, cryptoDeviceInfo ->
|
||||||
|
val info = deviceInfo.getOrNull()
|
||||||
|
val cryptoInfo = cryptoDeviceInfo.getOrNull()
|
||||||
|
val fullInfo = if (info != null && cryptoInfo != null) {
|
||||||
|
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
|
||||||
|
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
|
||||||
|
DeviceFullInfo(
|
||||||
|
deviceInfo = info,
|
||||||
|
cryptoDeviceInfo = cryptoInfo,
|
||||||
|
trustLevelForShield = roomEncryptionTrustLevel,
|
||||||
|
isInactive = isInactive
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
fullInfo.toOptional()
|
||||||
|
}
|
||||||
|
} ?: emptyFlow()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.overview
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
|
sealed class SessionOverviewAction : VectorViewModelAction
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.overview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.airbnb.mvrx.Mavericks
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.core.extensions.addFragment
|
||||||
|
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the overview info about a Session.
|
||||||
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class SessionOverviewActivity : SimpleFragmentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (isFirstCreation()) {
|
||||||
|
addFragment(
|
||||||
|
container = views.container,
|
||||||
|
fragmentClass = SessionOverviewFragment::class.java,
|
||||||
|
params = intent.getParcelableExtra(Mavericks.KEY_ARG)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newIntent(context: Context, deviceId: String): Intent {
|
||||||
|
return Intent(context, SessionOverviewActivity::class.java).apply {
|
||||||
|
putExtra(Mavericks.KEY_ARG, SessionOverviewArgs(deviceId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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.overview
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class SessionOverviewArgs(
|
||||||
|
val deviceId: String
|
||||||
|
) : Parcelable
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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.overview
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
|
import im.vector.app.databinding.FragmentSessionOverviewBinding
|
||||||
|
import im.vector.app.features.settings.devices.DeviceFullInfo
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the overview info about a Session.
|
||||||
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class SessionOverviewFragment :
|
||||||
|
VectorBaseFragment<FragmentSessionOverviewBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var dateFormatter: VectorDateFormatter
|
||||||
|
|
||||||
|
@Inject lateinit var drawableProvider: DrawableProvider
|
||||||
|
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
|
private val viewModel: SessionOverviewViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionOverviewBinding {
|
||||||
|
return FragmentSessionOverviewBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
initSessionInfoView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initSessionInfoView() {
|
||||||
|
views.sessionOverviewInfo.onLearnMoreClickListener = {
|
||||||
|
Toast.makeText(context, "Learn more verification status", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
cleanUpSessionInfoView()
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanUpSessionInfoView() {
|
||||||
|
views.sessionOverviewInfo.onLearnMoreClickListener = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
updateToolbar(state.isCurrentSession)
|
||||||
|
if (state.deviceInfo is Success) {
|
||||||
|
renderSessionInfo(state.isCurrentSession, state.deviceInfo.invoke())
|
||||||
|
} else {
|
||||||
|
hideSessionInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateToolbar(isCurrentSession: Boolean) {
|
||||||
|
val titleResId = if (isCurrentSession) R.string.device_manager_current_session_title else R.string.device_manager_session_title
|
||||||
|
(activity as? AppCompatActivity)
|
||||||
|
?.supportActionBar
|
||||||
|
?.setTitle(titleResId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) {
|
||||||
|
views.sessionOverviewInfo.isVisible = true
|
||||||
|
val viewState = SessionInfoViewState(
|
||||||
|
isCurrentSession = isCurrentSession,
|
||||||
|
deviceFullInfo = deviceFullInfo,
|
||||||
|
isDetailsButtonVisible = false,
|
||||||
|
isLearnMoreLinkVisible = true,
|
||||||
|
isLastSeenDetailsVisible = true,
|
||||||
|
)
|
||||||
|
views.sessionOverviewInfo.render(viewState, dateFormatter, drawableProvider, colorProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideSessionInfo() {
|
||||||
|
views.sessionOverviewInfo.isGone = true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.settings.devices.v2.overview
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
|
||||||
|
class SessionOverviewViewModel @AssistedInject constructor(
|
||||||
|
@Assisted val initialState: SessionOverviewViewState,
|
||||||
|
session: Session,
|
||||||
|
private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
|
||||||
|
) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
|
companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : MavericksAssistedViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> {
|
||||||
|
override fun create(initialState: SessionOverviewViewState): SessionOverviewViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val currentDeviceId = session.sessionParams.deviceId.orEmpty()
|
||||||
|
setState {
|
||||||
|
copy(isCurrentSession = deviceId.isNotEmpty() && deviceId == currentDeviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
observeSessionInfo(initialState.deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeSessionInfo(deviceId: String) {
|
||||||
|
getDeviceFullInfoUseCase.execute(deviceId)
|
||||||
|
.mapNotNull { it.getOrNull() }
|
||||||
|
.onEach { setState { copy(deviceInfo = Success(it)) } }
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: SessionOverviewAction) {
|
||||||
|
TODO("Implement when adding the first action")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.settings.devices.v2.overview
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.features.settings.devices.DeviceFullInfo
|
||||||
|
|
||||||
|
data class SessionOverviewViewState(
|
||||||
|
val deviceId: String,
|
||||||
|
val isCurrentSession: Boolean = false,
|
||||||
|
val deviceInfo: Async<DeviceFullInfo> = Uninitialized,
|
||||||
|
) : MavericksState {
|
||||||
|
constructor(args: SessionOverviewArgs) : this(
|
||||||
|
deviceId = args.deviceId
|
||||||
|
)
|
||||||
|
}
|
20
vector/src/main/res/layout/fragment_session_overview.xml
Normal file
20
vector/src/main/res/layout/fragment_session_overview.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<im.vector.app.features.settings.devices.v2.list.SessionInfoView
|
||||||
|
android:id="@+id/sessionOverviewInfo"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="24dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -8,7 +8,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
|
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
|
||||||
android:id="@+id/deviceListHeaderSectionSecurityRecommendations"
|
android:id="@+id/deviceListHeaderSectionSecurityRecommendations"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -56,17 +56,17 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/deviceListInactiveSessionsRecommendation" />
|
app:layout_constraintTop_toBottomOf="@id/deviceListInactiveSessionsRecommendation" />
|
||||||
|
|
||||||
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
|
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
|
||||||
android:id="@+id/deviceListHeaderCurrentSession"
|
android:id="@+id/deviceListHeaderCurrentSession"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:devicesListHeaderDescription=""
|
app:devicesListHeaderDescription=""
|
||||||
app:devicesListHeaderTitle="@string/device_manager_header_section_current_session"
|
app:devicesListHeaderTitle="@string/device_manager_current_session_title"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" />
|
app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" />
|
||||||
|
|
||||||
<im.vector.app.features.settings.devices.v2.list.CurrentSessionView
|
<im.vector.app.features.settings.devices.v2.list.SessionInfoView
|
||||||
android:id="@+id/deviceListCurrentSession"
|
android:id="@+id/deviceListCurrentSession"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -86,7 +86,7 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/deviceListCurrentSession" />
|
app:layout_constraintTop_toBottomOf="@id/deviceListCurrentSession" />
|
||||||
|
|
||||||
<im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
|
<im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
|
||||||
android:id="@+id/deviceListHeaderOtherSessions"
|
android:id="@+id/deviceListHeaderOtherSessions"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:foreground="?selectableItemBackground"
|
||||||
android:paddingTop="16dp">
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
android:paddingBottom="16dp">
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/currentSessionDeviceTypeImageView"
|
android:id="@+id/sessionInfoDeviceTypeImageView"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
@ -21,18 +21,18 @@
|
|||||||
tools:src="@drawable/ic_device_type_mobile" />
|
tools:src="@drawable/ic_device_type_mobile" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/currentSessionNameTextView"
|
android:id="@+id/sessionInfoNameTextView"
|
||||||
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/currentSessionDeviceTypeImageView"
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoDeviceTypeImageView"
|
||||||
tools:text="Element Mobile: Android" />
|
tools:text="Element Mobile: Android" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/currentSessionVerificationStatusContainer"
|
android:id="@+id/sessionInfoVerificationStatusContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
@ -40,17 +40,17 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/currentSessionNameTextView">
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoNameTextView">
|
||||||
|
|
||||||
<im.vector.app.core.ui.views.ShieldImageView
|
<im.vector.app.core.ui.views.ShieldImageView
|
||||||
android:id="@+id/currentSessionVerificationStatusImageView"
|
android:id="@+id/sessionInfoVerificationStatusImageView"
|
||||||
android:layout_width="16dp"
|
android:layout_width="16dp"
|
||||||
android:layout_height="16dp"
|
android:layout_height="16dp"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
tools:src="@drawable/ic_shield_trusted" />
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/currentSessionVerificationStatusTextView"
|
android:id="@+id/sessionInfoVerificationStatusTextView"
|
||||||
style="@style/TextAppearance.Vector.Body"
|
style="@style/TextAppearance.Vector.Body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/currentSessionVerificationStatusDetailTextView"
|
android:id="@+id/sessionInfoVerificationStatusDetailTextView"
|
||||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -69,11 +69,40 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/currentSessionVerificationStatusContainer"
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusContainer"
|
||||||
tools:text="@string/device_manager_verification_status_detail_verified" />
|
tools:text="@string/device_manager_verification_status_detail_current_session_verified" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionInfoLastActivityTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusDetailTextView"
|
||||||
|
app:layout_constraintWidth="wrap_content_constrained"
|
||||||
|
tools:text="Last activity Fri 14:59"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sessionInfoLastIPAddressTextView"
|
||||||
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoLastActivityTextView"
|
||||||
|
tools:text="81.235.41.100 (United Kingdom)"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/currentSessionVerifySessionButton"
|
android:id="@+id/sessionInfoVerifySessionButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="52dp"
|
android:layout_height="52dp"
|
||||||
android:layout_marginHorizontal="24dp"
|
android:layout_marginHorizontal="24dp"
|
||||||
@ -81,10 +110,10 @@
|
|||||||
android:text="@string/device_manager_verify_session"
|
android:text="@string/device_manager_verify_session"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/currentSessionVerificationStatusDetailTextView" />
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoLastIPAddressTextView" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/currentSessionViewDetailsButton"
|
android:id="@+id/sessionInfoViewDetailsButton"
|
||||||
style="@style/Widget.Vector.Button.Text"
|
style="@style/Widget.Vector.Button.Text"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -93,6 +122,6 @@
|
|||||||
android:text="@string/device_manager_view_details"
|
android:text="@string/device_manager_view_details"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/currentSessionVerifySessionButton" />
|
app:layout_constraintTop_toBottomOf="@id/sessionInfoVerifySessionButton" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -7,7 +7,7 @@
|
|||||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/devices_list_header_title"
|
android:id="@+id/sessions_list_header_title"
|
||||||
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -19,14 +19,14 @@
|
|||||||
tools:text="Other sessions" />
|
tools:text="Other sessions" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/devices_list_header_description"
|
android:id="@+id/sessions_list_header_description"
|
||||||
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
style="@style/TextAppearance.Vector.Body.DevicesManagement"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="18.5dp"
|
android:layout_marginTop="18.5dp"
|
||||||
android:layout_marginEnd="40dp"
|
android:layout_marginEnd="40dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/devices_list_header_title"
|
app:layout_constraintStart_toStartOf="@id/sessions_list_header_title"
|
||||||
app:layout_constraintTop_toBottomOf="@id/devices_list_header_title"
|
app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title"
|
||||||
tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />
|
tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />
|
||||||
</merge>
|
</merge>
|
@ -18,7 +18,7 @@ package im.vector.app.features.location.live
|
|||||||
|
|
||||||
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
import im.vector.app.test.fakes.givenAsFlowReturns
|
import im.vector.app.test.fakes.givenAsFlow
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@ -28,7 +28,6 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
|
||||||
|
|
||||||
private const val A_ROOM_ID = "room_id"
|
private const val A_ROOM_ID = "room_id"
|
||||||
private const val AN_EVENT_ID = "event_id"
|
private const val AN_EVENT_ID = "event_id"
|
||||||
@ -64,7 +63,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
|
|||||||
.getRoom(A_ROOM_ID)
|
.getRoom(A_ROOM_ID)
|
||||||
.locationSharingService()
|
.locationSharingService()
|
||||||
.givenLiveLocationShareSummaryReturns(AN_EVENT_ID, summary)
|
.givenLiveLocationShareSummaryReturns(AN_EVENT_ID, summary)
|
||||||
.givenAsFlowReturns(Optional(summary))
|
.givenAsFlow()
|
||||||
|
|
||||||
val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
|
val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
|
||||||
|
|
||||||
@ -77,7 +76,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
|
|||||||
.getRoom(A_ROOM_ID)
|
.getRoom(A_ROOM_ID)
|
||||||
.locationSharingService()
|
.locationSharingService()
|
||||||
.givenLiveLocationShareSummaryReturns(AN_EVENT_ID, null)
|
.givenLiveLocationShareSummaryReturns(AN_EVENT_ID, null)
|
||||||
.givenAsFlowReturns(Optional(null))
|
.givenAsFlow()
|
||||||
|
|
||||||
val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
|
val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ package im.vector.app.features.location.live.map
|
|||||||
import im.vector.app.features.location.LocationData
|
import im.vector.app.features.location.LocationData
|
||||||
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
import im.vector.app.test.fakes.givenAsFlowReturns
|
import im.vector.app.test.fakes.givenAsFlow
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
@ -81,7 +81,7 @@ class GetListOfUserLiveLocationUseCaseTest {
|
|||||||
.getRoom(A_ROOM_ID)
|
.getRoom(A_ROOM_ID)
|
||||||
.locationSharingService()
|
.locationSharingService()
|
||||||
.givenRunningLiveLocationShareSummariesReturns(summaries)
|
.givenRunningLiveLocationShareSummariesReturns(summaries)
|
||||||
.givenAsFlowReturns(summaries)
|
.givenAsFlow()
|
||||||
|
|
||||||
val viewState1 = UserLiveLocationViewState(
|
val viewState1 = UserLiveLocationViewState(
|
||||||
matrixItem = MatrixItem.UserItem(id = "@userId1:matrix.org", displayName = "User 1", avatarUrl = ""),
|
matrixItem = MatrixItem.UserItem(id = "@userId1:matrix.org", displayName = "User 1", avatarUrl = ""),
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
|
|
||||||
|
private const val A_DEVICE_ID = "device-id"
|
||||||
|
|
||||||
|
class GetCurrentSessionCrossSigningInfoUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase = GetCurrentSessionCrossSigningInfoUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the active session when getting cross signing info then the result is correct`() {
|
||||||
|
val sessionParams = mockk<SessionParams>()
|
||||||
|
every { sessionParams.deviceId } returns A_DEVICE_ID
|
||||||
|
fakeActiveSessionHolder.fakeSession.givenSessionParams(sessionParams)
|
||||||
|
val isCrossSigningInitialized = true
|
||||||
|
fakeActiveSessionHolder.fakeSession
|
||||||
|
.fakeCryptoService
|
||||||
|
.fakeCrossSigningService
|
||||||
|
.givenIsCrossSigningInitializedReturns(isCrossSigningInitialized)
|
||||||
|
val isCrossSigningVerified = true
|
||||||
|
fakeActiveSessionHolder.fakeSession
|
||||||
|
.fakeCryptoService
|
||||||
|
.fakeCrossSigningService
|
||||||
|
.givenIsCrossSigningVerifiedReturns(isCrossSigningVerified)
|
||||||
|
val expectedResult = CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
isCrossSigningInitialized = isCrossSigningInitialized,
|
||||||
|
isCrossSigningVerified = isCrossSigningVerified
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = getCurrentSessionCrossSigningInfoUseCase.execute()
|
||||||
|
|
||||||
|
result shouldBeEqualTo expectedResult
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
class GetEncryptionTrustLevelForCurrentDeviceUseCaseTest {
|
||||||
|
|
||||||
|
private val getEncryptionTrustLevelForCurrentDeviceUseCase = GetEncryptionTrustLevelForCurrentDeviceUseCase()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given in legacy mode when computing trust level then device is trusted`() {
|
||||||
|
val trustMSK = false
|
||||||
|
val legacyMode = true
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given trustMSK is true and not in legacy mode when computing trust level then device is trusted`() {
|
||||||
|
val trustMSK = true
|
||||||
|
val legacyMode = false
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given trustMSK is false and not in legacy mode when computing trust level then device is unverified`() {
|
||||||
|
val trustMSK = false
|
||||||
|
val legacyMode = false
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Warning
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
private const val A_DEVICE_ID = "device-id"
|
||||||
|
private const val A_DEVICE_ID_2 = "device-id-2"
|
||||||
|
|
||||||
|
class GetEncryptionTrustLevelForDeviceUseCaseTest {
|
||||||
|
|
||||||
|
private val getEncryptionTrustLevelForCurrentDeviceUseCase = mockk<GetEncryptionTrustLevelForCurrentDeviceUseCase>()
|
||||||
|
private val getEncryptionTrustLevelForOtherDeviceUseCase = mockk<GetEncryptionTrustLevelForOtherDeviceUseCase>()
|
||||||
|
|
||||||
|
private val getEncryptionTrustLevelForDeviceUseCase = GetEncryptionTrustLevelForDeviceUseCase(
|
||||||
|
getEncryptionTrustLevelForCurrentDeviceUseCase = getEncryptionTrustLevelForCurrentDeviceUseCase,
|
||||||
|
getEncryptionTrustLevelForOtherDeviceUseCase = getEncryptionTrustLevelForOtherDeviceUseCase,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given is current device when computing trust level then the correct sub use case result is returned`() {
|
||||||
|
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
isCrossSigningInitialized = true,
|
||||||
|
isCrossSigningVerified = false
|
||||||
|
)
|
||||||
|
val cryptoDeviceInfo = givenCryptoDeviceInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
trustLevel = null
|
||||||
|
)
|
||||||
|
val trustLevel = RoomEncryptionTrustLevel.Trusted
|
||||||
|
every { getEncryptionTrustLevelForCurrentDeviceUseCase.execute(any(), any()) } returns trustLevel
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
|
|
||||||
|
result shouldBeEqualTo trustLevel
|
||||||
|
verify {
|
||||||
|
getEncryptionTrustLevelForCurrentDeviceUseCase.execute(
|
||||||
|
trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified,
|
||||||
|
legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given is not current device when computing trust level then the correct sub use case result is returned`() {
|
||||||
|
val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
isCrossSigningInitialized = true,
|
||||||
|
isCrossSigningVerified = false
|
||||||
|
)
|
||||||
|
val cryptoDeviceInfo = givenCryptoDeviceInfo(
|
||||||
|
deviceId = A_DEVICE_ID_2,
|
||||||
|
trustLevel = null
|
||||||
|
)
|
||||||
|
val trustLevel = RoomEncryptionTrustLevel.Trusted
|
||||||
|
every { getEncryptionTrustLevelForOtherDeviceUseCase.execute(any(), any(), any()) } returns trustLevel
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
|
|
||||||
|
result shouldBeEqualTo trustLevel
|
||||||
|
verify {
|
||||||
|
getEncryptionTrustLevelForOtherDeviceUseCase.execute(
|
||||||
|
trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified,
|
||||||
|
legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized,
|
||||||
|
deviceTrustLevel = cryptoDeviceInfo.trustLevel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenCurrentSessionCrossSigningInfo(
|
||||||
|
deviceId: String?,
|
||||||
|
isCrossSigningInitialized: Boolean,
|
||||||
|
isCrossSigningVerified: Boolean
|
||||||
|
): CurrentSessionCrossSigningInfo {
|
||||||
|
return CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = deviceId,
|
||||||
|
isCrossSigningInitialized = isCrossSigningInitialized,
|
||||||
|
isCrossSigningVerified = isCrossSigningVerified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenCryptoDeviceInfo(
|
||||||
|
deviceId: String,
|
||||||
|
trustLevel: DeviceTrustLevel?
|
||||||
|
): CryptoDeviceInfo {
|
||||||
|
return CryptoDeviceInfo(
|
||||||
|
userId = "",
|
||||||
|
deviceId = deviceId,
|
||||||
|
trustLevel = trustLevel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
|
||||||
|
class GetEncryptionTrustLevelForOtherDeviceUseCaseTest {
|
||||||
|
|
||||||
|
private val getEncryptionTrustLevelForOtherDeviceUseCase = GetEncryptionTrustLevelForOtherDeviceUseCase()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given in legacy mode and device locally verified when computing trust level then device is trusted`() {
|
||||||
|
val trustMSK = false
|
||||||
|
val legacyMode = true
|
||||||
|
val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = true, crossSigningVerified = false)
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given in legacy mode and device not locally verified when computing trust level then device is unverified`() {
|
||||||
|
val trustMSK = false
|
||||||
|
val legacyMode = true
|
||||||
|
val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = false)
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given trustMSK is true and not in legacy mode and device cross signing verified when computing trust level then device is trusted`() {
|
||||||
|
val trustMSK = true
|
||||||
|
val legacyMode = false
|
||||||
|
val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = true)
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given trustMSK is true and not in legacy mode and device locally verified when computing trust level then device has default trust level`() {
|
||||||
|
val trustMSK = true
|
||||||
|
val legacyMode = false
|
||||||
|
val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = true, crossSigningVerified = false)
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given trustMSK is true and not in legacy mode and device not verified when computing trust level then device is unverified`() {
|
||||||
|
val trustMSK = true
|
||||||
|
val legacyMode = false
|
||||||
|
val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = false)
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given trustMSK is false and not in legacy mode when computing trust level then device has default trust level`() {
|
||||||
|
val trustMSK = false
|
||||||
|
val legacyMode = false
|
||||||
|
val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = false)
|
||||||
|
|
||||||
|
val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
|
||||||
|
|
||||||
|
result shouldBeEqualTo RoomEncryptionTrustLevel.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenDeviceTrustLevel(locallyVerified: Boolean?, crossSigningVerified: Boolean): DeviceTrustLevel {
|
||||||
|
return DeviceTrustLevel(
|
||||||
|
crossSigningVerified = crossSigningVerified,
|
||||||
|
locallyVerified = locallyVerified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||||
|
import im.vector.app.test.fakes.FakeContext
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val A_SESSION_ID = "session_id"
|
||||||
|
|
||||||
|
class VectorSettingsDevicesViewNavigatorTest {
|
||||||
|
|
||||||
|
private val context = FakeContext()
|
||||||
|
private val vectorSettingsDevicesViewNavigator = VectorSettingsDevicesViewNavigator()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
mockkObject(SessionOverviewActivity.Companion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a session id when navigating to overview then it starts the correct activity`() {
|
||||||
|
val intent = givenIntentForSessionOverview(A_SESSION_ID)
|
||||||
|
context.givenStartActivity(intent)
|
||||||
|
|
||||||
|
vectorSettingsDevicesViewNavigator.navigateToSessionOverview(context.instance, A_SESSION_ID)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.instance.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenIntentForSessionOverview(sessionId: String): Intent {
|
||||||
|
val intent = mockk<Intent>()
|
||||||
|
every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns intent
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* 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.overview
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
|
import im.vector.app.features.settings.devices.CurrentSessionCrossSigningInfo
|
||||||
|
import im.vector.app.features.settings.devices.DeviceFullInfo
|
||||||
|
import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
|
||||||
|
import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
|
||||||
|
import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fakes.FakeFlowLiveDataConversions
|
||||||
|
import im.vector.app.test.fakes.givenAsFlow
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
|
private const val A_DEVICE_ID = "device-id"
|
||||||
|
private const val A_TIMESTAMP = 123L
|
||||||
|
|
||||||
|
class GetDeviceFullInfoUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeActiveSessionHolder = FakeActiveSessionHolder()
|
||||||
|
private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
|
||||||
|
private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
|
||||||
|
private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
|
||||||
|
private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
|
||||||
|
|
||||||
|
private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase(
|
||||||
|
activeSessionHolder = fakeActiveSessionHolder.instance,
|
||||||
|
getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
|
||||||
|
getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
|
||||||
|
checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
fakeFlowLiveDataConversions.setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given current session and info for device when getting device info then the result is correct`() = runTest {
|
||||||
|
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 trustLevel = givenTrustLevel(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||||
|
val isInactive = false
|
||||||
|
every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive
|
||||||
|
|
||||||
|
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
|
||||||
|
|
||||||
|
deviceFullInfo shouldBeEqualTo Optional(
|
||||||
|
DeviceFullInfo(
|
||||||
|
deviceInfo = deviceInfo,
|
||||||
|
cryptoDeviceInfo = cryptoDeviceInfo,
|
||||||
|
trustLevelForShield = trustLevel,
|
||||||
|
isInactive = isInactive,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given current session and no info for device when getting device info then the result is null`() = runTest {
|
||||||
|
givenCurrentSessionCrossSigningInfo()
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(null))
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(null))
|
||||||
|
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
|
||||||
|
|
||||||
|
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
|
||||||
|
|
||||||
|
deviceFullInfo shouldBeEqualTo Optional(null)
|
||||||
|
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
|
||||||
|
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
|
||||||
|
verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given no current session when getting device info then the result is empty`() = runTest {
|
||||||
|
fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
|
||||||
|
|
||||||
|
val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
|
||||||
|
|
||||||
|
deviceFullInfo shouldBeEqualTo null
|
||||||
|
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
|
||||||
|
val currentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(
|
||||||
|
deviceId = A_DEVICE_ID,
|
||||||
|
isCrossSigningInitialized = true,
|
||||||
|
isCrossSigningVerified = false
|
||||||
|
)
|
||||||
|
every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns currentSessionCrossSigningInfo
|
||||||
|
return currentSessionCrossSigningInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenTrustLevel(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel {
|
||||||
|
val trustLevel = RoomEncryptionTrustLevel.Trusted
|
||||||
|
every { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) } returns trustLevel
|
||||||
|
return trustLevel
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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.overview
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.test.MvRxTestRule
|
||||||
|
import im.vector.app.features.settings.devices.DeviceFullInfo
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import im.vector.app.test.test
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
|
private const val A_SESSION_ID = "session-id"
|
||||||
|
|
||||||
|
class SessionOverviewViewModelTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher())
|
||||||
|
|
||||||
|
private val args = SessionOverviewArgs(
|
||||||
|
deviceId = A_SESSION_ID
|
||||||
|
)
|
||||||
|
private val fakeSession = FakeSession()
|
||||||
|
private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
|
||||||
|
|
||||||
|
private fun createViewModel() = SessionOverviewViewModel(
|
||||||
|
initialState = SessionOverviewViewState(args),
|
||||||
|
session = fakeSession,
|
||||||
|
getDeviceFullInfoUseCase = getDeviceFullInfoUseCase
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the viewModel has been initialized then viewState is updated with session info`() {
|
||||||
|
val sessionParams = givenIdForSession(A_SESSION_ID)
|
||||||
|
val deviceFullInfo = mockk<DeviceFullInfo>()
|
||||||
|
every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(Optional(deviceFullInfo))
|
||||||
|
val expectedState = SessionOverviewViewState(
|
||||||
|
deviceId = A_SESSION_ID,
|
||||||
|
isCurrentSession = true,
|
||||||
|
deviceInfo = Success(deviceFullInfo)
|
||||||
|
)
|
||||||
|
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
|
||||||
|
viewModel.test()
|
||||||
|
.assertLatestState { state -> state == expectedState }
|
||||||
|
.finish()
|
||||||
|
verify { sessionParams.deviceId }
|
||||||
|
verify { getDeviceFullInfoUseCase.execute(A_SESSION_ID) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenIdForSession(deviceId: String): SessionParams {
|
||||||
|
val sessionParams = mockk<SessionParams>()
|
||||||
|
every { sessionParams.deviceId } returns deviceId
|
||||||
|
fakeSession.givenSessionParams(sessionParams)
|
||||||
|
return sessionParams
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ package im.vector.app.test
|
|||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
|
|
||||||
private val testDispatcher = UnconfinedTestDispatcher()
|
internal val testDispatcher = UnconfinedTestDispatcher()
|
||||||
|
|
||||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(
|
||||||
io = testDispatcher,
|
io = testDispatcher,
|
||||||
|
@ -33,4 +33,8 @@ class FakeActiveSessionHolder(
|
|||||||
fun expectSetsActiveSession(session: Session) {
|
fun expectSetsActiveSession(session: Session) {
|
||||||
justRun { instance.setActiveSession(session) }
|
justRun { instance.setActiveSession(session) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenGetSafeActiveSessionReturns(session: Session?) {
|
||||||
|
every { instance.getSafeActiveSession() } returns session
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,14 @@ package im.vector.app.test.fakes
|
|||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.runs
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
class FakeContext(
|
class FakeContext(
|
||||||
@ -67,4 +70,8 @@ class FakeContext(
|
|||||||
connectivityManager.givenHasActiveConnection()
|
connectivityManager.givenHasActiveConnection()
|
||||||
givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance)
|
givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenStartActivity(intent: Intent) {
|
||||||
|
every { instance.startActivity(intent) } just runs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.test.fakes
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
|
|
||||||
|
class FakeCrossSigningService : CrossSigningService by mockk() {
|
||||||
|
|
||||||
|
fun givenIsCrossSigningInitializedReturns(isInitialized: Boolean) {
|
||||||
|
every { isCrossSigningInitialized() } returns isInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenIsCrossSigningVerifiedReturns(isVerified: Boolean) {
|
||||||
|
every { isCrossSigningVerified() } returns isVerified
|
||||||
|
}
|
||||||
|
}
|
@ -20,11 +20,19 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
class FakeCryptoService : CryptoService by mockk() {
|
class FakeCryptoService(
|
||||||
|
val fakeCrossSigningService: FakeCrossSigningService = FakeCrossSigningService()
|
||||||
|
) : CryptoService by mockk() {
|
||||||
|
|
||||||
var roomKeysExport = ByteArray(size = 1)
|
var roomKeysExport = ByteArray(size = 1)
|
||||||
var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>()
|
var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>()
|
||||||
|
var cryptoDeviceInfoWithIdLiveData: MutableLiveData<Optional<CryptoDeviceInfo>> = MutableLiveData()
|
||||||
|
var myDevicesInfoWithIdLiveData: MutableLiveData<Optional<DeviceInfo>> = MutableLiveData()
|
||||||
|
|
||||||
|
override fun crossSigningService() = fakeCrossSigningService
|
||||||
|
|
||||||
override suspend fun exportRoomKeys(password: String) = roomKeysExport
|
override suspend fun exportRoomKeys(password: String) = roomKeysExport
|
||||||
|
|
||||||
@ -35,4 +43,8 @@ class FakeCryptoService : CryptoService by mockk() {
|
|||||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
|
override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
|
||||||
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
|
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData
|
||||||
|
|
||||||
|
override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,6 @@ class FakeFlowLiveDataConversions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> LiveData<T>.givenAsFlowReturns(value: T) {
|
fun <T> LiveData<T>.givenAsFlow() {
|
||||||
every { asFlow() } returns flowOf(value)
|
every { asFlow() } returns flowOf(value!!)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import io.mockk.coJustRun
|
|||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.getRoomSummary
|
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
@ -71,6 +72,10 @@ class FakeSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenSessionParams(sessionParams: SessionParams) {
|
||||||
|
every { this@FakeSession.sessionParams } returns sessionParams
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply {
|
fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply {
|
||||||
|
Loading…
Reference in New Issue
Block a user