Merge pull request #3055 from vector-im/feature/bma/get_event

Get event
This commit is contained in:
Benoit Marty 2021-04-07 18:38:17 +02:00 committed by GitHub
commit e61a9e75e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 390 additions and 61 deletions

View File

@ -17,6 +17,7 @@ Improvements 🙌:
- Add better support for empty room name fallback (#3106) - Add better support for empty room name fallback (#3106)
- Room list improvements (paging) - Room list improvements (paging)
- Fix quick click action (#3127) - Fix quick click action (#3127)
- Get Event after a Push for a faster notification display in some conditions
Bugfix 🐛: Bugfix 🐛:
- Fix bad theme change for the MainActivity - Fix bad theme change for the MainActivity

View File

@ -39,6 +39,8 @@ interface PushRuleService {
fun removePushRuleListener(listener: PushRuleListener) fun removePushRuleListener(listener: PushRuleListener)
fun getActions(event: Event): List<Action>
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? // fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
interface PushRuleListener { interface PushRuleListener {

View File

@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.group.GroupService
@ -68,6 +69,7 @@ interface Session :
SignOutService, SignOutService,
FilterService, FilterService,
TermsService, TermsService,
EventService,
ProfileService, ProfileService,
PushRuleService, PushRuleService,
PushersService, PushersService,

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.events
import org.matrix.android.sdk.api.session.events.model.Event
interface EventService {
/**
* Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible
* The result will not be stored into cache
*/
suspend fun getEvent(roomId: String,
eventId: String): Event
}

View File

@ -289,3 +289,7 @@ fun Event.getRelationContent(): RelationDefaultContent? {
fun Event.isReply(): Boolean { fun Event.isReply(): Boolean {
return getRelationContent()?.inReplyTo?.eventId != null return getRelationContent()?.inReplyTo?.eventId != null
} }
fun Event.isEdition(): Boolean {
return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null
}

View File

@ -38,9 +38,13 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration,
Realm.getInstance(realmConfiguration).use { realm -> Realm.getInstance(realmConfiguration).use { realm ->
val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use
val eventToCheck = liveChunk.timelineEvents.find(eventId) val eventToCheck = liveChunk.timelineEvents.find(eventId)
isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) { isEventRead = when {
true eventToCheck == null -> {
} else { // This can happen in case of fast lane Event
false
}
eventToCheck.root?.sender == userId -> true
else -> {
val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst()
?: return@use ?: return@use
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex
@ -50,6 +54,7 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration,
eventToCheckIndex <= readReceiptIndex eventToCheckIndex <= readReceiptIndex
} }
} }
}
return isEventRead return isEventRead
} }

View File

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService import org.matrix.android.sdk.api.session.group.GroupService
@ -114,6 +115,7 @@ internal class DefaultSession @Inject constructor(
private val accountDataService: Lazy<AccountDataService>, private val accountDataService: Lazy<AccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
private val accountService: Lazy<AccountService>, private val accountService: Lazy<AccountService>,
private val eventService: Lazy<EventService>,
private val defaultIdentityService: DefaultIdentityService, private val defaultIdentityService: DefaultIdentityService,
private val integrationManagerService: IntegrationManagerService, private val integrationManagerService: IntegrationManagerService,
private val thirdPartyService: Lazy<ThirdPartyService>, private val thirdPartyService: Lazy<ThirdPartyService>,
@ -129,6 +131,7 @@ internal class DefaultSession @Inject constructor(
FilterService by filterService.get(), FilterService by filterService.get(),
PushRuleService by pushRuleService.get(), PushRuleService by pushRuleService.get(),
PushersService by pushersService.get(), PushersService by pushersService.get(),
EventService by eventService.get(),
TermsService by termsService.get(), TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(), InitialSyncProgressService by initialSyncProgressService.get(),
SecureStorageService by secureStorageService.get(), SecureStorageService by secureStorageService.get(),

View File

@ -32,10 +32,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.accountdata.AccountDataService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider
import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.call.CallEventProcessor
import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor
import org.matrix.android.sdk.internal.session.events.DefaultEventService
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
@ -357,6 +359,9 @@ internal abstract class SessionModule {
@Binds @Binds
abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService
@Binds
abstract fun bindEventService(service: DefaultEventService): EventService
@Binds @Binds
abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService

View File

@ -21,9 +21,11 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.EventInsertType
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@SessionScope
internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
: EventInsertLiveProcessor { : EventInsertLiveProcessor {
@ -51,6 +53,15 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
eventsToPostProcess.add(event) eventsToPostProcess.add(event)
} }
fun shouldProcessFastLane(eventType: String): Boolean {
return eventType == EventType.CALL_INVITE
}
suspend fun processFastLane(event: Event) {
eventsToPostProcess.add(event)
onPostProcess()
}
override suspend fun onPostProcess() { override suspend fun onPostProcess() {
eventsToPostProcess.forEach { eventsToPostProcess.forEach {
dispatchToCallSignalingHandlerIfNeeded(it) dispatchToCallSignalingHandlerIfNeeded(it)
@ -60,7 +71,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
// TODO might check if an invite is not closed (hangup/answsered) in the same event batch? // TODO might check if an invite is not closed (hangup/answered) in the same event batch?
event.roomId ?: return Unit.also { event.roomId ?: return Unit.also {
Timber.w("Event with no room id ${event.eventId}") Timber.w("Event with no room id ${event.eventId}")
} }

View File

@ -168,6 +168,14 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
return return
} }
val content = event.getClearContent().toModel<CallInviteContent>() ?: return val content = event.getClearContent().toModel<CallInviteContent>() ?: return
content.callId ?: return
if (activeCallHandler.getCallWithId(content.callId) != null) {
// Call is already known, maybe due to fast lane. Ignore
Timber.d("Ignoring already known call invite")
return
}
val incomingCall = mxCallFactory.createIncomingCall( val incomingCall = mxCallFactory.createIncomingCall(
roomId = event.roomId, roomId = event.roomId,
opponentUserId = event.senderId, opponentUserId = event.senderId,

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.events
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.session.call.CallEventProcessor
import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
import javax.inject.Inject
internal class DefaultEventService @Inject constructor(
private val getEventTask: GetEventTask,
private val callEventProcessor: CallEventProcessor
) : EventService {
override suspend fun getEvent(roomId: String, eventId: String): Event {
val event = getEventTask.execute(GetEventTask.Params(roomId, eventId))
// Fast lane to the call event processors: try to make the incoming call ring faster
if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
callEventProcessor.processFastLane(event)
}
return event
}
}

View File

@ -16,8 +16,10 @@
package org.matrix.android.sdk.internal.session.notification package org.matrix.android.sdk.internal.session.notification
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.PushRuleService
import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.RuleKind
import org.matrix.android.sdk.api.pushrules.RuleScope
import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.RuleSetKey
import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.getActions
import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.PushRule
@ -45,6 +47,7 @@ internal class DefaultPushRuleService @Inject constructor(
private val addPushRuleTask: AddPushRuleTask, private val addPushRuleTask: AddPushRuleTask,
private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask,
private val removePushRuleTask: RemovePushRuleTask, private val removePushRuleTask: RemovePushRuleTask,
private val pushRuleFinder: PushRuleFinder,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
@SessionDatabase private val monarchy: Monarchy @SessionDatabase private val monarchy: Monarchy
) : PushRuleService { ) : PushRuleService {
@ -130,6 +133,12 @@ internal class DefaultPushRuleService @Inject constructor(
} }
} }
override fun getActions(event: Event): List<Action> {
val rules = getPushRules(RuleScope.GLOBAL).getAllRules()
return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty()
}
// fun processEvents(events: List<Event>) { // fun processEvents(events: List<Event>) {
// var hasDoneSomething = false // var hasDoneSomething = false
// events.forEach { event -> // events.forEach { event ->

View File

@ -16,9 +16,7 @@
package org.matrix.android.sdk.internal.session.notification package org.matrix.android.sdk.internal.session.notification
import org.matrix.android.sdk.api.pushrules.ConditionResolver
import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
@ -35,7 +33,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
internal class DefaultProcessEventForPushTask @Inject constructor( internal class DefaultProcessEventForPushTask @Inject constructor(
private val defaultPushRuleService: DefaultPushRuleService, private val defaultPushRuleService: DefaultPushRuleService,
private val conditionResolver: ConditionResolver, private val pushRuleFinder: PushRuleFinder,
@UserId private val userId: String @UserId private val userId: String
) : ProcessEventForPushTask { ) : ProcessEventForPushTask {
@ -72,7 +70,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" + Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules") " to check for push rules with ${params.rules.size} rules")
allEvents.forEach { event -> allEvents.forEach { event ->
fulfilledBingRule(event, params.rules)?.let { pushRuleFinder.fulfilledBingRule(event, params.rules)?.let {
Timber.v("[PushRules] Rule $it match for event ${event.eventId}") Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it) defaultPushRuleService.dispatchBing(event, it)
} }
@ -94,13 +92,4 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
defaultPushRuleService.dispatchFinish() defaultPushRuleService.dispatchFinish()
} }
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
return rules.firstOrNull { rule ->
// All conditions must hold true for an event in order to apply the action for the event.
rule.enabled && rule.conditions?.all {
it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
} ?: false
}
}
} }

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.notification
import org.matrix.android.sdk.api.pushrules.ConditionResolver
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.events.model.Event
import javax.inject.Inject
internal class PushRuleFinder @Inject constructor(
private val conditionResolver: ConditionResolver
) {
fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
return rules.firstOrNull { rule ->
// All conditions must hold true for an event in order to apply the action for the event.
rule.enabled && rule.conditions?.all {
it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
} ?: false
}
}
}

View File

@ -79,9 +79,11 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultDeleteTagFromRoo
import org.matrix.android.sdk.internal.session.room.tags.DeleteTagFromRoomTask import org.matrix.android.sdk.internal.session.room.tags.DeleteTagFromRoomTask
import org.matrix.android.sdk.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask
import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetContextOfEventTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetContextOfEventTask
import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetEventTask
import org.matrix.android.sdk.internal.session.room.timeline.DefaultPaginationTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultPaginationTask
import org.matrix.android.sdk.internal.session.room.timeline.FetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.FetchTokenAndPaginateTask
import org.matrix.android.sdk.internal.session.room.timeline.GetContextOfEventTask import org.matrix.android.sdk.internal.session.room.timeline.GetContextOfEventTask
import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
import org.matrix.android.sdk.internal.session.room.timeline.PaginationTask import org.matrix.android.sdk.internal.session.room.timeline.PaginationTask
import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
@ -228,4 +230,7 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
@Binds
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
} }

