Merge pull request #7085 from vector-im/feature/bma/fix_push

Feature/bma/fix push
This commit is contained in:
Benoit Marty 2022-09-09 18:03:10 +02:00 committed by GitHub
commit 4b63f4b9bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 369 additions and 247 deletions

1
changelog.d/6936.misc Normal file
View file

@ -0,0 +1 @@
Smaff refactor of UnifiedPushHelper

1
changelog.d/7068.bugfix Normal file
View file

@ -0,0 +1 @@
Fix push with FCM

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application> <application>
@ -8,18 +9,14 @@
android:name="firebase_analytics_collection_deactivated" android:name="firebase_analytics_collection_deactivated"
android:value="true" /> android:value="true" />
<receiver <!-- Add tools:ignore="Instantiatable" for the error reported only by the CI :/ -->
android:name="im.vector.app.push.fcm.EmbeddedFCMDistributor" <service android:name="im.vector.app.push.fcm.VectorFirebaseMessagingService"
android:enabled="true" android:exported="false"
android:exported="false"> tools:ignore="Instantiatable">
<intent-filter> <intent-filter>
<action android:name="org.unifiedpush.android.distributor.REGISTER" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="org.unifiedpush.android.distributor.UNREGISTER" />
</intent-filter> </intent-filter>
</service>
</receiver>
</application> </application>
</manifest> </manifest>

View file

