Do not intent to VectorMessagingReceiver from FirebaseReceiver

Use VectorMessagingHelper to directly call onMessage
This commit is contained in:
sim 2022-09-08 10:34:56 +02:00
parent 00a04b807e
commit 0b9b8251f9
3 changed files with 198 additions and 166 deletions

View File

@ -16,7 +16,6 @@
package im.vector.app.push.fcm package im.vector.app.push.fcm
import android.content.Intent
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -24,11 +23,9 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.FcmHelper import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.VectorMessagingHelper
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import org.json.JSONObject import org.json.JSONObject
import org.unifiedpush.android.connector.ACTION_MESSAGE
import org.unifiedpush.android.connector.EXTRA_BYTES_MESSAGE
import org.unifiedpush.android.connector.EXTRA_TOKEN
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -38,6 +35,7 @@ class FirebaseReceiver : FirebaseMessagingService() {
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var pushersManager: PushersManager @Inject lateinit var pushersManager: PushersManager
@Inject lateinit var vectorMessagingHelper: VectorMessagingHelper
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
Timber.d("New Firebase token") Timber.d("New Firebase token")
@ -49,11 +47,6 @@ class FirebaseReceiver : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) { override fun onMessageReceived(message: RemoteMessage) {
Timber.d("New Firebase message") Timber.d("New Firebase message")
val intent = Intent() vectorMessagingHelper.onMessage(JSONObject(message.data as Map<*, *>).toString())
intent.action = ACTION_MESSAGE
intent.setPackage(baseContext.packageName)
intent.putExtra(EXTRA_BYTES_MESSAGE, JSONObject(message.data as Map<*, *>).toString().toByteArray())
intent.putExtra(EXTRA_TOKEN, fcmHelper.getFcmToken())
baseContext.sendBroadcast(intent)
} }
} }

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) 2022 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.pushers
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.model.PushData
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import timber.log.Timber
import javax.inject.Inject
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/**
* Hilt injection happen at super.onReceive().
*/
class VectorMessagingHelper @Inject constructor(
private val notificationDrawerManager: NotificationDrawerManager,
private val notifiableEventResolver: NotifiableEventResolver,
private val activeSessionHolder: ActiveSessionHolder,
private val vectorPreferences: VectorPreferences,
private val vectorDataStore: VectorDataStore,
private val wifiDetector: WifiDetector,
private val unifiedPushHelper: UnifiedPushHelper,
private val pushParser: PushParser,
private val actionIds: NotificationActionIds,
private val context: Context,
private val buildMeta: BuildMeta
) {
private val coroutineScope = CoroutineScope(SupervisorJob())
// UI handler
private val mUIHandler by lazy {
Handler(Looper.getMainLooper())
}
/**
* Called when message is received.
*
* @param context the Android context
* @param message the message
*/
fun onMessage(message: String) {
Timber.tag(loggerTag.value).d("## onMessage() received")
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessage() $message")
}
runBlocking {
vectorDataStore.incrementPushCounter()
}
val pushData = pushParser.parseData(message, unifiedPushHelper.isEmbeddedDistributor())
?: return Unit.also { Timber.tag(loggerTag.value).w("Invalid received data Json format") }
// Diagnostic Push
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(actionIds.push)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
return
}
if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.tag(loggerTag.value).i("Notification are disabled for this device")
return
}
mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// we are in foreground, let the sync do the things?
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else {
coroutineScope.launch(Dispatchers.IO) { onMessageReceivedInternal(pushData) }
}
}
}
/**
* Internal receive method.
*
* @param pushData Object containing message data.
*/
private suspend fun onMessageReceivedInternal(pushData: PushData) {
try {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
}
val session = activeSessionHolder.getOrInitializeSession(startSync = false)
if (session == null) {
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
} else {
if (isEventAlreadyKnown(pushData)) {
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else {
// Try to get the Event content faster
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
getEventFastLane(session, pushData)
Timber.tag(loggerTag.value).d("Requesting background sync")
session.syncService().requireBackgroundSync()
}
}
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed")
}
}
private suspend fun getEventFastLane(session: Session, pushData: PushData) {
pushData.roomId ?: return
pushData.eventId ?: return
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(pushData.roomId)) {
return
}
if (wifiDetector.isConnectedToWifi().not()) {
Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
return
}
Timber.tag(loggerTag.value).d("Fast lane: start request")
val event = tryOrNull { session.eventService().getEvent(pushData.roomId, pushData.eventId) } ?: return
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)
resolvedEvent
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
notificationDrawerManager.updateEvents { it.onNotifiableEventReceived(resolvedEvent) }
}
}
// check if the event was not yet received
// a previous catchup might have already retrieved the notified event
private fun isEventAlreadyKnown(pushData: PushData): Boolean {
if (pushData.eventId != null && pushData.roomId != null) {
try {
val session = activeSessionHolder.getSafeActiveSession() ?: return false
val room = session.getRoom(pushData.roomId) ?: return false
return room.getTimelineEvent(pushData.eventId) != null
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
}
}
return false
}
}

