Merge pull request #7171 from vector-im/feature/ons/device_manager_security_sessions
[Device Manager] Unverified and inactive sessions list (PSG-698, PSG-696)
This commit is contained in:
commit
6c79aae3aa
changelog.d
library/ui-strings/src/main/res/values
vector/src
main/java/im/vector/app/features/settings/devices/v2
DeviceFullInfo.ktDevicesViewModel.ktGetDeviceFullInfoListUseCase.ktVectorSettingsDevicesFragment.ktVectorSettingsDevicesViewNavigator.kt
filter
list
othersessions
OtherSessionsActivity.ktOtherSessionsArgs.ktOtherSessionsFragment.ktOtherSessionsViewModel.ktOtherSessionsViewState.kt
overview
test/java/im/vector/app/features/settings/devices/v2
1
changelog.d/7170.wip
Normal file
1
changelog.d/7170.wip
Normal file
@ -0,0 +1 @@
|
||||
[Device Manager] Unverified and inactive sessions list
|
@ -3244,6 +3244,7 @@
|
||||
<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_current_session">Unverified · Your current session</string>
|
||||
<!-- Example: Inactive for 90+ days (Dec 25, 2021) -->
|
||||
<plurals name="device_manager_other_sessions_description_inactive">
|
||||
<item quantity="one">Inactive for %1$d+ day (%2$s)</item>
|
||||
|
@ -25,4 +25,5 @@ data class DeviceFullInfo(
|
||||
val cryptoDeviceInfo: CryptoDeviceInfo?,
|
||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel,
|
||||
val isInactive: Boolean,
|
||||
val isCurrentDevice: Boolean,
|
||||
)
|
||||
|
@ -74,7 +74,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||
.execute { async ->
|
||||
if (async is Success) {
|
||||
val deviceFullInfoList = async.invoke()
|
||||
val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.isVerified.orFalse() }
|
||||
val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse() }
|
||||
val inactiveSessionsCount = deviceFullInfoList.count { it.isInactive }
|
||||
copy(
|
||||
devices = async,
|
||||
|
@ -71,7 +71,8 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
|
||||
val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
|
||||
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
|
||||
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive)
|
||||
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoDeviceInfo?.deviceId
|
||||
DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive, isCurrentDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,11 @@ import im.vector.app.core.resources.DrawableProvider
|
||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
|
||||
import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
|
||||
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.SecurityRecommendationView
|
||||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||
import javax.inject.Inject
|
||||
@ -83,6 +85,7 @@ class VectorSettingsDevicesFragment :
|
||||
initLearnMoreButtons()
|
||||
initWaitingView()
|
||||
initOtherSessionsView()
|
||||
initSecurityRecommendationsView()
|
||||
observeViewEvents()
|
||||
}
|
||||
|
||||
@ -124,6 +127,29 @@ class VectorSettingsDevicesFragment :
|
||||
views.deviceListOtherSessions.callback = this
|
||||
}
|
||||
|
||||
private fun initSecurityRecommendationsView() {
|
||||
views.deviceListUnverifiedSessionsRecommendation.callback = object : SecurityRecommendationView.Callback {
|
||||
override fun onViewAllClicked() {
|
||||
viewNavigator.navigateToOtherSessions(
|
||||
requireActivity(),
|
||||
R.string.device_manager_header_section_security_recommendations_title,
|
||||
DeviceManagerFilterType.UNVERIFIED,
|
||||
excludeCurrentDevice = false
|
||||
)
|
||||
}
|
||||
}
|
||||
views.deviceListInactiveSessionsRecommendation.callback = object : SecurityRecommendationView.Callback {
|
||||
override fun onViewAllClicked() {
|
||||
viewNavigator.navigateToOtherSessions(
|
||||
requireActivity(),
|
||||
R.string.device_manager_header_section_security_recommendations_title,
|
||||
DeviceManagerFilterType.INACTIVE,
|
||||
excludeCurrentDevice = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
cleanUpLearnMoreButtonsListeners()
|
||||
super.onDestroyView()
|
||||
@ -262,6 +288,11 @@ class VectorSettingsDevicesFragment :
|
||||
}
|
||||
|
||||
override fun onViewAllOtherSessionsClicked() {
|
||||
viewNavigator.navigateToOtherSessions(requireActivity())
|
||||
viewNavigator.navigateToOtherSessions(
|
||||
context = requireActivity(),
|
||||
titleResourceId = R.string.device_manager_sessions_other_title,
|
||||
defaultFilter = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
excludeCurrentDevice = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
|
||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||
import javax.inject.Inject
|
||||
@ -27,7 +28,14 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
|
||||
context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
|
||||
}
|
||||
|
||||
fun navigateToOtherSessions(context: Context) {
|
||||
context.startActivity(OtherSessionsActivity.newIntent(context))
|
||||
fun navigateToOtherSessions(
|
||||
context: Context,
|
||||
titleResourceId: Int,
|
||||
defaultFilter: DeviceManagerFilterType,
|
||||
excludeCurrentDevice: Boolean,
|
||||
) {
|
||||
context.startActivity(
|
||||
OtherSessionsActivity.newIntent(context, titleResourceId, defaultFilter, excludeCurrentDevice)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ class FilterDevicesUseCase @Inject constructor() {
|
||||
.filter {
|
||||
when (filterType) {
|
||||
DeviceManagerFilterType.ALL_SESSIONS -> true
|
||||
DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse()
|
||||
DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse()
|
||||
DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
|
||||
DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
|
||||
DeviceManagerFilterType.INACTIVE -> it.isInactive
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2.list
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
@ -45,6 +46,10 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
|
||||
@EpoxyAttribute
|
||||
var sessionDescription: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@ColorInt
|
||||
var sessionDescriptionColor: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var sessionDescriptionDrawable: Drawable? = null
|
||||
|
||||
@ -82,6 +87,9 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
|
||||
holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel)
|
||||
holder.otherSessionNameTextView.text = sessionName
|
||||
holder.otherSessionDescriptionTextView.text = sessionDescription
|
||||
sessionDescriptionColor?.let {
|
||||
holder.otherSessionDescriptionTextView.setTextColor(it)
|
||||
}
|
||||
holder.otherSessionDescriptionTextView.setCompoundDrawablesWithIntrinsicBounds(sessionDescriptionDrawable, null, null, null)
|
||||
}
|
||||
|
||||
|
@ -53,20 +53,14 @@ class OtherSessionsController @Inject constructor(
|
||||
data.forEach { device ->
|
||||
val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
|
||||
val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
|
||||
val description = if (device.isInactive) {
|
||||
stringProvider.getQuantityString(
|
||||
R.plurals.device_manager_other_sessions_description_inactive,
|
||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||
formattedLastActivityDate
|
||||
)
|
||||
} else if (device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
|
||||
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
|
||||
val description = calculateDescription(device, formattedLastActivityDate)
|
||||
val descriptionColor = if (device.isCurrentDevice) {
|
||||
host.colorProvider.getColorFromAttribute(R.attr.colorError)
|
||||
} else {
|
||||
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
|
||||
host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||
}
|
||||
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||
val descriptionDrawable = if (device.isInactive) drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) else null
|
||||
val drawableColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
|
||||
val descriptionDrawable = if (device.isInactive) host.drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) else null
|
||||
|
||||
otherSessionItem {
|
||||
id(device.deviceInfo.deviceId)
|
||||
@ -75,10 +69,33 @@ class OtherSessionsController @Inject constructor(
|
||||
sessionName(device.deviceInfo.displayName)
|
||||
sessionDescription(description)
|
||||
sessionDescriptionDrawable(descriptionDrawable)
|
||||
sessionDescriptionColor(descriptionColor)
|
||||
stringProvider(this@OtherSessionsController.stringProvider)
|
||||
clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateDescription(device: DeviceFullInfo, formattedLastActivityDate: String): String {
|
||||
return when {
|
||||
device.isInactive -> {
|
||||
stringProvider.getQuantityString(
|
||||
R.plurals.device_manager_other_sessions_description_inactive,
|
||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
|
||||
formattedLastActivityDate
|
||||
)
|
||||
}
|
||||
device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted -> {
|
||||
stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
|
||||
}
|
||||
device.isCurrentDevice -> {
|
||||
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified_current_session)
|
||||
}
|
||||
else -> {
|
||||
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SecurityRecommendationView.kt
14
vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SecurityRecommendationView.kt
@ -31,7 +31,12 @@ class SecurityRecommendationView @JvmOverloads constructor(
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
interface Callback {
|
||||
fun onViewAllClicked()
|
||||
}
|
||||
|
||||
private val views: ViewSecurityRecommendationBinding
|
||||
var callback: Callback? = null
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.view_security_recommendation, this)
|
||||
@ -47,6 +52,10 @@ class SecurityRecommendationView @JvmOverloads constructor(
|
||||
setDescription(it)
|
||||
setImage(it)
|
||||
}
|
||||
|
||||
views.recommendationViewAllButton.setOnClickListener {
|
||||
callback?.onViewAllClicked()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setTitle(typedArray: TypedArray) {
|
||||
@ -78,4 +87,9 @@ class SecurityRecommendationView @JvmOverloads constructor(
|
||||
setDescription(viewState.description)
|
||||
setCount(viewState.sessionsCount)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
callback = null
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,12 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OtherSessionsActivity : SimpleFragmentActivity() {
|
||||
@ -35,14 +38,23 @@ class OtherSessionsActivity : SimpleFragmentActivity() {
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
container = views.container,
|
||||
fragmentClass = OtherSessionsFragment::class.java
|
||||
fragmentClass = OtherSessionsFragment::class.java,
|
||||
params = intent.getParcelableExtra(Mavericks.KEY_ARG)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context): Intent {
|
||||
return Intent(context, OtherSessionsActivity::class.java)
|
||||
fun newIntent(
|
||||
context: Context,
|
||||
@StringRes
|
||||
titleResourceId: Int,
|
||||
defaultFilter: DeviceManagerFilterType,
|
||||
excludeCurrentDevice: Boolean,
|
||||
): Intent {
|
||||
return Intent(context, OtherSessionsActivity::class.java).apply {
|
||||
putExtra(Mavericks.KEY_ARG, OtherSessionsArgs(titleResourceId, defaultFilter, excludeCurrentDevice))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsArgs.kt
Normal file
30
vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsArgs.kt
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.settings.devices.v2.othersessions
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class OtherSessionsArgs(
|
||||
@StringRes
|
||||
val titleResourceId: Int,
|
||||
val defaultFilter: DeviceManagerFilterType,
|
||||
val excludeCurrentDevice: Boolean,
|
||||
) : Parcelable
|
@ -22,6 +22,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@ -46,6 +47,7 @@ class OtherSessionsFragment :
|
||||
OtherSessionsView.Callback {
|
||||
|
||||
private val viewModel: OtherSessionsViewModel by fragmentViewModel()
|
||||
private val args: OtherSessionsArgs by args()
|
||||
|
||||
@Inject lateinit var colorProvider: ColorProvider
|
||||
|
||||
@ -57,7 +59,7 @@ class OtherSessionsFragment :
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupToolbar(views.otherSessionsToolbar).allowBack()
|
||||
setupToolbar(views.otherSessionsToolbar).setTitle(args.titleResourceId).allowBack()
|
||||
observeViewEvents()
|
||||
initFilterView()
|
||||
}
|
||||
@ -85,6 +87,10 @@ class OtherSessionsFragment :
|
||||
}
|
||||
|
||||
views.deviceListOtherSessions.callback = this
|
||||
|
||||
if (args.defaultFilter != DeviceManagerFilterType.ALL_SESSIONS) {
|
||||
viewModel.handle(OtherSessionsAction.FilterDevices(args.defaultFilter))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBottomSheetResult(resultCode: Int, data: Any?) {
|
||||
|
@ -30,7 +30,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
class OtherSessionsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: OtherSessionsViewState,
|
||||
@Assisted private val initialState: OtherSessionsViewState,
|
||||
activeSessionHolder: ActiveSessionHolder,
|
||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||
refreshDevicesUseCase: RefreshDevicesUseCase
|
||||
@ -55,7 +55,7 @@ class OtherSessionsViewModel @AssistedInject constructor(
|
||||
observeDevicesJob?.cancel()
|
||||
observeDevicesJob = getDeviceFullInfoListUseCase.execute(
|
||||
filterType = currentFilter,
|
||||
excludeCurrentDevice = true
|
||||
excludeCurrentDevice = initialState.excludeCurrentDevice
|
||||
)
|
||||
.execute { async ->
|
||||
copy(
|
||||
|
@ -25,4 +25,8 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
data class OtherSessionsViewState(
|
||||
val devices: Async<List<DeviceFullInfo>> = Uninitialized,
|
||||
val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
|
||||
) : MavericksState
|
||||
val excludeCurrentDevice: Boolean = false,
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: OtherSessionsArgs) : this(excludeCurrentDevice = args.excludeCurrentDevice)
|
||||
}
|
||||
|
4
vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
4
vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@ -48,11 +48,13 @@ class GetDeviceFullInfoUseCase @Inject constructor(
|
||||
val fullInfo = if (info != null && cryptoInfo != null) {
|
||||
val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
|
||||
val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
|
||||
val isCurrentDevice = currentSessionCrossSigningInfo.deviceId == cryptoInfo.deviceId
|
||||
DeviceFullInfo(
|
||||
deviceInfo = info,
|
||||
cryptoDeviceInfo = cryptoInfo,
|
||||
roomEncryptionTrustLevel = roomEncryptionTrustLevel,
|
||||
isInactive = isInactive
|
||||
isInactive = isInactive,
|
||||
isCurrentDevice = isCurrentDevice,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import android.os.SystemClock
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.test.MvRxTestRule
|
||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||
@ -30,11 +31,16 @@ import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkAll
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
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
|
||||
|
||||
@ -62,6 +68,17 @@ class DevicesViewModelTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(SystemClock::class)
|
||||
every { SystemClock.elapsedRealtime() } returns 1234
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the viewModel when initializing it then verification listener is added`() {
|
||||
// Given
|
||||
@ -216,21 +233,23 @@ class DevicesViewModelTest {
|
||||
*/
|
||||
private fun givenDeviceFullInfoList(): List<DeviceFullInfo> {
|
||||
val verifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||
every { verifiedCryptoDeviceInfo.isVerified } returns true
|
||||
every { verifiedCryptoDeviceInfo.trustLevel } returns DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||
val unverifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
|
||||
every { unverifiedCryptoDeviceInfo.isVerified } returns false
|
||||
every { unverifiedCryptoDeviceInfo.trustLevel } returns DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||
|
||||
val deviceFullInfo1 = DeviceFullInfo(
|
||||
deviceInfo = mockk(),
|
||||
cryptoDeviceInfo = verifiedCryptoDeviceInfo,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = false
|
||||
isInactive = false,
|
||||
isCurrentDevice = true
|
||||
)
|
||||
val deviceFullInfo2 = DeviceFullInfo(
|
||||
deviceInfo = mockk(),
|
||||
cryptoDeviceInfo = unverifiedCryptoDeviceInfo,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = true
|
||||
isInactive = true,
|
||||
isCurrentDevice = false
|
||||
)
|
||||
val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
|
||||
val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
|
||||
|
10
vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
10
vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
@ -109,19 +109,22 @@ class GetDeviceFullInfoListUseCaseTest {
|
||||
deviceInfo = deviceInfo1,
|
||||
cryptoDeviceInfo = cryptoDeviceInfo1,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = true
|
||||
isInactive = true,
|
||||
isCurrentDevice = true
|
||||
)
|
||||
val expectedResult2 = DeviceFullInfo(
|
||||
deviceInfo = deviceInfo2,
|
||||
cryptoDeviceInfo = cryptoDeviceInfo2,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = false
|
||||
isInactive = false,
|
||||
isCurrentDevice = false
|
||||
)
|
||||
val expectedResult3 = DeviceFullInfo(
|
||||
deviceInfo = deviceInfo3,
|
||||
cryptoDeviceInfo = cryptoDeviceInfo3,
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = false
|
||||
isInactive = false,
|
||||
isCurrentDevice = false
|
||||
)
|
||||
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
|
||||
every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult
|
||||
@ -163,6 +166,7 @@ class GetDeviceFullInfoListUseCaseTest {
|
||||
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
|
||||
val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
|
||||
every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
|
||||
every { currentSessionCrossSigningInfo.deviceId } returns A_DEVICE_ID_1
|
||||
return currentSessionCrossSigningInfo
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import android.content.Intent
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
|
||||
import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
|
||||
import im.vector.app.test.fakes.FakeContext
|
||||
@ -30,6 +31,8 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
private const val A_SESSION_ID = "session_id"
|
||||
private const val A_TITLE_RESOURCE_ID = 1234
|
||||
private val A_DEFAULT_FILTER = DeviceManagerFilterType.INACTIVE
|
||||
|
||||
class VectorSettingsDevicesViewNavigatorTest {
|
||||
|
||||
@ -61,10 +64,10 @@ class VectorSettingsDevicesViewNavigatorTest {
|
||||
|
||||
@Test
|
||||
fun `given an intent when navigating to other sessions list then it starts the correct activity`() {
|
||||
val intent = givenIntentForOtherSessions()
|
||||
val intent = givenIntentForOtherSessions(A_TITLE_RESOURCE_ID, A_DEFAULT_FILTER, true)
|
||||
context.givenStartActivity(intent)
|
||||
|
||||
vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance)
|
||||
vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance, A_TITLE_RESOURCE_ID, A_DEFAULT_FILTER, true)
|
||||
|
||||
verify {
|
||||
context.instance.startActivity(intent)
|
||||
@ -77,9 +80,9 @@ class VectorSettingsDevicesViewNavigatorTest {
|
||||
return intent
|
||||
}
|
||||
|
||||
private fun givenIntentForOtherSessions(): Intent {
|
||||
private fun givenIntentForOtherSessions(titleResourceId: Int, defaultFilter: DeviceManagerFilterType, excludeCurrentDevice: Boolean): Intent {
|
||||
val intent = mockk<Intent>()
|
||||
every { OtherSessionsActivity.newIntent(context.instance) } returns intent
|
||||
every { OtherSessionsActivity.newIntent(context.instance, titleResourceId, defaultFilter, excludeCurrentDevice) } returns intent
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
12
vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt
12
vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt
@ -33,7 +33,8 @@ private val activeVerifiedDevice = DeviceFullInfo(
|
||||
trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = false
|
||||
isInactive = false,
|
||||
isCurrentDevice = true
|
||||
)
|
||||
private val inactiveVerifiedDevice = DeviceFullInfo(
|
||||
deviceInfo = DeviceInfo(deviceId = "INACTIVE_VERIFIED_DEVICE"),
|
||||
@ -43,7 +44,8 @@ private val inactiveVerifiedDevice = DeviceFullInfo(
|
||||
trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
|
||||
isInactive = true
|
||||
isInactive = true,
|
||||
isCurrentDevice = false
|
||||
)
|
||||
private val activeUnverifiedDevice = DeviceFullInfo(
|
||||
deviceInfo = DeviceInfo(deviceId = "ACTIVE_UNVERIFIED_DEVICE"),
|
||||
@ -53,7 +55,8 @@ private val activeUnverifiedDevice = DeviceFullInfo(
|
||||
trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = false
|
||||
isInactive = false,
|
||||
isCurrentDevice = false
|
||||
)
|
||||
private val inactiveUnverifiedDevice = DeviceFullInfo(
|
||||
deviceInfo = DeviceInfo(deviceId = "INACTIVE_UNVERIFIED_DEVICE"),
|
||||
@ -63,7 +66,8 @@ private val inactiveUnverifiedDevice = DeviceFullInfo(
|
||||
trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
|
||||
),
|
||||
roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
|
||||
isInactive = true
|
||||
isInactive = true,
|
||||
isCurrentDevice = false
|
||||
)
|
||||
|
||||
private val devices = listOf(
|
||||
|
@ -85,6 +85,7 @@ class GetDeviceFullInfoUseCaseTest {
|
||||
fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
|
||||
val trustLevel = givenTrustLevel(currentSessionCrossSigningInfo, cryptoDeviceInfo)
|
||||
val isInactive = false
|
||||
val isCurrentDevice = true
|
||||
every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive
|
||||
|
||||
// When
|
||||
@ -96,6 +97,7 @@ class GetDeviceFullInfoUseCaseTest {
|
||||
cryptoDeviceInfo = cryptoDeviceInfo,
|
||||
roomEncryptionTrustLevel = trustLevel,
|
||||
isInactive = isInactive,
|
||||
isCurrentDevice = isCurrentDevice
|
||||
)
|
||||
verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
|
||||
verify { getCurrentSessionCrossSigningInfoUseCase.execute() }
|
||||
|
Loading…
x
Reference in New Issue
Block a user