@ -257,7 +257,7 @@ dependencies {
// UnifiedPush // UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.0.1' implementation 'com.github.UnifiedPush:android-connector:2.0.1'
// UnifiedPush gplay flavor only // UnifiedPush gplay flavor only
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.3') { gplayImplementation('com.google.firebase:firebase-messaging:23.0.8') {
exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'

View file

@ -28,20 +28,6 @@
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<receiver
android:name=".fdroid.receiver.KeepInternalDistributor"
android:enabled="true"
android:exported="false">
<intent-filter>
<!--
This action is checked to track installed and uninstalled distributors.
We declare it to keep the background sync as an internal
unifiedpush distributor.
-->
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
</intent-filter>
</receiver>
<service <service
android:name=".fdroid.service.GuardAndroidService" android:name=".fdroid.service.GuardAndroidService"
android:exported="false" android:exported="false"

View file

@ -1,27 +0,0 @@
/*
* 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.push.fcm
import android.content.Context
import org.unifiedpush.android.embedded_fcm_distributor.EmbeddedDistributorReceiver
class EmbeddedFCMDistributor : EmbeddedDistributorReceiver() {
override fun getEndpoint(context: Context, token: String, instance: String): String {
// Here token is the FCM Token, used by the gateway (sygnal)
return token
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.push.fcm
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dagger.hilt.android.AndroidEntryPoint
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.PushParser
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.pushers.VectorPushHandler
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber
import javax.inject.Inject
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
@AndroidEntryPoint
class VectorFirebaseMessagingService : FirebaseMessagingService() {
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var pushersManager: PushersManager
@Inject lateinit var pushParser: PushParser
@Inject lateinit var vectorPushHandler: VectorPushHandler
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
override fun onNewToken(token: String) {
Timber.tag(loggerTag.value).d("New Firebase token")
fcmHelper.storeFcmToken(token)
if (
vectorPreferences.areNotificationEnabledForDevice() &&
activeSessionHolder.hasActiveSession() &&
unifiedPushHelper.isEmbeddedDistributor()
) {
pushersManager.enqueueRegisterPusher(token, getString(R.string.pusher_http_url))
}
}
override fun onMessageReceived(message: RemoteMessage) {
Timber.tag(loggerTag.value).d("New Firebase message")
pushParser.parsePushDataFcm(message.data).let {
vectorPushHandler.handle(it)
}
}
}

View file

@ -413,7 +413,7 @@
<!-- UnifiedPush --> <!-- UnifiedPush -->
<receiver <receiver
android:name=".core.pushers.VectorMessagingReceiver" android:name=".core.pushers.VectorUnifiedPushMessagingReceiver"
android:enabled="true" android:enabled="true"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -424,6 +424,20 @@
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" /> <action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".core.pushers.KeepInternalDistributor"
android:enabled="true"
android:exported="false">
<intent-filter>
<!--
This action is checked to track installed and uninstalled distributors.
We declare it to keep the background sync as an internal
unifiedpush distributor.
-->
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.fdroid.receiver package im.vector.app.core.pushers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context

View file

@ -24,28 +24,34 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.api.util.MatrixJsonParser
import javax.inject.Inject import javax.inject.Inject
/**
* Parse the received data from Push. Json format are different depending on the source.
*
* Notifications received by FCM are formatted by the matrix gateway [1]. The data send to FCM is the content
* of the "notification" attribute of the json sent to the gateway [2][3].
* On the other side, with UnifiedPush, the content of the message received is the content posted to the push
* gateway endpoint [3].
*
* *Note*: If we want to get the same content with FCM and unifiedpush, we can do a new sygnal pusher [4].
*
* [1] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py
* [2] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py#L366
* [3] https://spec.matrix.org/latest/push-gateway-api/
* [4] https://github.com/p1gp1g/sygnal/blob/unifiedpush/sygnal/upfcmpushkin.py (Not tested for a while)
*/
class PushParser @Inject constructor() { class PushParser @Inject constructor() {
/** fun parsePushDataUnifiedPush(message: ByteArray): PushData? {
* Parse the received data from Push. Json format are different depending on the source. return MatrixJsonParser.getMoshi().let {
* tryOrNull { it.adapter(PushDataUnifiedPush::class.java).fromJson(String(message)) }?.toPushData()
* Notifications received by FCM are formatted by the matrix gateway [1]. The data send to FCM is the content
* of the "notification" attribute of the json sent to the gateway [2][3].
* On the other side, with UnifiedPush, the content of the message received is the content posted to the push
* gateway endpoint [3].
*
* *Note*: If we want to get the same content with FCM and unifiedpush, we can do a new sygnal pusher [4].
*
* [1] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py
* [2] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py#L366
* [3] https://spec.matrix.org/latest/push-gateway-api/
* [4] https://github.com/p1gp1g/sygnal/blob/unifiedpush/sygnal/upfcmpushkin.py (Not tested for a while)
*/
fun parseData(message: String, firebaseFormat: Boolean): PushData? {
val moshi = MatrixJsonParser.getMoshi()
return if (firebaseFormat) {
tryOrNull { moshi.adapter(PushDataFcm::class.java).fromJson(message) }?.toPushData()
} else {
tryOrNull { moshi.adapter(PushDataUnifiedPush::class.java).fromJson(message) }?.toPushData()
} }
} }
fun parsePushDataFcm(message: Map<String, String?>): PushData {
val pushDataFcm = PushDataFcm(
eventId = message["event_id"],
roomId = message["room_id"],
unread = message["unread"]?.let { tryOrNull { Integer.parseInt(it) } },
)
return pushDataFcm.toPushData()
}
} }

View file

