Merge pull request #7361 from vector-im/feature/mna/device-manager-unknown-verification-status

[Device management] Update the unknown verification status icon (PSG-824)
This commit is contained in:
Maxime NATUREL 2022-10-14 16:22:33 +02:00 committed by GitHub
commit 612d2e51e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 158 additions and 82 deletions

1
changelog.d/7327.wip Normal file
View File

@ -0,0 +1 @@
[Device management] Update the unknown verification status icon

View File

@ -3254,10 +3254,12 @@
<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_unknown">Unknown verification status</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_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_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_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_verification_status_detail_other_session_unverified">Verify or sign out from this session for best security and reliability.</string>
<string name="device_manager_verification_status_detail_other_session_unknown">Verify your current session to reveal this session\'s verification status.</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_other_sessions_view_all">View All (%1$d)</string> <string name="device_manager_other_sessions_view_all">View All (%1$d)</string>

View File

@ -146,6 +146,7 @@
<color name="shield_color_gray">#91A1C0</color> <color name="shield_color_gray">#91A1C0</color>
<color name="shield_color_warning">#FF4B55</color> <color name="shield_color_warning">#FF4B55</color>
<color name="shield_color_warning_background">#0FFF4B55</color> <color name="shield_color_warning_background">#0FFF4B55</color>
<color name="shield_color_unknown">@color/palette_gray_200</color>
<!-- Badge Colors --> <!-- Badge Colors -->
<attr name="vctr_badge_color_border" format="color" /> <attr name="vctr_badge_color_border" format="color" />

View File

