Skip to content

Commit

Permalink
Merge pull request #7361 from vector-im/feature/mna/device-manager-un…
Browse files Browse the repository at this point in the history
…known-verification-status

[Device management] Update the unknown verification status icon (PSG-824)
  • Loading branch information
mnaturel authored Oct 14, 2022
2 parents ec5964b + 81038bd commit 612d2e5
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 82 deletions.
1 change: 1 addition & 0 deletions changelog.d/7327.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Device management] Update the unknown verification status icon
2 changes: 2 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3254,10 +3254,12 @@
<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_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_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_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_view_details">View Details</string>
<string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
Expand Down
1 change: 1 addition & 0 deletions library/ui-styles/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
<color name="shield_color_gray">#91A1C0</color>
<color name="shield_color_warning">#FF4B55</color>
<color name="shield_color_warning_background">#0FFF4B55</color>
<color name="shield_color_unknown">@color/palette_gray_200</color>

<!-- Badge Colors -->
<attr name="vctr_badge_color_border" format="color" />
Expand Down
2 changes: 2 additions & 0 deletions vector/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ android {
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'

vectorDrawables.useSupportLibrary = true
}

testOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class ShieldImageView @JvmOverloads constructor(
RoomEncryptionTrustLevel.Default -> {
contentDescription = context.getString(R.string.a11y_trust_level_default)
setImageResource(
if (borderLess) R.drawable.ic_shield_black_no_border
else R.drawable.ic_shield_black
if (borderLess) R.drawable.ic_shield_unknown_no_border
else R.drawable.ic_shield_unknown
)
}
RoomEncryptionTrustLevel.Warning -> {
Expand Down Expand Up @@ -137,7 +137,7 @@ class ShieldImageView @JvmOverloads constructor(
@DrawableRes
fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
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.Trusted -> R.drawable.ic_shield_trusted
RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
} else {
emptyList()
}
filterDevicesUseCase.execute(deviceFullInfoList, filterType, excludedDeviceIds)
filterDevicesUseCase.execute(currentSessionCrossSigningInfo, deviceFullInfoList, filterType, excludedDeviceIds)
}

deviceFullInfoFlow.distinctUntilChanged()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.SecurityRecommendationViewState
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import javax.inject.Inject

/**
Expand Down Expand Up @@ -164,12 +165,11 @@ class VectorSettingsDevicesFragment :
if (state.devices is Success) {
val devices = state.devices()
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
val currentDeviceInfo = devices?.firstOrNull {
it.deviceInfo.deviceId == currentDeviceId
}
val currentDeviceInfo = devices?.firstOrNull { it.deviceInfo.deviceId == currentDeviceId }
val isCurrentSessionVerified = currentDeviceInfo?.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted
val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }

renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount)
renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount, isCurrentSessionVerified)
renderCurrentDevice(currentDeviceInfo)
renderOtherSessionsView(otherDevices)
} else {
Expand All @@ -181,14 +181,21 @@ class VectorSettingsDevicesFragment :
handleLoadingStatus(state.isLoading)
}

private fun renderSecurityRecommendations(inactiveSessionsCount: Int, unverifiedSessionsCount: Int) {
if (unverifiedSessionsCount == 0 && inactiveSessionsCount == 0) {
private fun renderSecurityRecommendations(
inactiveSessionsCount: Int,
unverifiedSessionsCount: Int,
isCurrentSessionVerified: Boolean,
) {
val isUnverifiedSectionVisible = unverifiedSessionsCount > 0 && isCurrentSessionVerified
val isInactiveSectionVisible = inactiveSessionsCount > 0
if (isUnverifiedSectionVisible.not() && isInactiveSectionVisible.not()) {
hideSecurityRecommendations()
} else {
views.deviceListHeaderSectionSecurityRecommendations.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(
description = getString(R.string.device_manager_unverified_sessions_description),
sessionsCount = unverifiedSessionsCount,
Expand All @@ -206,11 +213,19 @@ class VectorSettingsDevicesFragment :
}
}

private fun hideSecurityRecommendations() {
views.deviceListHeaderSectionSecurityRecommendations.isVisible = false
private fun hideUnverifiedSessionsRecommendation() {
views.deviceListUnverifiedSessionsRecommendation.isVisible = false
}

private fun hideInactiveSessionsRecommendation() {
views.deviceListInactiveSessionsRecommendation.isVisible = false
}

private fun hideSecurityRecommendations() {
views.deviceListHeaderSectionSecurityRecommendations.isVisible = false
views.deviceListSecurityRecommendationsDivider.isVisible = false
hideUnverifiedSessionsRecommendation()
hideInactiveSessionsRecommendation()
}

private fun renderOtherSessionsView(otherDevices: List<DeviceFullInfo>?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,27 @@
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.verification.CurrentSessionCrossSigningInfo
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject

class FilterDevicesUseCase @Inject constructor() {

fun execute(
currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo,
devices: List<DeviceFullInfo>,
filterType: DeviceManagerFilterType,
excludedDeviceIds: List<String> = emptyList(),
): List<DeviceFullInfo> {
val isCurrentSessionVerified = currentSessionCrossSigningInfo.isCrossSigningVerified.orFalse()
return devices
.filter {
when (filterType) {
DeviceManagerFilterType.ALL_SESSIONS -> true
DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.trustLevel?.isCrossSigningVerified().orFalse()
// when current session is not verified, other session status cannot be trusted
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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ 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 = calculateDescription(device, formattedLastActivityDate)
val description = buildDescription(device, formattedLastActivityDate)
val descriptionColor = if (device.isCurrentDevice) {
host.colorProvider.getColorFromAttribute(R.attr.colorError)
} else {
Expand All @@ -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 {
device.isInactive -> {
stringProvider.getQuantityString(
Expand All @@ -93,6 +93,9 @@ class OtherSessionsController @Inject constructor(
device.isCurrentDevice -> {
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 -> {
stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ class SessionInfoView @JvmOverloads constructor(
isVerifyButtonVisible: Boolean,
) {
views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
if (encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
renderCrossSigningVerified(isCurrentSession)
} else {
renderCrossSigningUnverified(isCurrentSession, isVerifyButtonVisible)
when {
encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted -> renderCrossSigningVerified(isCurrentSession)
encryptionTrustLevel == RoomEncryptionTrustLevel.Default && !isCurrentSession -> renderCrossSigningUnknown()
else -> renderCrossSigningUnverified(isCurrentSession, isVerifyButtonVisible)
}
if (hasLearnMoreLink) {
appendLearnMoreToVerificationStatus()
Expand Down Expand Up @@ -142,6 +142,12 @@ class SessionInfoView @JvmOverloads constructor(
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) {
setDeviceTypeIconUseCase.execute(deviceType, views.sessionInfoDeviceTypeImageView, stringProvider)
views.sessionInfoNameTextView.text = sessionName
Expand All @@ -155,34 +161,31 @@ class SessionInfoView @JvmOverloads constructor(
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
}

if (deviceInfo.lastSeenTs != null && isLastSeenDetailsVisible) {
val timestamp = deviceInfo.lastSeenTs
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)
} else {
views.sessionInfoLastActivityTextView.isGone = true
}
views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible })
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ 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
Expand All @@ -42,7 +41,6 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.databinding.FragmentSessionOverviewBinding
import im.vector.app.features.auth.ReAuthActivity
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.more.SessionLearnMoreBottomSheet
import im.vector.app.features.workers.signout.SignOutUiWorker
Expand Down Expand Up @@ -181,11 +179,6 @@ class SessionOverviewFragment :
updateSessionInfo(state)
updateLoading(state.isLoading)
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) {
Expand Down Expand Up @@ -214,7 +207,7 @@ class SessionOverviewFragment :
deviceFullInfo = deviceInfo,
isVerifyButtonVisible = isCurrentSession || viewState.isCurrentSessionTrusted,
isDetailsButtonVisible = false,
isLearnMoreLinkVisible = true,
isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
isLastSeenDetailsVisible = !isCurrentSession,
)
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
Expand Down Expand Up @@ -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) {
if (isLoading) {
showLoading(null)
Expand Down Expand Up @@ -313,8 +294,4 @@ class SessionOverviewFragment :
)
SessionLearnMoreBottomSheet.show(childFragmentManager, args)
}

private fun hideSessionInfo() {
views.sessionOverviewInfo.isGone = true
}
}
Loading

0 comments on commit 612d2e5

Please sign in to comment.