View File

@ -16,28 +16,49 @@
package org.matrix.android.sdk.internal.session.room.timeline package org.matrix.android.sdk.internal.session.room.timeline
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
// TODO Add parent task internal interface GetEventTask : Task<GetEventTask.Params, Event> {
data class Params(
internal class GetEventTask @Inject constructor(
private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver
) : Task<GetEventTask.Params, Event> {
internal data class Params(
val roomId: String, val roomId: String,
val eventId: String val eventId: String
) )
}
override suspend fun execute(params: Params): Event { internal class DefaultGetEventTask @Inject constructor(
return executeRequest(globalErrorReceiver) { private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver,
private val eventDecryptor: EventDecryptor
) : GetEventTask {
override suspend fun execute(params: GetEventTask.Params): Event {
val event = executeRequest(globalErrorReceiver) {
roomAPI.getEvent(params.roomId, params.eventId) roomAPI.getEvent(params.roomId, params.eventId)
} }
// Try to decrypt the Event
if (event.isEncrypted()) {
tryOrNull(message = "Unable to decrypt the event") {
eventDecryptor.decryptEvent(event, "")
}
?.let { result ->
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
)
}
}
return event
} }
} }

View File

@ -408,6 +408,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
private fun decryptIfNeeded(event: Event, roomId: String) { private fun decryptIfNeeded(event: Event, roomId: String) {
try { try {
// Event from sync does not have roomId, so add it to the event first
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "") val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
event.mxDecryptionResult = OlmDecryptionResult( event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,

View File

@ -31,6 +31,7 @@ import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.features.badge.BadgeProxy import im.vector.app.features.badge.BadgeProxy
import im.vector.app.features.notifications.NotifiableEventResolver import im.vector.app.features.notifications.NotifiableEventResolver
@ -40,6 +41,10 @@ import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.notifications.SimpleNotifiableEvent import im.vector.app.features.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@ -55,6 +60,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private lateinit var pusherManager: PushersManager private lateinit var pusherManager: PushersManager
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences private lateinit var vectorPreferences: VectorPreferences
private lateinit var wifiDetector: WifiDetector
private val coroutineScope = CoroutineScope(SupervisorJob())
// UI handler // UI handler
private val mUIHandler by lazy { private val mUIHandler by lazy {
@ -69,6 +77,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
pusherManager = pusherManager() pusherManager = pusherManager()
activeSessionHolder = activeSessionHolder() activeSessionHolder = activeSessionHolder()
vectorPreferences = vectorPreferences() vectorPreferences = vectorPreferences()
wifiDetector = wifiDetector()
} }
} }
@ -78,6 +87,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* @param message the message * @param message the message
*/ */
override fun onMessageReceived(message: RemoteMessage) { override fun onMessageReceived(message: RemoteMessage) {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.d("## onMessageReceived() %s", message.data.toString())
}
Timber.d("## onMessageReceived() from FCM with priority %s", message.priority)
// Diagnostic Push // Diagnostic Push
if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) { if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
val intent = Intent(NotificationUtils.PUSH_ACTION) val intent = Intent(NotificationUtils.PUSH_ACTION)
@ -90,14 +104,10 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
return return
} }
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("## onMessageReceived() %s", message.data.toString())
Timber.i("## onMessageReceived() from FCM with priority %s", message.priority)
}
mUIHandler.post { mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// we are in foreground, let the sync do the things? // we are in foreground, let the sync do the things?
Timber.v("PUSH received in a foreground state, ignore") Timber.d("PUSH received in a foreground state, ignore")
} else { } else {
onMessageReceivedInternal(message.data) onMessageReceivedInternal(message.data)
} }
@ -140,7 +150,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private fun onMessageReceivedInternal(data: Map<String, String>) { private fun onMessageReceivedInternal(data: Map<String, String>) {
try { try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("## onMessageReceivedInternal() : $data") Timber.d("## onMessageReceivedInternal() : $data")
} else {
Timber.d("## onMessageReceivedInternal() : $data")
} }
// update the badge counter // update the badge counter
@ -156,9 +168,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val roomId = data["room_id"] val roomId = data["room_id"]
if (isEventAlreadyKnown(eventId, roomId)) { if (isEventAlreadyKnown(eventId, roomId)) {
Timber.i("Ignoring push, event already known") Timber.d("Ignoring push, event already known")
} else { } else {
Timber.v("Requesting background sync") // Try to get the Event content faster
Timber.d("Requesting event in fast lane")
getEventFastLane(session, roomId, eventId)
Timber.d("Requesting background sync")
session.requireBackgroundSync() session.requireBackgroundSync()
} }
} }
@ -167,6 +183,36 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
} }
} }
private fun getEventFastLane(session: Session, roomId: String?, eventId: String?) {
roomId?.takeIf { it.isNotEmpty() } ?: return
eventId?.takeIf { it.isNotEmpty() } ?: return
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(roomId)) {
return
}
if (wifiDetector.isConnectedToWifi().not()) {
Timber.d("No WiFi network, do not get Event")
return
}
coroutineScope.launch {
Timber.d("Fast lane: start request")
val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
resolvedEvent
?.also { Timber.d("Fast lane: notify drawer") }
?.let {
it.isPushGatewayEvent = true
notificationDrawerManager.onNotifiableEventReceived(it)
notificationDrawerManager.refreshNotificationDrawer()
}
}
}
// check if the event was not yet received // check if the event was not yet received
// a previous catchup might have already retrieved the notified event // a previous catchup might have already retrieved the notified event
private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean { private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {

View File

@ -26,6 +26,7 @@ import im.vector.app.EmojiCompatWrapper
import im.vector.app.VectorApplication import im.vector.app.VectorApplication
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.utils.AssetReader import im.vector.app.core.utils.AssetReader
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
@ -140,6 +141,8 @@ interface VectorComponent {
fun vectorPreferences(): VectorPreferences fun vectorPreferences(): VectorPreferences
fun wifiDetector(): WifiDetector
fun vectorFileLogger(): VectorFileLogger fun vectorFileLogger(): VectorFileLogger
fun uiStateRepository(): UiStateRepository fun uiStateRepository(): UiStateRepository

View File

@ -39,7 +39,7 @@ inline fun <T> LiveData<LiveEvent<T>>.observeEventFirstThrottle(owner: Lifecycle
val firstThrottler = FirstThrottler(minimumInterval) val firstThrottler = FirstThrottler(minimumInterval)
this.observe(owner, EventObserver { this.observe(owner, EventObserver {
if (firstThrottler.canHandle()) { if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) {
it.run(observer) it.run(observer)
} }
}) })

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2021 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.core.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import androidx.core.content.getSystemService
import org.matrix.android.sdk.api.extensions.orFalse
import timber.log.Timber
import javax.inject.Inject
class WifiDetector @Inject constructor(
context: Context
) {
private val connectivityManager = context.getSystemService<ConnectivityManager>()!!
fun isConnectedToWifi(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.activeNetwork
?.let { connectivityManager.getNetworkCapabilities(it) }
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
.orFalse()
} else {
@Suppress("DEPRECATION")
connectivityManager.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI
}
.also { Timber.d("isConnected to WiFi: $it") }
}
}

View File

@ -24,14 +24,27 @@ import android.os.SystemClock
class FirstThrottler(private val minimumInterval: Long = 800) { class FirstThrottler(private val minimumInterval: Long = 800) {
private var lastDate = 0L private var lastDate = 0L
fun canHandle(): Boolean { sealed class CanHandlerResult {
object Yes : CanHandlerResult()
data class No(val shouldWaitMillis: Long) : CanHandlerResult()
fun waitMillis(): Long {
return when (this) {
Yes -> 0
is No -> shouldWaitMillis
}
}
}
fun canHandle(): CanHandlerResult {
val now = SystemClock.elapsedRealtime() val now = SystemClock.elapsedRealtime()
if (now > lastDate + minimumInterval) { val delaySinceLast = now - lastDate
if (delaySinceLast > minimumInterval) {
lastDate = now lastDate = now
return true return CanHandlerResult.Yes
} }
// Too soon // Too soon
return false return CanHandlerResult.No(minimumInterval - delaySinceLast)
} }
} }

View File

@ -26,9 +26,11 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.isEdition
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
@ -42,7 +44,8 @@ import javax.inject.Inject
* The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that, * The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that,
* this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk.
*/ */
class NotifiableEventResolver @Inject constructor(private val stringProvider: StringProvider, class NotifiableEventResolver @Inject constructor(
private val stringProvider: StringProvider,
private val noticeEventFormatter: NoticeEventFormatter, private val noticeEventFormatter: NoticeEventFormatter,
private val displayableEventFormatter: DisplayableEventFormatter) { private val displayableEventFormatter: DisplayableEventFormatter) {
@ -84,6 +87,47 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
} }
} }
fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? {
if (event.getClearType() != EventType.MESSAGE) return null
// Ignore message edition
if (event.isEdition()) return null
val actions = session.getActions(event)
val notificationAction = actions.toNotificationAction()
return if (notificationAction.shouldNotify) {
val user = session.getUser(event.senderId!!) ?: return null
val timelineEvent = TimelineEvent(
root = event,
localId = -1,
eventId = event.eventId!!,
displayIndex = 0,
senderInfo = SenderInfo(
userId = user.userId,
displayName = user.getBestName(),
isUniqueDisplayName = true,
avatarUrl = user.avatarUrl
)
)
val notifiableEvent = resolveMessageEvent(timelineEvent, session)
if (notifiableEvent == null) {
Timber.d("## Failed to resolve event")
// TODO
null
} else {
notifiableEvent.noisy = !notificationAction.soundName.isNullOrBlank()
notifiableEvent
}
} else {
Timber.d("Matched push rule is set to not notify")
null
}
}
private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? { private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? {
// The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...) // The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/) val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)

View File

@ -26,6 +26,7 @@ import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.FirstThrottler
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import me.gujun.android.span.span import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -88,7 +89,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
// If we support multi session, event list should be per userId // If we support multi session, event list should be per userId
// Currently only manage single session // Currently only manage single session
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.v("%%%%%%%% onNotifiableEventReceived $notifiableEvent") Timber.d("onNotifiableEventReceived(): $notifiableEvent")
} else {
Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.isPushGatewayEvent}")
} }
synchronized(eventList) { synchronized(eventList) {
val existing = eventList.firstOrNull { it.eventId == notifiableEvent.eventId } val existing = eventList.firstOrNull { it.eventId == notifiableEvent.eventId }
@ -194,10 +197,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
notificationUtils.cancelNotificationMessage(roomId, ROOM_INVITATION_NOTIFICATION_ID) notificationUtils.cancelNotificationMessage(roomId, ROOM_INVITATION_NOTIFICATION_ID)
} }
private var firstThrottler = FirstThrottler(200)
fun refreshNotificationDrawer() { fun refreshNotificationDrawer() {
// Implement last throttler // Implement last throttler
Timber.v("refreshNotificationDrawer()") val canHandle = firstThrottler.canHandle()
Timber.v("refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms")
backgroundHandler.removeCallbacksAndMessages(null) backgroundHandler.removeCallbacksAndMessages(null)
backgroundHandler.postDelayed( backgroundHandler.postDelayed(
{ {
try { try {
@ -206,7 +213,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
// It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer // It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer
Timber.w(throwable, "refreshNotificationDrawerBg failure") Timber.w(throwable, "refreshNotificationDrawerBg failure")
} }
}, 200) },
canHandle.waitMillis())
} }
@WorkerThread @WorkerThread
@ -544,7 +552,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
return bitmapLoader.getRoomBitmap(roomAvatarPath) return bitmapLoader.getRoomBitmap(roomAvatarPath)
} }
private fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean { fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean {
return currentRoomId != null && roomId == currentRoomId return currentRoomId != null && roomId == currentRoomId
} }

View File

@ -101,7 +101,7 @@ class VectorSettingsHelpAboutFragment @Inject constructor(
// third party notice // third party notice
findPreference<VectorPreference>(VectorPreferences.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY)!! findPreference<VectorPreference>(VectorPreferences.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY)!!
.onPreferenceClickListener = Preference.OnPreferenceClickListener { .onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (firstThrottler.canHandle()) { if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) {
activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES)
} }
false false