@ -29,7 +29,7 @@ import kotlin.math.abs
private const val DEFAULT_PUSHER_FILE_TAG = "mobile" private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
class PushersManager @Inject constructor( class PushersManager @Inject constructor(
private val unifiedPushStore: UnifiedPushStore, private val unifiedPushHelper: UnifiedPushHelper,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val localeProvider: LocaleProvider, private val localeProvider: LocaleProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
@ -39,9 +39,9 @@ class PushersManager @Inject constructor(
val currentSession = activeSessionHolder.getActiveSession() val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().testPush( currentSession.pushersService().testPush(
unifiedPushStore.getPushGateway()!!, unifiedPushHelper.getPushGateway() ?: return,
stringProvider.getString(R.string.pusher_app_id), stringProvider.getString(R.string.pusher_app_id),
unifiedPushStore.getEndpointOrToken().orEmpty(), unifiedPushHelper.getEndpointOrToken().orEmpty(),
TEST_EVENT_ID TEST_EVENT_ID
) )
} }

View file

@ -46,6 +46,9 @@ class UnifiedPushHelper @Inject constructor(
private val vectorFeatures: VectorFeatures, private val vectorFeatures: VectorFeatures,
private val fcmHelper: FcmHelper, private val fcmHelper: FcmHelper,
) { ) {
// Called when the home activity starts
// or when notifications are enabled
fun register( fun register(
activity: FragmentActivity, activity: FragmentActivity,
onDoneRunnable: Runnable? = null, onDoneRunnable: Runnable? = null,
@ -56,7 +59,14 @@ class UnifiedPushHelper @Inject constructor(
) )
} }
fun reRegister( // If registration is forced:
// * the current distributor (if any) is removed
// * The dialog is opened
//
// The registration is forced in 2 cases :
// * in the settings
// * in the troubleshoot list (doFix)
fun forceRegister(
activity: FragmentActivity, activity: FragmentActivity,
pushersManager: PushersManager, pushersManager: PushersManager,
onDoneRunnable: Runnable? = null onDoneRunnable: Runnable? = null
@ -86,7 +96,8 @@ class UnifiedPushHelper @Inject constructor(
// Un-register first // Un-register first
unregister(pushersManager) unregister(pushersManager)
} }
if (UnifiedPush.getDistributor(context).isNotEmpty()) { // the !force should not be needed
if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) {
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
onDoneRunnable?.run() onDoneRunnable?.run()
return@launch return@launch
@ -94,45 +105,26 @@ class UnifiedPushHelper @Inject constructor(
val distributors = UnifiedPush.getDistributors(context) val distributors = UnifiedPush.getDistributors(context)
if (distributors.size == 1 && !force) { if (!force && distributors.size == 1) {
UnifiedPush.saveDistributor(context, distributors.first()) UnifiedPush.saveDistributor(context, distributors.first())
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
onDoneRunnable?.run() onDoneRunnable?.run()
} else { } else {
openDistributorDialogInternal( openDistributorDialogInternal(
activity = activity, activity = activity,
pushersManager = pushersManager,
onDoneRunnable = onDoneRunnable, onDoneRunnable = onDoneRunnable,
distributors = distributors, distributors = distributors
unregisterFirst = force,
cancellable = !force
) )
} }
} }
} }
fun openDistributorDialog( // There is no case where this function is called
activity: FragmentActivity, // with a saved distributor and/or a pusher
pushersManager: PushersManager,
onDoneRunnable: Runnable,
) {
val distributors = UnifiedPush.getDistributors(activity)
openDistributorDialogInternal(
activity,
pushersManager,
onDoneRunnable, distributors,
unregisterFirst = true,
cancellable = true,
)
}
private fun openDistributorDialogInternal( private fun openDistributorDialogInternal(
activity: FragmentActivity, activity: FragmentActivity,
pushersManager: PushersManager?,
onDoneRunnable: Runnable?, onDoneRunnable: Runnable?,
distributors: List<String>, distributors: List<String>
unregisterFirst: Boolean,
cancellable: Boolean,
) { ) {
val internalDistributorName = stringProvider.getString( val internalDistributorName = stringProvider.getString(
if (fcmHelper.isFirebaseAvailable()) { if (fcmHelper.isFirebaseAvailable()) {
@ -154,16 +146,8 @@ class UnifiedPushHelper @Inject constructor(
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title)) .setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
.setItems(distributorsName.toTypedArray()) { _, which -> .setItems(distributorsName.toTypedArray()) { _, which ->
val distributor = distributors[which] val distributor = distributors[which]
if (distributor == UnifiedPush.getDistributor(context)) {
Timber.d("Same distributor selected again, no action")
return@setItems
}
activity.lifecycleScope.launch { activity.lifecycleScope.launch {
if (unregisterFirst) {
// Un-register first
unregister(pushersManager)
}
UnifiedPush.saveDistributor(context, distributor) UnifiedPush.saveDistributor(context, distributor)
Timber.i("Saving distributor: $distributor") Timber.i("Saving distributor: $distributor")
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
@ -176,7 +160,7 @@ class UnifiedPushHelper @Inject constructor(
UnifiedPush.registerApp(context) UnifiedPush.registerApp(context)
onDoneRunnable?.run() onDoneRunnable?.run()
} }
.setCancelable(cancellable) .setCancelable(true)
.show() .show()
} }
@ -184,7 +168,10 @@ class UnifiedPushHelper @Inject constructor(
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode) vectorPreferences.setFdroidSyncBackgroundMode(mode)
try { try {
pushersManager?.unregisterPusher(unifiedPushStore.getEndpointOrToken().orEmpty()) getEndpointOrToken()?.let {
Timber.d("Removing $it")
pushersManager?.unregisterPusher(it)
}
} catch (e: Exception) { } catch (e: Exception) {
Timber.d(e, "Probably unregistering a non existing pusher") Timber.d(e, "Probably unregistering a non existing pusher")
} }
@ -253,15 +240,20 @@ class UnifiedPushHelper @Inject constructor(
} }
fun isEmbeddedDistributor(): Boolean { fun isEmbeddedDistributor(): Boolean {
return UnifiedPush.getDistributor(context) == context.packageName && fcmHelper.isFirebaseAvailable() return isInternalDistributor() && fcmHelper.isFirebaseAvailable()
} }
fun isBackgroundSync(): Boolean { fun isBackgroundSync(): Boolean {
return UnifiedPush.getDistributor(context) == context.packageName && !fcmHelper.isFirebaseAvailable() return isInternalDistributor() && !fcmHelper.isFirebaseAvailable()
}
private fun isInternalDistributor(): Boolean {
return UnifiedPush.getDistributor(context).isEmpty() ||
UnifiedPush.getDistributor(context) == context.packageName
} }
fun getPrivacyFriendlyUpEndpoint(): String? { fun getPrivacyFriendlyUpEndpoint(): String? {
val endpoint = unifiedPushStore.getEndpointOrToken() val endpoint = getEndpointOrToken()
if (endpoint.isNullOrEmpty()) return null if (endpoint.isNullOrEmpty()) return null
if (isEmbeddedDistributor()) { if (isEmbeddedDistributor()) {
return endpoint return endpoint
@ -274,4 +266,14 @@ class UnifiedPushHelper @Inject constructor(
null null
} }
} }
fun getEndpointOrToken(): String? {
return if (isEmbeddedDistributor()) fcmHelper.getFcmToken()
else unifiedPushStore.getEndpoint()
}
fun getPushGateway(): String? {
return if (isEmbeddedDistributor()) stringProvider.getString(R.string.pusher_http_url)
else unifiedPushStore.getPushGateway()
}
} }

View file

@ -22,7 +22,8 @@ import im.vector.app.core.di.DefaultSharedPreferences
import javax.inject.Inject import javax.inject.Inject
class UnifiedPushStore @Inject constructor( class UnifiedPushStore @Inject constructor(
context: Context, val context: Context,
val fcmHelper: FcmHelper
) { ) {
private val defaultPrefs = DefaultSharedPreferences.getInstance(context) private val defaultPrefs = DefaultSharedPreferences.getInstance(context)
@ -31,7 +32,7 @@ class UnifiedPushStore @Inject constructor(
* *
* @return the UnifiedPush Endpoint or null if not received * @return the UnifiedPush Endpoint or null if not received
*/ */
fun getEndpointOrToken(): String? { fun getEndpoint(): String? {
return defaultPrefs.getString(PREFS_ENDPOINT_OR_TOKEN, null) return defaultPrefs.getString(PREFS_ENDPOINT_OR_TOKEN, null)
} }

View file

@ -20,20 +20,16 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.widget.Toast
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
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.network.WifiDetector
import im.vector.app.core.pushers.model.PushData import im.vector.app.core.pushers.model.PushData
import im.vector.app.core.resources.BuildMeta 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.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationActionIds import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.notifications.NotificationDrawerManager 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.VectorDataStore
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -46,30 +42,22 @@ import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.unifiedpush.android.connector.MessagingReceiver
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC) private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/** class VectorPushHandler @Inject constructor(
* Hilt injection happen at super.onReceive(). private val notificationDrawerManager: NotificationDrawerManager,
*/ private val notifiableEventResolver: NotifiableEventResolver,
@AndroidEntryPoint private val activeSessionHolder: ActiveSessionHolder,
class VectorMessagingReceiver : MessagingReceiver() { private val vectorPreferences: VectorPreferences,
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager private val vectorDataStore: VectorDataStore,
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver private val wifiDetector: WifiDetector,
@Inject lateinit var pushersManager: PushersManager private val actionIds: NotificationActionIds,
@Inject lateinit var activeSessionHolder: ActiveSessionHolder private val context: Context,
@Inject lateinit var vectorPreferences: VectorPreferences private val buildMeta: BuildMeta
@Inject lateinit var vectorDataStore: VectorDataStore ) {
@Inject lateinit var wifiDetector: WifiDetector
@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
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
@ -81,25 +69,19 @@ class VectorMessagingReceiver : MessagingReceiver() {
/** /**
* Called when message is received. * Called when message is received.
* *
* @param context the Android context * @param pushData the data received in the push.
* @param message the message
* @param instance connection, for multi-account
*/ */
override fun onMessage(context: Context, message: ByteArray, instance: String) { fun handle(pushData: PushData) {
Timber.tag(loggerTag.value).d("## onMessage() received") Timber.tag(loggerTag.value).d("## handling pushData")
val sMessage = String(message)
if (buildMeta.lowPrivacyLoggingEnabled) { if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessage() $sMessage") Timber.tag(loggerTag.value).d("## pushData: $pushData")
} }
runBlocking { runBlocking {
vectorDataStore.incrementPushCounter() vectorDataStore.incrementPushCounter()
} }
val pushData = pushParser.parseData(sMessage, unifiedPushHelper.isEmbeddedDistributor())
?: return Unit.also { Timber.tag(loggerTag.value).w("Invalid received data Json format") }
// Diagnostic Push // Diagnostic Push
if (pushData.eventId == PushersManager.TEST_EVENT_ID) { if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(actionIds.push) val intent = Intent(actionIds.push)
@ -117,51 +99,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
// we are in foreground, let the sync do the things? // we are in foreground, let the sync do the things?
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore") Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else { } else {
coroutineScope.launch(Dispatchers.IO) { onMessageReceivedInternal(pushData) } coroutineScope.launch(Dispatchers.IO) { handleInternal(pushData) }
}
}
}
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
// If the endpoint has changed
// or the gateway has changed
if (unifiedPushStore.getEndpointOrToken() != endpoint) {
unifiedPushStore.storeUpEndpoint(endpoint)
coroutineScope.launch {
unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) {
unifiedPushStore.getPushGateway()?.let {
pushersManager.enqueueRegisterPusher(endpoint, it)
}
}
}
} else {
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
}
}
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
vectorPreferences.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.stop()
}
override fun onRegistrationFailed(context: Context, instance: String) {
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.start()
}
override fun onUnregistered(context: Context, instance: String) {
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.start()
runBlocking {
try {
pushersManager.unregisterPusher(unifiedPushStore.getEndpointOrToken().orEmpty())
} catch (e: Exception) {
Timber.tag(loggerTag.value).d("Probably unregistering a non existing pusher")
} }
} }
} }
@ -171,12 +109,12 @@ class VectorMessagingReceiver : MessagingReceiver() {
* *
* @param pushData Object containing message data. * @param pushData Object containing message data.
*/ */
private suspend fun onMessageReceivedInternal(pushData: PushData) { private suspend fun handleInternal(pushData: PushData) {
try { try {
if (buildMeta.lowPrivacyLoggingEnabled) { if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData") Timber.tag(loggerTag.value).d("## handleInternal() : $pushData")
} else { } else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()") Timber.tag(loggerTag.value).d("## handleInternal()")
} }
val session = activeSessionHolder.getOrInitializeSession(startSync = false) val session = activeSessionHolder.getOrInitializeSession(startSync = false)
@ -196,7 +134,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed") Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
} }
} }

