From 0b9b8251f940c892afb7bee62162980f2ed02a57 Mon Sep 17 00:00:00 2001 From: sim Date: Thu, 8 Sep 2022 10:34:56 +0200 Subject: [PATCH] Do not intent to VectorMessagingReceiver from FirebaseReceiver Use VectorMessagingHelper to directly call onMessage --- .../vector/app/push/fcm/FirebaseReceiver.kt | 13 +- .../app/core/pushers/VectorMessagingHelper.kt | 192 ++++++++++++++++++ .../core/pushers/VectorMessagingReceiver.kt | 159 +-------------- 3 files changed, 198 insertions(+), 166 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/pushers/VectorMessagingHelper.kt diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FirebaseReceiver.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FirebaseReceiver.kt index eb983fc8c5..d744634991 100644 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FirebaseReceiver.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FirebaseReceiver.kt @@ -16,7 +16,6 @@ package im.vector.app.push.fcm -import android.content.Intent import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage 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.pushers.FcmHelper import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.pushers.VectorMessagingHelper import im.vector.app.features.settings.VectorPreferences 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 javax.inject.Inject @@ -38,6 +35,7 @@ class FirebaseReceiver : FirebaseMessagingService() { @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var pushersManager: PushersManager + @Inject lateinit var vectorMessagingHelper: VectorMessagingHelper override fun onNewToken(token: String) { Timber.d("New Firebase token") @@ -49,11 +47,6 @@ class FirebaseReceiver : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { Timber.d("New Firebase message") - val intent = Intent() - 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) + vectorMessagingHelper.onMessage(JSONObject(message.data as Map<*, *>).toString()) } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingHelper.kt new file mode 100644 index 0000000000..80d4e91a76 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingHelper.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt index 4871b9de74..e99e3fdd16 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt @@ -17,37 +17,17 @@ package im.vector.app.core.pushers import android.content.Context -import android.content.Intent -import android.os.Handler -import android.os.Looper import android.widget.Toast -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.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.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 org.unifiedpush.android.connector.EXTRA_BYTES_MESSAGE -import org.unifiedpush.android.connector.EXTRA_TOKEN import org.unifiedpush.android.connector.MessagingReceiver import timber.log.Timber import javax.inject.Inject @@ -59,28 +39,16 @@ private val loggerTag = LoggerTag("Push", LoggerTag.SYNC) */ @AndroidEntryPoint class VectorMessagingReceiver : MessagingReceiver() { - @Inject lateinit var notificationDrawerManager: NotificationDrawerManager - @Inject lateinit var notifiableEventResolver: NotifiableEventResolver @Inject lateinit var pushersManager: PushersManager - @Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorPreferences: VectorPreferences - @Inject lateinit var vectorDataStore: VectorDataStore - @Inject lateinit var wifiDetector: WifiDetector + @Inject lateinit var vectorMessagingHelper: VectorMessagingHelper @Inject lateinit var guardServiceStarter: GuardServiceStarter - @Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var unifiedPushStore: UnifiedPushStore - @Inject lateinit var pushParser: PushParser - @Inject lateinit var actionIds: NotificationActionIds - @Inject lateinit var buildMeta: BuildMeta + @Inject lateinit var unifiedPushHelper: UnifiedPushHelper private val coroutineScope = CoroutineScope(SupervisorJob()) - // UI handler - private val mUIHandler by lazy { - Handler(Looper.getMainLooper()) - } - /** * Called when message is received. * @@ -89,40 +57,7 @@ class VectorMessagingReceiver : MessagingReceiver() { * @param instance connection, for multi-account */ override fun onMessage(context: Context, message: ByteArray, instance: String) { - Timber.tag(loggerTag.value).d("## onMessage() received") - - 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) } - } - } + vectorMessagingHelper.onMessage(String(message)) } 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 - } }