View File

@ -17,37 +17,17 @@
package im.vector.app.core.pushers package im.vector.app.core.pushers
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.model.PushData
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.unifiedpush.android.connector.EXTRA_BYTES_MESSAGE
import org.unifiedpush.android.connector.EXTRA_TOKEN
import org.unifiedpush.android.connector.MessagingReceiver import org.unifiedpush.android.connector.MessagingReceiver
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -59,28 +39,16 @@ private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class VectorMessagingReceiver : MessagingReceiver() { class VectorMessagingReceiver : MessagingReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
@Inject lateinit var pushersManager: PushersManager @Inject lateinit var pushersManager: PushersManager
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var vectorDataStore: VectorDataStore @Inject lateinit var vectorMessagingHelper: VectorMessagingHelper
@Inject lateinit var wifiDetector: WifiDetector
@Inject lateinit var guardServiceStarter: GuardServiceStarter @Inject lateinit var guardServiceStarter: GuardServiceStarter
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@Inject lateinit var unifiedPushStore: UnifiedPushStore @Inject lateinit var unifiedPushStore: UnifiedPushStore
@Inject lateinit var pushParser: PushParser @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@Inject lateinit var actionIds: NotificationActionIds
@Inject lateinit var buildMeta: BuildMeta
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
// UI handler
private val mUIHandler by lazy {
Handler(Looper.getMainLooper())
}
/** /**
* Called when message is received. * Called when message is received.
* *
@ -89,40 +57,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
* @param instance connection, for multi-account * @param instance connection, for multi-account
*/ */
override fun onMessage(context: Context, message: ByteArray, instance: String) { override fun onMessage(context: Context, message: ByteArray, instance: String) {
Timber.tag(loggerTag.value).d("## onMessage() received") vectorMessagingHelper.onMessage(String(message))
val sMessage = String(message)
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessage() $sMessage")
}
runBlocking {
vectorDataStore.incrementPushCounter()
}
val pushData = pushParser.parseData(sMessage, unifiedPushHelper.isEmbeddedDistributor())
?: return Unit.also { Timber.tag(loggerTag.value).w("Invalid received data Json format") }
// Diagnostic Push
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(actionIds.push)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
return
}
if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.tag(loggerTag.value).i("Notification are disabled for this device")
return
}
mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// we are in foreground, let the sync do the things?
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else {
coroutineScope.launch(Dispatchers.IO) { onMessageReceivedInternal(pushData) }
}
}
} }
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) { override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
@ -168,92 +103,4 @@ class VectorMessagingReceiver : MessagingReceiver() {
} }
} }
} }
override fun onReceive(context: Context, intent: Intent) {
// Injections happens here
super.onReceive(context, intent)
// if it is from FirebaseReceiver, then the token has been rejected
if (unifiedPushHelper.isEmbeddedDistributor() &&
intent.getStringExtra(EXTRA_TOKEN) == fcmHelper.getFcmToken()) {
intent.getByteArrayExtra(EXTRA_BYTES_MESSAGE)?.let {
onMessage(context, it, "")
}
}
}
/**
* Internal receive method.
*
* @param pushData Object containing message data.
*/
private suspend fun onMessageReceivedInternal(pushData: PushData) {
try {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
}
val session = activeSessionHolder.getOrInitializeSession(startSync = false)
if (session == null) {
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
} else {
if (isEventAlreadyKnown(pushData)) {
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else {
// Try to get the Event content faster
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
getEventFastLane(session, pushData)
Timber.tag(loggerTag.value).d("Requesting background sync")
session.syncService().requireBackgroundSync()
}
}
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed")
}
}
private suspend fun getEventFastLane(session: Session, pushData: PushData) {
pushData.roomId ?: return
pushData.eventId ?: return
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(pushData.roomId)) {
return
}
if (wifiDetector.isConnectedToWifi().not()) {
Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
return
}
Timber.tag(loggerTag.value).d("Fast lane: start request")
val event = tryOrNull { session.eventService().getEvent(pushData.roomId, pushData.eventId) } ?: return
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)
resolvedEvent
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
notificationDrawerManager.updateEvents { it.onNotifiableEventReceived(resolvedEvent) }
}
}
// check if the event was not yet received
// a previous catchup might have already retrieved the notified event
private fun isEventAlreadyKnown(pushData: PushData): Boolean {
if (pushData.eventId != null && pushData.roomId != null) {
try {
val session = activeSessionHolder.getSafeActiveSession() ?: return false
val room = session.getRoom(pushData.roomId) ?: return false
return room.getTimelineEvent(pushData.eventId) != null
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
}
}
return false
}
} }