View file

@ -0,0 +1,112 @@
/*
* 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.widget.Toast
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.logger.LoggerTag
import org.unifiedpush.android.connector.MessagingReceiver
import timber.log.Timber
import javax.inject.Inject
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/**
* Hilt injection happen at super.onReceive().
*/
@AndroidEntryPoint
class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
@Inject lateinit var pushersManager: PushersManager
@Inject lateinit var pushParser: PushParser
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var vectorPushHandler: VectorPushHandler
@Inject lateinit var guardServiceStarter: GuardServiceStarter
@Inject lateinit var unifiedPushStore: UnifiedPushStore
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
private val coroutineScope = CoroutineScope(SupervisorJob())
/**
* Called when message is received.
*
* @param context the Android context
* @param message the message
* @param instance connection, for multi-account
*/
override fun onMessage(context: Context, message: ByteArray, instance: String) {
Timber.tag(loggerTag.value).d("New message")
pushParser.parsePushDataUnifiedPush(message)?.let {
vectorPushHandler.handle(it)
} ?: run {
Timber.tag(loggerTag.value).w("Invalid received data Json format")
}
}
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
// If the endpoint has changed
// or the gateway has changed
if (unifiedPushHelper.getEndpointOrToken() != endpoint) {
unifiedPushStore.storeUpEndpoint(endpoint)
coroutineScope.launch {
unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) {
unifiedPushHelper.getPushGateway()?.let {
pushersManager.enqueueRegisterPusher(endpoint, it)
}
}
}
} else {
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
}
}
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
vectorPreferences.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.stop()
}
override fun onRegistrationFailed(context: Context, instance: String) {
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.start()
}
override fun onUnregistered(context: Context, instance: String) {
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.start()
runBlocking {
try {
pushersManager.unregisterPusher(unifiedPushHelper.getEndpointOrToken().orEmpty())
} catch (e: Exception) {
Timber.tag(loggerTag.value).d("Probably unregistering a non existing pusher")
}
}
}
}