@ -53,6 +53,8 @@ android {
// "pm clear" command after each test invocation. This command ensures // "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
vectorDrawables.useSupportLibrary = true
} }
testOptions { testOptions {

View File

@ -45,8 +45,8 @@ class ShieldImageView @JvmOverloads constructor(
RoomEncryptionTrustLevel.Default -> { RoomEncryptionTrustLevel.Default -> {
contentDescription = context.getString(R.string.a11y_trust_level_default) contentDescription = context.getString(R.string.a11y_trust_level_default)
setImageResource( setImageResource(
if (borderLess) R.drawable.ic_shield_black_no_border if (borderLess) R.drawable.ic_shield_unknown_no_border
else R.drawable.ic_shield_black else R.drawable.ic_shield_unknown
) )
} }
RoomEncryptionTrustLevel.Warning -> { RoomEncryptionTrustLevel.Warning -> {
@ -137,7 +137,7 @@ class ShieldImageView @JvmOverloads constructor(
@DrawableRes @DrawableRes
fun RoomEncryptionTrustLevel.toDrawableRes(): Int { fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
return when (this) { return when (this) {
RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_unknown
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge

View File

@ -57,7 +57,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
} else { } else {
emptyList() emptyList()
} }
filterDevicesUseCase.execute(deviceFullInfoList, filterType, excludedDeviceIds) filterDevicesUseCase.execute(currentSessionCrossSigningInfo, deviceFullInfoList, filterType, excludedDeviceIds)
} }
deviceFullInfoFlow.distinctUntilChanged() deviceFullInfoFlow.distinctUntilChanged()

View File

@ -44,6 +44,7 @@ import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INAC
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView 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.SecurityRecommendationViewState
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -164,12 +165,11 @@ class VectorSettingsDevicesFragment :
if (state.devices is Success) { if (state.devices is Success) {
val devices = state.devices() val devices = state.devices()
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
val currentDeviceInfo = devices?.firstOrNull { val currentDeviceInfo = devices?.firstOrNull { it.deviceInfo.deviceId == currentDeviceId }
it.deviceInfo.deviceId == currentDeviceId val isCurrentSessionVerified = currentDeviceInfo?.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted
}
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId } val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount) renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
renderCurrentDevice(currentDeviceInfo) renderCurrentDevice(currentDeviceInfo)
renderOtherSessionsView(otherDevices) renderOtherSessionsView(otherDevices)
} else { } else {
@ -181,14 +181,21 @@ class VectorSettingsDevicesFragment :
handleLoadingStatus(state.isLoading) handleLoadingStatus(state.isLoading)
} }
private fun renderSecurityRecommendations(inactiveSessionsCount: Int, unverifiedSessionsCount: Int) { private fun renderSecurityRecommendations(
if (unverifiedSessionsCount == 0 && inactiveSessionsCount == 0) { inactiveSessionsCount: Int,
unverifiedSessionsCount: Int,
isCurrentSessionVerified: Boolean,
) {
val isUnverifiedSectionVisible = unverifiedSessionsCount > 0 && isCurrentSessionVerified
val isInactiveSectionVisible = inactiveSessionsCount > 0
if (isUnverifiedSectionVisible.not() && isInactiveSectionVisible.not()) {
hideSecurityRecommendations() hideSecurityRecommendations()
} else { } else {
views.deviceListHeaderSectionSecurityRecommendations.isVisible = true views.deviceListHeaderSectionSecurityRecommendations.isVisible = true
views.deviceListSecurityRecommendationsDivider.isVisible = true views.deviceListSecurityRecommendationsDivider.isVisible = true
views.deviceListUnverifiedSessionsRecommendation.isVisible = unverifiedSessionsCount > 0
views.deviceListInactiveSessionsRecommendation.isVisible = inactiveSessionsCount > 0 views.deviceListUnverifiedSessionsRecommendation.isVisible = isUnverifiedSectionVisible
views.deviceListInactiveSessionsRecommendation.isVisible = isInactiveSectionVisible
val unverifiedSessionsViewState = SecurityRecommendationViewState( val unverifiedSessionsViewState = SecurityRecommendationViewState(
description = getString(R.string.device_manager_unverified_sessions_description), description = getString(R.string.device_manager_unverified_sessions_description),
sessionsCount = unverifiedSessionsCount, sessionsCount = unverifiedSessionsCount,
@ -206,11 +213,19 @@ class VectorSettingsDevicesFragment :
} }
} }
private fun hideUnverifiedSessionsRecommendation() {
views.deviceListUnverifiedSessionsRecommendation.isVisible = false
}
private fun hideInactiveSessionsRecommendation() {
views.deviceListInactiveSessionsRecommendation.isVisible = false
}
private fun hideSecurityRecommendations() { private fun hideSecurityRecommendations() {
views.deviceListHeaderSectionSecurityRecommendations.isVisible = false views.deviceListHeaderSectionSecurityRecommendations.isVisible = false
views.deviceListUnverifiedSessionsRecommendation.isVisible = false
views.deviceListInactiveSessionsRecommendation.isVisible = false
views.deviceListSecurityRecommendationsDivider.isVisible = false views.deviceListSecurityRecommendationsDivider.isVisible = false
hideUnverifiedSessionsRecommendation()
hideInactiveSessionsRecommendation()
} }
private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?) { private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?) {

View File

@ -17,22 +17,27 @@
package im.vector.app.features.settings.devices.v2.filter package im.vector.app.features.settings.devices.v2.filter
import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject import javax.inject.Inject
class FilterDevicesUseCase @Inject constructor() { class FilterDevicesUseCase @Inject constructor() {
fun execute( fun execute(
currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo,
devices: List<DeviceFullInfo>, devices: List<DeviceFullInfo>,
filterType: DeviceManagerFilterType, filterType: DeviceManagerFilterType,
excludedDeviceIds: List<String> = emptyList(), excludedDeviceIds: List<String> = emptyList(),
): List<DeviceFullInfo> { ): List<DeviceFullInfo> {
val isCurrentSessionVerified = currentSessionCrossSigningInfo.isCrossSigningVerified.orFalse()
return devices return devices
.filter { .filter {
when (filterType) { when (filterType) {
DeviceManagerFilterType.ALL_SESSIONS -> true DeviceManagerFilterType.ALL_SESSIONS -> true
DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse() // when current session is not verified, other session status cannot be trusted
DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse() DeviceManagerFilterType.VERIFIED -> isCurrentSessionVerified && it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
// when current session is not verified, other session status cannot be trusted
DeviceManagerFilterType.UNVERIFIED -> isCurrentSessionVerified && !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
DeviceManagerFilterType.INACTIVE -> it.isInactive DeviceManagerFilterType.INACTIVE -> it.isInactive
} }
} }

View File

@ -53,7 +53,7 @@ class OtherSessionsController @Inject constructor(
data.forEach { device -> data.forEach { device ->
val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME 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 formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
val description = calculateDescription(device, formattedLastActivityDate) val description = buildDescription(device, formattedLastActivityDate)
val descriptionColor = if (device.isCurrentDevice) { val descriptionColor = if (device.isCurrentDevice) {
host.colorProvider.getColorFromAttribute(R.attr.colorError) host.colorProvider.getColorFromAttribute(R.attr.colorError)
} else { } else {
@ -77,7 +77,7 @@ class OtherSessionsController @Inject constructor(
} }
} }
private fun calculateDescription(device: DeviceFullInfo, formattedLastActivityDate: String): String { private fun buildDescription(device: DeviceFullInfo, formattedLastActivityDate: String): String {
return when { return when {
device.isInactive -> { device.isInactive -> {
stringProvider.getQuantityString( stringProvider.getQuantityString(
@ -93,6 +93,9 @@ class OtherSessionsController @Inject constructor(
device.isCurrentDevice -> { device.isCurrentDevice -> {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified_current_session) stringProvider.getString(R.string.device_manager_other_sessions_description_unverified_current_session)
} }
device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Default -> {
stringProvider.getString(R.string.device_manager_session_last_activity, formattedLastActivityDate)
}
else -> { else -> {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate) stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
} }

View File

@ -90,10 +90,10 @@ class SessionInfoView @JvmOverloads constructor(
isVerifyButtonVisible: Boolean, isVerifyButtonVisible: Boolean,
) { ) {
views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel) views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
if (encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) { when {
renderCrossSigningVerified(isCurrentSession) encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted -> renderCrossSigningVerified(isCurrentSession)
} else { encryptionTrustLevel == RoomEncryptionTrustLevel.Default && !isCurrentSession -> renderCrossSigningUnknown()
renderCrossSigningUnverified(isCurrentSession, isVerifyButtonVisible) else -> renderCrossSigningUnverified(isCurrentSession, isVerifyButtonVisible)
} }
if (hasLearnMoreLink) { if (hasLearnMoreLink) {
appendLearnMoreToVerificationStatus() appendLearnMoreToVerificationStatus()
@ -142,6 +142,12 @@ class SessionInfoView @JvmOverloads constructor(
views.sessionInfoVerifySessionButton.isVisible = isVerifyButtonVisible views.sessionInfoVerifySessionButton.isVisible = isVerifyButtonVisible
} }
private fun renderCrossSigningUnknown() {
views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unknown)
views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_other_session_unknown)
views.sessionInfoVerifySessionButton.isVisible = false
}
private fun renderDeviceInfo(sessionName: String, deviceType: DeviceType, stringProvider: StringProvider) { private fun renderDeviceInfo(sessionName: String, deviceType: DeviceType, stringProvider: StringProvider) {
setDeviceTypeIconUseCase.execute(deviceType, views.sessionInfoDeviceTypeImageView, stringProvider) setDeviceTypeIconUseCase.execute(deviceType, views.sessionInfoDeviceTypeImageView, stringProvider)
views.sessionInfoNameTextView.text = sessionName views.sessionInfoNameTextView.text = sessionName
@ -155,34 +161,31 @@ class SessionInfoView @JvmOverloads constructor(
drawableProvider: DrawableProvider, drawableProvider: DrawableProvider,
colorProvider: ColorProvider, colorProvider: ColorProvider,
) { ) {
deviceInfo.lastSeenTs if (deviceInfo.lastSeenTs != null && isLastSeenDetailsVisible) {
?.takeIf { isLastSeenDetailsVisible } val timestamp = deviceInfo.lastSeenTs
?.let { timestamp -> views.sessionInfoLastActivityTextView.isVisible = true
views.sessionInfoLastActivityTextView.isVisible = true views.sessionInfoLastActivityTextView.text = if (isInactive) {
views.sessionInfoLastActivityTextView.text = if (isInactive) { val formattedTs = dateFormatter.format(timestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)
val formattedTs = dateFormatter.format(timestamp, DateFormatKind.TIMELINE_DAY_DIVIDER) context.resources.getQuantityString(
context.resources.getQuantityString( R.plurals.device_manager_other_sessions_description_inactive,
R.plurals.device_manager_other_sessions_description_inactive, SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS, SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS, formattedTs
formattedTs )
) } else {
} else { val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME) context.getString(R.string.device_manager_session_last_activity, formattedTs)
context.getString(R.string.device_manager_session_last_activity, formattedTs) }
} val drawable = if (isInactive) {
val drawable = if (isInactive) { val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor)
drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor) } else {
} else { null
null }
} views.sessionInfoLastActivityTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
views.sessionInfoLastActivityTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) } else {
} views.sessionInfoLastActivityTextView.isGone = true
?: run { }
views.sessionInfoLastActivityTextView.isGone = true
}
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible }) views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible })
} }

View File

@ -24,7 +24,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
@ -42,7 +41,6 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.FragmentSessionOverviewBinding import im.vector.app.databinding.FragmentSessionOverviewBinding
import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
import im.vector.app.features.workers.signout.SignOutUiWorker import im.vector.app.features.workers.signout.SignOutUiWorker
@ -181,11 +179,6 @@ class SessionOverviewFragment :
updateSessionInfo(state) updateSessionInfo(state)
updateLoading(state.isLoading) updateLoading(state.isLoading)
updatePushNotificationToggle(state.deviceId, state.pushers.invoke().orEmpty()) updatePushNotificationToggle(state.deviceId, state.pushers.invoke().orEmpty())
if (state.deviceInfo is Success) {
renderSessionInfo(state.isCurrentSessionTrusted, state.deviceInfo.invoke())
} else {
hideSessionInfo()
}
} }
private fun updateToolbar(viewState: SessionOverviewViewState) { private fun updateToolbar(viewState: SessionOverviewViewState) {
@ -214,7 +207,7 @@ class SessionOverviewFragment :
deviceFullInfo = deviceInfo, deviceFullInfo = deviceInfo,
isVerifyButtonVisible = isCurrentSession || viewState.isCurrentSessionTrusted, isVerifyButtonVisible = isCurrentSession || viewState.isCurrentSessionTrusted,
isDetailsButtonVisible = false, isDetailsButtonVisible = false,
isLearnMoreLinkVisible = true, isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
isLastSeenDetailsVisible = !isCurrentSession, isLastSeenDetailsVisible = !isCurrentSession,
) )
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider) views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
@ -243,18 +236,6 @@ class SessionOverviewFragment :
} }
} }
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, stringProvider)
}
private fun updateLoading(isLoading: Boolean) { private fun updateLoading(isLoading: Boolean) {
if (isLoading) { if (isLoading) {
showLoading(null) showLoading(null)
@ -313,8 +294,4 @@ class SessionOverviewFragment :
) )
SessionLearnMoreBottomSheet.show(childFragmentManager, args) SessionLearnMoreBottomSheet.show(childFragmentManager, args)
} }
private fun hideSessionInfo() {
views.sessionOverviewInfo.isGone = true
}
} }

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/shield_color_unknown"
android:pathData="M12.008,23.487C12.005,23.487 12.002,23.488 12,23.489C11.998,23.488 11.995,23.487 11.992,23.487C11.92,23.471 11.813,23.445 11.675,23.409C11.399,23.337 11.002,23.223 10.523,23.058C9.565,22.725 8.292,22.186 7.022,21.361C4.49,19.715 2,16.954 2,12.405V3.45L12,0.521L22,3.45V12.405C22,16.954 19.51,19.715 16.978,21.361C15.708,22.186 14.435,22.725 13.477,23.058C12.998,23.223 12.601,23.337 12.325,23.409C12.187,23.445 12.08,23.471 12.008,23.487Z"
android:strokeWidth="1"
android:strokeColor="#ffffff" />
<path
android:fillColor="@color/shield_color_unknown"
android:pathData="M12.008,23.487C12.005,23.487 12.002,23.488 12,23.489C11.998,23.488 11.995,23.487 11.992,23.487C11.92,23.471 11.813,23.445 11.675,23.409C11.399,23.337 11.002,23.223 10.523,23.058C9.565,22.725 8.292,22.186 7.022,21.361C4.49,19.715 2,16.954 2,12.405V3.45L12,0.521L22,3.45V12.405C22,16.954 19.51,19.715 16.978,21.361C15.708,22.186 14.435,22.725 13.477,23.058C12.998,23.223 12.601,23.337 12.325,23.409C12.187,23.445 12.08,23.471 12.008,23.487Z"
android:strokeWidth="1"
android:strokeColor="#ffffff" />
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M12.003,16.5C12.577,16.5 13.043,16.035 13.043,15.46C13.043,14.887 12.577,14.421 12.003,14.421C11.429,14.421 10.964,14.887 10.964,15.46C10.964,16.035 11.429,16.5 12.003,16.5ZM10.559,9.823C10.559,9.023 11.208,8.379 12.003,8.379C12.795,8.379 13.447,9.031 13.447,9.823C13.447,10.19 13.29,10.321 12.671,10.748C12.398,10.937 12.022,11.2 11.729,11.586C11.416,11.999 11.223,12.513 11.223,13.168H12.783C12.783,12.847 12.868,12.665 12.971,12.528C13.096,12.364 13.276,12.226 13.558,12.031C13.587,12.01 13.618,11.989 13.651,11.967C14.162,11.619 15.006,11.045 15.006,9.823C15.006,8.17 13.656,6.82 12.003,6.82C10.352,6.82 9,8.157 9,9.823H10.559Z" />
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/shield_color_unknown"
android:pathData="M12.008,23.487C12.005,23.487 12.002,23.488 12,23.489C11.998,23.488 11.995,23.487 11.992,23.487C11.92,23.471 11.813,23.445 11.675,23.409C11.399,23.337 11.002,23.223 10.523,23.058C9.565,22.725 8.292,22.186 7.022,21.361C4.49,19.715 2,16.954 2,12.405V3.45L12,0.521L22,3.45V12.405C22,16.954 19.51,19.715 16.978,21.361C15.708,22.186 14.435,22.725 13.477,23.058C12.998,23.223 12.601,23.337 12.325,23.409C12.187,23.445 12.08,23.471 12.008,23.487Z"
android:strokeWidth="1"
android:strokeColor="#ffffff" />
<path
android:fillColor="@color/shield_color_unknown"
android:pathData="M1.5,12.405V3.075L12,0L22.5,3.075V12.405C22.5,21.945 12,24 12,24C12,24 1.5,21.945 1.5,12.405Z" />
<path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M12.003,16.5C12.577,16.5 13.043,16.035 13.043,15.46C13.043,14.887 12.577,14.421 12.003,14.421C11.429,14.421 10.964,14.887 10.964,15.46C10.964,16.035 11.429,16.5 12.003,16.5ZM10.559,9.823C10.559,9.023 11.208,8.379 12.003,8.379C12.795,8.379 13.447,9.031 13.447,9.823C13.447,10.19 13.29,10.321 12.671,10.748C12.398,10.937 12.022,11.2 11.729,11.586C11.416,11.999 11.223,12.513 11.223,13.168H12.783C12.783,12.847 12.868,12.665 12.971,12.528C13.096,12.364 13.276,12.226 13.558,12.031C13.587,12.01 13.618,11.989 13.651,11.967C14.162,11.619 15.006,11.045 15.006,9.823C15.006,8.17 13.656,6.82 12.003,6.82C10.352,6.82 9,8.157 9,9.823H10.559Z" />
</vector>

