Decorate timeline with e2e warning

This commit is contained in:
Valere 2020-04-24 16:19:01 +02:00
parent 57779c99c2
commit 20e5ebc88b
15 changed files with 177 additions and 4 deletions

View File

@ -46,6 +46,7 @@ data class RoomSummary constructor(
val readMarkerId: String? = null,
val userDrafts: List<UserDraft> = emptyList(),
val isEncrypted: Boolean,
val encryptionEventTs: Long?,
val inviterId: String? = null,
val typingRoomMemberIds: List<String> = emptyList(),
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,

View File

@ -53,6 +53,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
canonicalAlias = roomSummaryEntity.canonicalAlias,
aliases = roomSummaryEntity.aliases.toList(),
isEncrypted = roomSummaryEntity.isEncrypted,
encryptionEventTs = roomSummaryEntity.encryptionEventTs,
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,

View File

@ -48,6 +48,7 @@ internal open class RoomSummaryEntity(
// this is required for querying
var flatAliases: String = "",
var isEncrypted: Boolean = false,
var encryptionEventTs: Long? = 0,
var typingUserIds: RealmList<String> = RealmList(),
var roomEncryptionTrustLevelStr: String? = null,
var inviterId: String? = null

View File

@ -136,6 +136,7 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.aliases.addAll(roomAliases)
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
roomSummaryEntity.isEncrypted = encryptionEvent != null
roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs ?: System.currentTimeMillis()
roomSummaryEntity.typingUserIds.clear()
roomSummaryEntity.typingUserIds.addAll(ephemeralResult?.typingUserIds.orEmpty())

View File

@ -26,7 +26,6 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import androidx.transition.AutoTransition
@ -172,7 +171,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib
RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
else -> R.drawable.ic_shield_black
}
composerShieldImageView.setImageDrawable(ContextCompat.getDrawable(context, shieldRes))
composerShieldImageView.setImageResource(shieldRes)
} else {
composerEditText.setHint(R.string.room_message_placeholder)
composerShieldImageView.isVisible = false

View File

@ -29,6 +29,7 @@ import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.item.E2EDecoration
import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
import javax.inject.Inject
@ -72,6 +73,29 @@ class MessageActionsEpoxyController @Inject constructor(
}
}
when (state.informationData.e2eDecoration) {
E2EDecoration.WARN_IN_CLEAR -> {
bottomSheetSendStateItem {
id("e2e_clear")
showProgress(false)
text(stringProvider.getString(R.string.unencrytped))
drawableStart(R.drawable.ic_shield_warning_small)
}
}
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
bottomSheetSendStateItem {
id("e2e_unverified")
showProgress(false)
text(stringProvider.getString(R.string.encrypted_unverified))
drawableStart(R.drawable.ic_shield_warning_small)
}
}
else -> {
// nothing
}
}
// Quick reactions
if (state.canReact() && state.quickStates is Success) {
// Separator

View File

@ -18,6 +18,7 @@
package im.vector.riotx.features.home.room.detail.timeline.helper
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
@ -26,11 +27,13 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.session.room.VerificationState
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.getColorFromUserId
import im.vector.riotx.features.home.room.detail.timeline.item.E2EDecoration
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.PollResponseData
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
@ -72,6 +75,52 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId))
}
val room = event.root.roomId?.let { session.getRoom(it) }
val e2eDecoration: E2EDecoration
val isUserVerified = session.cryptoService().crossSigningService().getUserCrossSigningKeys(event.root.senderId ?: "")?.isTrusted() == true
if (room?.isEncrypted() == true && isUserVerified) {
val ts = room.roomSummary()?.encryptionEventTs ?: 0
val eventTs = event.root.originServerTs ?: 0
if (event.isEncrypted()) {
// Do not decorate failed to decrypt, or redaction (we lost sender device info)
if (event.root.getClearType() == EventType.ENCRYPTED || event.root.isRedacted()) {
e2eDecoration = E2EDecoration.NONE
} else {
val sendingDevice = event.root.content
.toModel<EncryptedEventContent>()
?.deviceId
?.let { deviceId ->
session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
}
e2eDecoration = when {
sendingDevice == null -> {
// For now do not decorate this with warning
// maybe it's a deleted session
E2EDecoration.NONE
}
sendingDevice.trustLevel == null -> {
E2EDecoration.WARN_SENT_BY_UNKNOWN
}
sendingDevice.trustLevel?.isVerified().orFalse() -> {
E2EDecoration.NONE
}
else -> {
E2EDecoration.WARN_SENT_BY_UNVERIFIED
}
}
}
} else {
if (EventType.isStateEvent(event.root.type)) {
// Do not warn for state event, they are always in clear
e2eDecoration = E2EDecoration.NONE
} else {
// If event is in clear after the room enabled encryption we should warn
e2eDecoration = if (eventTs > ts) E2EDecoration.WARN_IN_CLEAR else E2EDecoration.NONE
}
}
} else {
e2eDecoration = E2EDecoration.NONE
}
return MessageInformationData(
eventId = eventId,
senderId = event.root.senderId ?: "",
@ -111,7 +160,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
?: VerificationState.REQUEST
ReferencesInfoData(verificationState)
},
sentByMe = event.root.senderId == session.myUserId
sentByMe = event.root.senderId == session.myUserId,
e2eDecoration = e2eDecoration
)
}

View File