View file

@ -16,8 +16,6 @@
package im.vector.app.core.pushers.model package im.vector.app.core.pushers.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
/** /**
@ -32,11 +30,10 @@ import org.matrix.android.sdk.api.MatrixPatterns
* </pre> * </pre>
* . * .
*/ */
@JsonClass(generateAdapter = true)
data class PushDataFcm( data class PushDataFcm(
@Json(name = "event_id") val eventId: String?, val eventId: String?,
@Json(name = "room_id") val roomId: String?, val roomId: String?,
@Json(name = "unread") var unread: Int?, var unread: Int?,
) )
fun PushDataFcm.toPushData() = PushData( fun PushDataFcm.toPushData() = PushData(

View file

@ -128,7 +128,7 @@ class HomeActivity :
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var pushManager: PushersManager @Inject lateinit var pushersManager: PushersManager
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var popupAlertManager: PopupAlertManager
@ -208,7 +208,7 @@ class HomeActivity :
if (unifiedPushHelper.isEmbeddedDistributor()) { if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved( fcmHelper.ensureFcmTokenIsRetrieved(
this, this,
pushManager, pushersManager,
vectorPreferences.areNotificationEnabledForDevice() vectorPreferences.areNotificationEnabledForDevice()
) )
} }

View file

@ -38,6 +38,7 @@ import im.vector.app.core.preference.VectorEditTextPreference
import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.preference.VectorSwitchPreference
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.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
@ -70,6 +71,7 @@ class VectorSettingsNotificationPreferenceFragment :
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@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 guardServiceStarter: GuardServiceStarter @Inject lateinit var guardServiceStarter: GuardServiceStarter
@ -106,6 +108,13 @@ class VectorSettingsNotificationPreferenceFragment :
if (isChecked) { if (isChecked) {
unifiedPushHelper.register(requireActivity()) { unifiedPushHelper.register(requireActivity()) {
// Update the summary // Update the summary
if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(
requireActivity(),
pushersManager,
vectorPreferences.areNotificationEnabledForDevice()
)
}
findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY) findPreference<VectorPreference>(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
?.summary = unifiedPushHelper.getCurrentDistributorName() ?.summary = unifiedPushHelper.getCurrentDistributorName()
} }
@ -158,7 +167,14 @@ class VectorSettingsNotificationPreferenceFragment :
if (vectorFeatures.allowExternalUnifiedPushDistributors()) { if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
it.summary = unifiedPushHelper.getCurrentDistributorName() it.summary = unifiedPushHelper.getCurrentDistributorName()
it.onPreferenceClickListener = Preference.OnPreferenceClickListener { it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
unifiedPushHelper.openDistributorDialog(requireActivity(), pushersManager) { unifiedPushHelper.forceRegister(requireActivity(), pushersManager) {
if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(
requireActivity(),
pushersManager,
vectorPreferences.areNotificationEnabledForDevice()
)
}
it.summary = unifiedPushHelper.getCurrentDistributorName() it.summary = unifiedPushHelper.getCurrentDistributorName()
session.pushersService().refreshPushers() session.pushersService().refreshPushers()
refreshBackgroundSyncPrefs() refreshBackgroundSyncPrefs()

View file

@ -26,7 +26,6 @@ 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.PushersManager import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.pushers.UnifiedPushStore
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.api.session.pushers.PusherState
import javax.inject.Inject import javax.inject.Inject
@ -37,12 +36,11 @@ class TestEndpointAsTokenRegistration @Inject constructor(
private val pushersManager: PushersManager, private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val unifiedPushHelper: UnifiedPushHelper, private val unifiedPushHelper: UnifiedPushHelper,
private val unifiedPushStore: UnifiedPushStore,
) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) { ) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
// Check if we have a registered pusher for this token // Check if we have a registered pusher for this token
val endpoint = unifiedPushStore.getEndpointOrToken() ?: run { val endpoint = unifiedPushHelper.getEndpointOrToken() ?: run {
status = TestStatus.FAILED status = TestStatus.FAILED
return return
} }
@ -60,7 +58,7 @@ class TestEndpointAsTokenRegistration @Inject constructor(
) )
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) {
override fun doFix() { override fun doFix() {
unifiedPushHelper.reRegister( unifiedPushHelper.forceRegister(
context, context,
pushersManager pushersManager
) )

View file

@ -19,19 +19,19 @@ package im.vector.app.features.settings.troubleshoot
import android.content.Intent import android.content.Intent
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.pushers.UnifiedPushStore import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import javax.inject.Inject import javax.inject.Inject
class TestUnifiedPushGateway @Inject constructor( class TestUnifiedPushGateway @Inject constructor(
private val unifiedPushStore: UnifiedPushStore, private val unifiedPushHelper: UnifiedPushHelper,
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_gateway_title) { ) : TroubleshootTest(R.string.settings_troubleshoot_test_current_gateway_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
description = stringProvider.getString( description = stringProvider.getString(
R.string.settings_troubleshoot_test_current_gateway, R.string.settings_troubleshoot_test_current_gateway,
unifiedPushStore.getPushGateway() unifiedPushHelper.getPushGateway()
) )
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
} }

View file

@ -35,73 +35,89 @@ class PushParserTest {
) )
@Test @Test
fun `test edge cases`() { fun `test edge cases Firebase`() {
doAllEdgeTests(true) val pushParser = PushParser()
doAllEdgeTests(false) // Empty Json
pushParser.parsePushDataFcm(emptyMap()) shouldBeEqualTo emptyData
// Bad Json
pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("unread", "str")) shouldBeEqualTo validData.copy(unread = null)
// Extra data
pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("extra", "5")) shouldBeEqualTo validData
} }
private fun doAllEdgeTests(firebaseFormat: Boolean) { @Test
fun `test edge cases UnifiedPush`() {
val pushParser = PushParser() val pushParser = PushParser()
// Empty string // Empty string
pushParser.parseData("", firebaseFormat) shouldBe null pushParser.parsePushDataUnifiedPush("".toByteArray()) shouldBe null
// Empty Json // Empty Json
pushParser.parseData("{}", firebaseFormat) shouldBeEqualTo emptyData pushParser.parsePushDataUnifiedPush("{}".toByteArray()) shouldBeEqualTo emptyData
// Bad Json // Bad Json
pushParser.parseData("ABC", firebaseFormat) shouldBe null pushParser.parsePushDataUnifiedPush("ABC".toByteArray()) shouldBe null
} }
@Test @Test
fun `test unified push format`() { fun `test UnifiedPush format`() {
val pushParser = PushParser() val pushParser = PushParser()
pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.toByteArray()) shouldBeEqualTo validData
pushParser.parseData(UNIFIED_PUSH_DATA, false) shouldBeEqualTo validData
pushParser.parseData(UNIFIED_PUSH_DATA, true) shouldBeEqualTo emptyData
} }
@Test @Test
fun `test firebase push format`() { fun `test Firebase format`() {
val pushParser = PushParser() val pushParser = PushParser()
pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA) shouldBeEqualTo validData
pushParser.parseData(FIREBASE_PUSH_DATA, true) shouldBeEqualTo validData
pushParser.parseData(FIREBASE_PUSH_DATA, false) shouldBeEqualTo emptyData
} }
@Test @Test
fun `test empty roomId`() { fun `test empty roomId`() {
val pushParser = PushParser() val pushParser = PushParser()
val expected = validData.copy(roomId = null)
pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", ""), true) shouldBeEqualTo validData.copy(roomId = null) pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", null)) shouldBeEqualTo expected
pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", ""), false) shouldBeEqualTo validData.copy(roomId = null) pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", "")) shouldBeEqualTo expected
pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "").toByteArray()) shouldBeEqualTo expected
} }
@Test @Test
fun `test invalid roomId`() { fun `test invalid roomId`() {
val pushParser = PushParser() val pushParser = PushParser()
val expected = validData.copy(roomId = null)
pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), true) shouldBeEqualTo validData.copy(roomId = null) pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", "aRoomId:domain")) shouldBeEqualTo expected
pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), false) shouldBeEqualTo validData.copy(roomId = null) pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("!aRoomId:domain", "aRoomId:domain")) shouldBeEqualTo expected
} }
@Test @Test
fun `test empty eventId`() { fun `test empty eventId`() {
val pushParser = PushParser() val pushParser = PushParser()
val expected = validData.copy(eventId = null)
pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", ""), true) shouldBeEqualTo validData.copy(eventId = null) pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", null)) shouldBeEqualTo expected
pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", ""), false) shouldBeEqualTo validData.copy(eventId = null) pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", "")) shouldBeEqualTo expected
pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("\$anEventId", "")) shouldBeEqualTo expected
} }
@Test @Test
fun `test invalid eventId`() { fun `test invalid eventId`() {
val pushParser = PushParser() val pushParser = PushParser()
val expected = validData.copy(eventId = null)
pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", "anEventId"), true) shouldBeEqualTo validData.copy(eventId = null) pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", "anEventId")) shouldBeEqualTo expected
pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", "anEventId"), false) shouldBeEqualTo validData.copy(eventId = null) pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("\$anEventId", "anEventId")) shouldBeEqualTo expected
} }
companion object { companion object {
private const val UNIFIED_PUSH_DATA = private const val UNIFIED_PUSH_DATA =
"{\"notification\":{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}" "{\"notification\":{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}"
private const val FIREBASE_PUSH_DATA = private val FIREBASE_PUSH_DATA = mapOf(
"{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"unread\":\"1\",\"prio\":\"high\"}" "event_id" to "\$anEventId",
"room_id" to "!aRoomId:domain",
"unread" to "1",
"prio" to "high",
)
} }
} }
private fun Map<String, String?>.mutate(key: String, value: String?): Map<String, String?> {
return toMutableMap().apply { put(key, value) }
}
private fun String.mutate(oldValue: String, newValue: String): ByteArray {
return replace(oldValue, newValue).toByteArray()
}