View File

@ -144,10 +144,11 @@ class GetDeviceFullInfoListUseCaseTest {
matrixClientInfo = matrixClientInfo3, matrixClientInfo = matrixClientInfo3,
) )
val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1) val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult every { filterDevicesUseCase.execute(any(), any(), any()) } returns expectedResult
val filterType = DeviceManagerFilterType.ALL_SESSIONS
// When // When
val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false) val result = getDeviceFullInfoListUseCase.execute(filterType, excludeCurrentDevice = false)
.test(this) .test(this)
// Then // Then
@ -166,6 +167,7 @@ class GetDeviceFullInfoListUseCaseTest {
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_1) getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_1)
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_2) getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_2)
getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_3) getMatrixClientInfoUseCase.execute(fakeActiveSessionHolder.fakeSession, A_DEVICE_ID_3)
filterDevicesUseCase.execute(currentSessionCrossSigningInfo, expectedResult, filterType, emptyList())
} }
} }

View File

@ -20,6 +20,7 @@ import im.vector.app.core.session.clientinfo.MatrixClientInfoContent
import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.DeviceFullInfo
import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo import im.vector.app.features.settings.devices.v2.details.extended.DeviceExtendedInfo
import im.vector.app.features.settings.devices.v2.list.DeviceType import im.vector.app.features.settings.devices.v2.list.DeviceType
import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldContainAll import org.amshove.kluent.shouldContainAll
import org.junit.Test import org.junit.Test
@ -94,32 +95,58 @@ class FilterDevicesUseCaseTest {
@Test @Test
fun `given a device list when filter type is ALL_SESSIONS then returns the same list`() { fun `given a device list when filter type is ALL_SESSIONS then returns the same list`() {
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.ALL_SESSIONS, emptyList()) val currentSessionCrossSigningInfo = givenCurrentSessionVerified(true)
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.ALL_SESSIONS, emptyList())
filteredDeviceList.size shouldBeEqualTo devices.size filteredDeviceList.size shouldBeEqualTo devices.size
} }
@Test @Test
fun `given a device list when filter type is VERIFIED then returns only verified devices`() { fun `given a device list and current session is verified when filter type is VERIFIED then returns only verified devices`() {
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.VERIFIED, emptyList()) val currentSessionCrossSigningInfo = givenCurrentSessionVerified(true)
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.VERIFIED, emptyList())
filteredDeviceList.size shouldBeEqualTo 2 filteredDeviceList.size shouldBeEqualTo 2
filteredDeviceList shouldContainAll listOf(activeVerifiedDevice, inactiveVerifiedDevice) filteredDeviceList shouldContainAll listOf(activeVerifiedDevice, inactiveVerifiedDevice)
} }
@Test @Test
fun `given a device list when filter type is UNVERIFIED then returns only unverified devices`() { fun `given a device list and current session is unverified when filter type is VERIFIED then returns empty list of devices`() {
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.UNVERIFIED, emptyList()) val currentSessionCrossSigningInfo = givenCurrentSessionVerified(false)
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.VERIFIED, emptyList())
filteredDeviceList.size shouldBeEqualTo 0
}
@Test
fun `given a device list and current session is verified when filter type is UNVERIFIED then returns only unverified devices`() {
val currentSessionCrossSigningInfo = givenCurrentSessionVerified(true)
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
filteredDeviceList.size shouldBeEqualTo 2 filteredDeviceList.size shouldBeEqualTo 2
filteredDeviceList shouldContainAll listOf(activeUnverifiedDevice, inactiveUnverifiedDevice) filteredDeviceList shouldContainAll listOf(activeUnverifiedDevice, inactiveUnverifiedDevice)
} }
@Test
fun `given a device list and current session is unverified when filter type is UNVERIFIED then returns empty list of devices`() {
val currentSessionCrossSigningInfo = givenCurrentSessionVerified(false)
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
filteredDeviceList.size shouldBeEqualTo 0
}
@Test @Test
fun `given a device list when filter type is INACTIVE then returns only inactive devices`() { fun `given a device list when filter type is INACTIVE then returns only inactive devices`() {
val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.INACTIVE, emptyList()) val currentSessionCrossSigningInfo = givenCurrentSessionVerified(true)
val filteredDeviceList = filterDevicesUseCase.execute(currentSessionCrossSigningInfo, devices, DeviceManagerFilterType.INACTIVE, emptyList())
filteredDeviceList.size shouldBeEqualTo 2 filteredDeviceList.size shouldBeEqualTo 2
filteredDeviceList shouldContainAll listOf(inactiveVerifiedDevice, inactiveUnverifiedDevice) filteredDeviceList shouldContainAll listOf(inactiveVerifiedDevice, inactiveUnverifiedDevice)
} }
private fun givenCurrentSessionVerified(isVerified: Boolean): CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(
isCrossSigningVerified = isVerified,
isCrossSigningInitialized = true,
deviceId = ""
)
} }