Merge pull request #3031 from vector-im/feature/bma/other_fixies

Some fixies
This commit is contained in:
Benoit Marty 2021-03-19 17:06:54 +01:00 committed by GitHub
commit 43fd9910e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 96 additions and 148 deletions

View file

@ -8,9 +8,11 @@ Improvements 🙌:
- Split network request `/keys/query` into smaller requests (250 users max) (#2925)
- Crypto improvement | Bulk send NO_OLM withheld code
- Display the room shield in all room setting screens
- Improve message with Emoji only detection (#3017)
Bugfix 🐛:
-
- Fix bad theme change for the MainActivity
- Handle encrypted reactions (#2509)
Translations 🗣:
-

View file

@ -2,7 +2,7 @@ This document aims to describe how Element android displays notifications to the
# Table of Contents
1. [Prerequisites Knowledge](#prerequisites-knowledge)
* [How does a matrix client gets a message from a Home Server?](#how-does-a-matrix-client-gets-a-message-from-a-home-server)
* [How does a matrix client get a message from a Home Server?](#how-does-a-matrix-client-get-a-message-from-a-home-server)
* [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification)
* [Push VS Notification](#push-vs-notification)
* [Push in the matrix federated world](#push-in-the-matrix-federated-world)
@ -22,7 +22,7 @@ First let's start with some prerequisite knowledge
# Prerequisites Knowledge
## How does a matrix client gets a message from a Home Server?
## How does a matrix client get a message from a Home Server?
In order to get messages from a home server, a matrix client need to perform a ``sync`` operation.

View file

@ -53,8 +53,9 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
import timber.log.Timber
import javax.inject.Inject
internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String)
: EventInsertLiveProcessor {
internal class EventRelationsAggregationProcessor @Inject constructor(
@UserId private val userId: String
) : EventInsertLiveProcessor {
private val allowedTypes = listOf(
EventType.MESSAGE,
@ -87,12 +88,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
EventType.REACTION -> {
// we got a reaction!!
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
handleReaction(event, roomId, realm, userId, isLocalEcho)
handleReaction(realm, event, roomId, isLocalEcho)
}
EventType.MESSAGE -> {
if (event.unsignedData?.relations?.annotations != null) {
Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
?.let {
@ -108,7 +109,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
handleReplace(realm, event, content, roomId, isLocalEcho)
} else if (content?.relatesTo?.type == RelationType.RESPONSE) {
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
handleResponse(realm, userId, event, content, roomId, isLocalEcho)
handleResponse(realm, event, content, roomId, isLocalEcho)
}
}
@ -122,7 +123,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
event.content.toModel<MessageRelationContent>()?.relatesTo?.let {
if (it.type == RelationType.REFERENCE && it.eventId != null) {
handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId)
handleVerification(realm, event, roomId, isLocalEcho, it.eventId)
}
}
}
@ -140,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
} else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) {
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
}
}
} else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
@ -154,10 +155,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
EventType.KEY_VERIFICATION_KEY -> {
Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
encryptedEventContent.relatesTo.eventId?.let {
handleVerification(realm, event, roomId, isLocalEcho, it, userId)
handleVerification(realm, event, roomId, isLocalEcho, it)
}
}
}
} else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) {
// Reaction
if (event.getClearType() == EventType.REACTION) {
// we got a reaction!!
Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}")
handleReaction(realm, event, roomId, isLocalEcho)
}
}
}
EventType.REDACTION -> {
@ -172,11 +180,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
// was this event a m.replace
val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
handleRedactionOfReplace(realm, eventToPrune, contentModel.relatesTo!!.eventId!!)
}
}
EventType.REACTION -> {
handleReactionRedact(eventToPrune, realm, userId)
handleReactionRedact(realm, eventToPrune)
}
}
}
@ -267,7 +275,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
}
private fun handleResponse(realm: Realm,
userId: String,
event: Event,
content: MessageContent,
roomId: String,
@ -354,7 +361,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
}
private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
private fun handleInitialAggregatedRelations(realm: Realm,
event: Event,
roomId: String,
aggregation: AggregatedAnnotation) {
if (SHOULD_HANDLE_SERVER_AGREGGATION) {
aggregation.chunk?.forEach {
if (it.type == EventType.REACTION) {
@ -376,7 +386,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
}
}
private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) {
private fun handleReaction(realm: Realm,
event: Event,
roomId: String,
isLocalEcho: Boolean) {
val content = event.content.toModel<ReactionContent>()
if (content == null) {
Timber.e("Malformed reaction content ${event.content}")
@ -441,7 +454,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
/**
* Called when an event is deleted
*/
private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) {
private fun handleRedactionOfReplace(realm: Realm,
redacted: EventEntity,
relatedEventId: String) {
Timber.d("Handle redaction of m.replace")
val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst()
if (eventSummary == null) {
@ -457,7 +472,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
sourceToDiscard.deleteFromRealm()
}
private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
private fun handleReactionRedact(realm: Realm,
eventToPrune: EventEntity) {
Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
// delete a reaction, need to update the annotation summary if any
val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return
@ -494,7 +510,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
}
}
private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String, userId: String) {
private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) {
val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId)
val verifSummary = eventSummary.referencesSummaryEntity

View file

@ -16,62 +16,7 @@
package im.vector.app.core.utils
import java.util.regex.Pattern
private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
"|[\uD83E\uDD00-\uD83E\uDDFF]" +
"|[\uD83D\uDE00-\uD83D\uDE4F]" +
"|[\uD83D\uDE80-\uD83D\uDEFF]" +
"|[\u2600-\u26FF]\uFE0F?" +
"|[\u2700-\u27BF]\uFE0F?" +
"|\u24C2\uFE0F?" +
"|[\uD83C\uDDE6-\uD83C\uDDFF]{1,2}" +
"|[\uD83C\uDD70\uD83C\uDD71\uD83C\uDD7E\uD83C\uDD7F\uD83C\uDD8E\uD83C\uDD91-\uD83C\uDD9A]\uFE0F?" +
"|[\u0023\u002A\u0030-\u0039]\uFE0F?\u20E3" +
"|[\u2194-\u2199\u21A9-\u21AA]\uFE0F?" +
"|[\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55]\uFE0F?" +
"|[\u2934\u2935]\uFE0F?" +
"|[\u3030\u303D]\uFE0F?" +
"|[\u3297\u3299]\uFE0F?" +
"|[\uD83C\uDE01\uD83C\uDE02\uD83C\uDE1A\uD83C\uDE2F\uD83C\uDE32-\uD83C\uDE3A\uD83C\uDE50\uD83C\uDE51]\uFE0F?" +
"|[\u203C\u2049]\uFE0F?" +
"|[\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE]\uFE0F?" +
"|[\u00A9\u00AE]\uFE0F?" +
"|[\u2122\u2139]\uFE0F?" +
"|\uD83C\uDC04\uFE0F?" +
"|\uD83C\uDCCF\uFE0F?" +
"|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
/*
// A hashset from all supported emoji
private var knownEmojiSet: HashSet<String>? = null
fun initKnownEmojiHashSet(context: Context, done: (() -> Unit)? = null) {
GlobalScope.launch {
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(EmojiData::class.java)
val inputAsString = input.bufferedReader().use { it.readText() }
val source = jsonAdapter.fromJson(inputAsString)
knownEmojiSet = HashSet<String>().also {
source?.emojis?.mapTo(it) { (_, value) ->
value.emojiString()
}
}
done?.invoke()
}
}
}
fun isSingleEmoji(string: String): Boolean {
if (knownEmojiSet == null) {
Timber.e("Known Emoji Hashset not initialized")
// use fallback regexp
return containsOnlyEmojis(string)
}
return knownEmojiSet?.contains(string) ?: false
}
*/
import com.vanniktech.emoji.EmojiUtils
/**
* Test if a string contains emojis.
@ -82,36 +27,8 @@ fun isSingleEmoji(string: String): Boolean {
* @return true if the body contains only emojis
*/
fun containsOnlyEmojis(str: String?): Boolean {
var res = false
if (str != null && str.isNotEmpty()) {
val matcher = emojisPattern.matcher(str)
var start = -1
var end = -1
while (matcher.find()) {
val nextStart = matcher.start()
// first emoji position
if (start < 0) {
if (nextStart > 0) {
return false
}
} else {
// must not have a character between
if (nextStart != end) {
return false
}
}
start = nextStart
end = matcher.end()
}
res = -1 != start && end == str.length
}
return res
// Now rely on vanniktech library
return EmojiUtils.isOnlyEmojis(str)
}
/**

View file

@ -43,6 +43,7 @@ import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.signout.soft.SoftLogoutActivity
import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.ui.UiStateRepository
import kotlinx.parcelize.Parcelize
import kotlinx.coroutines.Dispatchers
@ -83,6 +84,8 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
override fun getBinding() = ActivityMainBinding.inflate(layoutInflater)
override fun getOtherThemes() = ActivityOtherThemes.Launcher
private lateinit var args: MainActivityArgs
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager

View file

@ -30,10 +30,8 @@ import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -59,8 +57,9 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
key
)
.toSpannable()
.colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
.colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
// TODO Restore coloration when we will have a FAQ to open with those terms
// .colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
// .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS)

View file

@ -119,13 +119,18 @@ class IncomingVerificationRequestHandler @Inject constructor(
Timber.v("## SAS verificationRequestCreated ${pr.transactionId}")
// For incoming request we should prompt (if not in activity where this request apply)
if (pr.isIncoming) {
val user = session?.getUser(pr.otherUserId)
val user = session?.getUser(pr.otherUserId)?.toMatrixItem()
val name = user?.getBestName() ?: pr.otherUserId
val description = if (name == pr.otherUserId) {
name
} else {
"$name (${pr.otherUserId})"
}
val alert = VerificationVectorAlert(
uniqueIdForVerificationRequest(pr),
context.getString(R.string.sas_incoming_request_notif_title),
"$name(${pr.otherUserId})",
description,
R.drawable.ic_shield_black,
shouldBeDisplayedIn = { activity ->
if (activity is RoomDetailActivity) {
@ -136,7 +141,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
}
)
.apply {
viewBinder = VerificationVectorAlert.ViewBinder(user?.toMatrixItem(), avatarRenderer.get())
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer.get())
contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
val roomId = pr.roomId

View file

@ -64,7 +64,6 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_ROOM_SERVER_ACL,
EventType.STATE_ROOM_GUEST_ACCESS,
EventType.STATE_ROOM_POWER_LEVELS,
EventType.REACTION,
EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback)
EventType.STATE_ROOM_WIDGET_LEGACY,
EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(event, highlight, callback)
@ -91,6 +90,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_MAC,
EventType.REACTION,
EventType.CALL_CANDIDATES,
EventType.CALL_REPLACES,
EventType.CALL_SELECT_ANSWER,

View file

@ -26,6 +26,7 @@ import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.isAnimationDisabled
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.signout.hard.SignedOutActivity
import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber
import java.lang.ref.WeakReference
@ -294,6 +295,7 @@ class PopupAlertManager @Inject constructor() {
private fun shouldBeDisplayedIn(alert: VectorAlert?, activity: Activity): Boolean {
return alert != null
&& activity !is PinActivity
&& activity !is SignedOutActivity
&& activity is VectorBaseActivity<*>
&& alert.shouldBeDisplayedIn.invoke(activity)
}

View file

@ -222,7 +222,6 @@ class RoomProfileController @Inject constructor(
buildProfileAction(
id = "devTools",
title = stringProvider.getString(R.string.dev_tools_menu_name),
subtitle = roomSummary.roomId,
dividerColor = dividerColor,
divider = false,
editable = true,

View file

@ -31,6 +31,11 @@ sealed class ActivityOtherThemes(@StyleRes val dark: Int,
R.style.AppTheme_Black
)
object Launcher : ActivityOtherThemes(
R.style.AppTheme_Launcher,
R.style.AppTheme_Launcher
)
object AttachmentsPreview : ActivityOtherThemes(
R.style.AppTheme_AttachmentsPreview,
R.style.AppTheme_AttachmentsPreview

View file

@ -8,7 +8,9 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingTop="32dp"
android:paddingBottom="32dp">
<ImageView
android:id="@+id/ssss_shield"
@ -28,7 +30,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="36dp"
android:layout_marginEnd="16dp"
android:text="@string/recovery_passphrase"
android:textColor="?riotx_text_primary"
@ -38,7 +39,6 @@
app:layout_constraintStart_toEndOf="@id/ssss_shield"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/ssss_restore_with_passphrase_warning_text"
android:layout_width="0dp"
@ -51,7 +51,6 @@
app:layout_constraintTop_toBottomOf="@id/ssss_restore_with_passphrase"
tools:text="@string/enter_secret_storage_passphrase_or_key" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ssss_passphrase_enter_til"
style="@style/VectorTextInputLayout"
@ -82,61 +81,62 @@
android:layout_width="@dimen/layout_touch_size"
android:layout_height="@dimen/layout_touch_size"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:background="?attr/selectableItemBackground"
android:scaleType="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ssss_passphrase_enter_til"
app:layout_constraintTop_toTopOf="@+id/ssss_passphrase_enter_til"
app:tint="?colorAccent" />
<!-- -->
<com.google.android.material.button.MaterialButton
android:id="@+id/ssss_passphrase_use_key"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_recovery_key"
app:icon="@drawable/ic_security_key_24dp"
tools:ignore="MissingConstraints" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ssss_passphrase_submit"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/_continue"
tools:ignore="MissingConstraints" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/ssss_passphrase_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
app:constraint_referenced_ids="ssss_passphrase_use_key,ssss_passphrase_submit"
app:flow_horizontalStyle="spread_inside"
app:flow_wrapMode="chain"
android:text="@string/_continue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_enter_til" />
<TextView
android:id="@+id/ssss_passphrase_or"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/or"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_submit" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ssss_passphrase_use_key"
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/use_recovery_key"
app:icon="@drawable/ic_security_key_24dp"
app:layout_constraintBottom_toTopOf="@+id/ssss_passphrase_reset"
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_enter_til"
app:layout_goneMarginBottom="32dp" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_or" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/ssss_passphrase_reset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="32dp"
android:clickable="true"
android:focusable="true"
app:actionTitle="@string/bad_passphrase_key_reset_all_action"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/ssss_passphrase_flow"
app:layout_constraintTop_toBottomOf="@id/ssss_passphrase_use_key"
app:leftIcon="@drawable/ic_alert_triangle"
app:tint="@color/vector_error_color"
app:titleTextColor="?riotx_text_secondary" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>