@ -21,6 +21,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.IdRes
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.riotx.R
@ -92,6 +93,18 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
}
when (baseAttributes.informationData.e2eDecoration) {
E2EDecoration.NONE -> {
holder.e2EDecorationView.isVisible = false
}
E2EDecoration.WARN_IN_CLEAR,
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
holder.e2EDecorationView.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning))
holder.e2EDecorationView.isVisible = true
}
}
holder.view.setOnClickListener(baseAttributes.itemClickListener)
holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
}
@ -110,6 +123,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
val reactionsContainer by bind<ViewGroup>(R.id.reactionsContainer)
val e2EDecorationView by bind<ImageView>(R.id.messageE2EDecoration)
}
/**

View File

@ -40,7 +40,8 @@ data class MessageInformationData(
val hasPendingEdits: Boolean = false,
val readReceipts: List<ReadReceiptData> = emptyList(),
val referencesInfoData: ReferencesInfoData? = null,
val sentByMe : Boolean
val sentByMe : Boolean,
val e2eDecoration: E2EDecoration = E2EDecoration.NONE
) : Parcelable {
val matrixItem: MatrixItem
@ -75,4 +76,11 @@ data class PollResponseData(
val isClosed: Boolean = false
) : Parcelable
enum class E2EDecoration {
NONE,
WARN_IN_CLEAR,
WARN_SENT_BY_UNVERIFIED,
WARN_SENT_BY_UNKNOWN
}
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)

View File

@ -19,6 +19,8 @@ package im.vector.riotx.features.home.room.detail.timeline.item
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
@ -45,6 +47,18 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
holder.avatarImageView.onClick(attributes.avatarClickListener)
when (attributes.informationData.e2eDecoration) {
E2EDecoration.NONE -> {
holder.e2EDecorationView.isVisible = false
}
E2EDecoration.WARN_IN_CLEAR,
E2EDecoration.WARN_SENT_BY_UNVERIFIED,
E2EDecoration.WARN_SENT_BY_UNKNOWN -> {
holder.e2EDecorationView.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning))
holder.e2EDecorationView.isVisible = true
}
}
}
override fun getEventIds(): List<String> {
@ -56,6 +70,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
class Holder : BaseHolder(STUB_ID) {
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
val e2EDecorationView by bind<ImageView>(R.id.messageE2EDecoration)
}
data class Attributes(

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:strokeWidth="1"
android:pathData="m12,21s9,-3.8 9,-9.5v-6.65l-9,-2.85 -9,2.85v6.65c0,5.7 9,9.5 9,9.5z"
android:strokeLineJoin="round"
android:fillColor="#ff4b55"
android:strokeColor="#fff"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
android:pathData="M12.05,5.5L12.05,5.5A1.25,1.25 0,0 1,13.3 6.75L13.3,12.25A1.25,1.25 0,0 1,12.05 13.5L12.05,13.5A1.25,1.25 0,0 1,10.8 12.25L10.8,6.75A1.25,1.25 0,0 1,12.05 5.5z"
android:fillColor="#fff"/>
<path
android:pathData="M12.05,15L12.05,15A1.25,1.25 0,0 1,13.3 16.25L13.3,16.25A1.25,1.25 0,0 1,12.05 17.5L12.05,17.5A1.25,1.25 0,0 1,10.8 16.25L10.8,16.25A1.25,1.25 0,0 1,12.05 15z"
android:fillColor="#fff"/>
</vector>

View File

@ -62,6 +62,18 @@
android:layout_height="0dp"
tools:layout_marginStart="52dp" />
<ImageView
android:id="@+id/messageE2EDecoration"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignTop="@id/viewStubContainer"
android:layout_toStartOf="@id/viewStubContainer"
android:visibility="gone"
android:layout_marginEnd="-4dp"
android:layout_marginTop="7dp"
tools:src="@drawable/ic_shield_warning"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/viewStubContainer"
android:layout_width="match_parent"

View File

@ -63,6 +63,18 @@
</FrameLayout>
<ImageView
android:id="@+id/messageE2EDecoration"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentTop="true"
android:layout_marginTop="8dp"
android:layout_toStartOf="@id/viewStubContainer"
android:visibility="gone"
tools:src="@drawable/ic_shield_warning"
tools:visibility="visible" />
<im.vector.riotx.core.ui.views.ReadReceiptsView
android:id="@+id/readReceiptsView"
android:layout_width="wrap_content"

View File

@ -54,6 +54,18 @@
</FrameLayout>
<ImageView
android:id="@+id/messageE2EDecoration"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="4dp"
android:layout_alignTop="@id/viewStubContainer"
android:layout_toStartOf="@id/viewStubContainer"
android:visibility="gone"
tools:src="@drawable/ic_shield_warning"
tools:visibility="visible" />
<ImageView
android:id="@+id/messageFailToSendIndicator"
android:layout_width="14dp"

View File

@ -19,6 +19,9 @@
<string name="enter_secret_storage_input_key">Select your Recovery Key, or input it manually by typing it or pasting from your clipboard</string>
<string name="keys_backup_recovery_key_error_decrypt">Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key.</string>
<string name="failed_to_access_secure_storage">Failed to access secure storage</string>
<string name="unencrytped">Unencrypted</string>
<string name="encrypted_unverified">Encrypted by an unverified device</string>
<!-- END Strings added by Valere -->