diff --git a/changelog.d/6936.misc b/changelog.d/6936.misc
new file mode 100644
index 0000000000..f032ad9805
--- /dev/null
+++ b/changelog.d/6936.misc
@@ -0,0 +1 @@
+Smaff refactor of UnifiedPushHelper
diff --git a/changelog.d/7068.bugfix b/changelog.d/7068.bugfix
new file mode 100644
index 0000000000..448dcdd46c
--- /dev/null
+++ b/changelog.d/7068.bugfix
@@ -0,0 +1 @@
+Fix push with FCM
diff --git a/vector-app/src/gplay/AndroidManifest.xml b/vector-app/src/gplay/AndroidManifest.xml
index a5f0eae6be..0ac14f9cd3 100755
--- a/vector-app/src/gplay/AndroidManifest.xml
+++ b/vector-app/src/gplay/AndroidManifest.xml
@@ -1,5 +1,6 @@
-
+
@@ -8,18 +9,14 @@
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
-
-
+
+
-
-
+
-
-
-
+
diff --git a/vector/build.gradle b/vector/build.gradle
index 65adc7089c..a5538053fc 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -257,7 +257,7 @@ dependencies {
// UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.0.1'
// 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-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml
index 29dac6533e..15db89ca13 100644
--- a/vector/src/fdroid/AndroidManifest.xml
+++ b/vector/src/fdroid/AndroidManifest.xml
@@ -28,20 +28,6 @@
android:enabled="true"
android:exported="false" />
-
-
-
-
-
-
-
@@ -424,6 +424,20 @@
+
+
+
+
+
+
+
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/KeepInternalDistributor.kt b/vector/src/main/java/im/vector/app/core/pushers/KeepInternalDistributor.kt
similarity index 96%
rename from vector/src/fdroid/java/im/vector/app/fdroid/receiver/KeepInternalDistributor.kt
rename to vector/src/main/java/im/vector/app/core/pushers/KeepInternalDistributor.kt
index 3feee8c63b..63725f01a3 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/KeepInternalDistributor.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/KeepInternalDistributor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package im.vector.app.fdroid.receiver
+package im.vector.app.core.pushers
import android.content.BroadcastReceiver
import android.content.Context
diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt b/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt
index 6f141e3736..8c18295c45 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt
@@ -24,28 +24,34 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.util.MatrixJsonParser
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() {
- /**
- * 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)
- */
- 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 parsePushDataUnifiedPush(message: ByteArray): PushData? {
+ return MatrixJsonParser.getMoshi().let {
+ tryOrNull { it.adapter(PushDataUnifiedPush::class.java).fromJson(String(message)) }?.toPushData()
}
}
+
+ fun parsePushDataFcm(message: Map): PushData {
+ val pushDataFcm = PushDataFcm(
+ eventId = message["event_id"],
+ roomId = message["room_id"],
+ unread = message["unread"]?.let { tryOrNull { Integer.parseInt(it) } },
+ )
+ return pushDataFcm.toPushData()
+ }
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
index 91ab58207d..c77f454ab0 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
@@ -29,7 +29,7 @@ import kotlin.math.abs
private const val DEFAULT_PUSHER_FILE_TAG = "mobile"
class PushersManager @Inject constructor(
- private val unifiedPushStore: UnifiedPushStore,
+ private val unifiedPushHelper: UnifiedPushHelper,
private val activeSessionHolder: ActiveSessionHolder,
private val localeProvider: LocaleProvider,
private val stringProvider: StringProvider,
@@ -39,9 +39,9 @@ class PushersManager @Inject constructor(
val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().testPush(
- unifiedPushStore.getPushGateway()!!,
+ unifiedPushHelper.getPushGateway() ?: return,
stringProvider.getString(R.string.pusher_app_id),
- unifiedPushStore.getEndpointOrToken().orEmpty(),
+ unifiedPushHelper.getEndpointOrToken().orEmpty(),
TEST_EVENT_ID
)
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
index 0993485471..aab94eca93 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
@@ -46,6 +46,9 @@ class UnifiedPushHelper @Inject constructor(
private val vectorFeatures: VectorFeatures,
private val fcmHelper: FcmHelper,
) {
+
+ // Called when the home activity starts
+ // or when notifications are enabled
fun register(
activity: FragmentActivity,
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,
pushersManager: PushersManager,
onDoneRunnable: Runnable? = null
@@ -86,7 +96,8 @@ class UnifiedPushHelper @Inject constructor(
// Un-register first
unregister(pushersManager)
}
- if (UnifiedPush.getDistributor(context).isNotEmpty()) {
+ // the !force should not be needed
+ if (!force && UnifiedPush.getDistributor(context).isNotEmpty()) {
UnifiedPush.registerApp(context)
onDoneRunnable?.run()
return@launch
@@ -94,45 +105,26 @@ class UnifiedPushHelper @Inject constructor(
val distributors = UnifiedPush.getDistributors(context)
- if (distributors.size == 1 && !force) {
+ if (!force && distributors.size == 1) {
UnifiedPush.saveDistributor(context, distributors.first())
UnifiedPush.registerApp(context)
onDoneRunnable?.run()
} else {
openDistributorDialogInternal(
activity = activity,
- pushersManager = pushersManager,
onDoneRunnable = onDoneRunnable,
- distributors = distributors,
- unregisterFirst = force,
- cancellable = !force
+ distributors = distributors
)
}
}
}
- fun openDistributorDialog(
- activity: FragmentActivity,
- pushersManager: PushersManager,
- onDoneRunnable: Runnable,
- ) {
- val distributors = UnifiedPush.getDistributors(activity)
- openDistributorDialogInternal(
- activity,
- pushersManager,
- onDoneRunnable, distributors,
- unregisterFirst = true,
- cancellable = true,
- )
- }
-
+ // There is no case where this function is called
+ // with a saved distributor and/or a pusher
private fun openDistributorDialogInternal(
activity: FragmentActivity,
- pushersManager: PushersManager?,
onDoneRunnable: Runnable?,
- distributors: List,
- unregisterFirst: Boolean,
- cancellable: Boolean,
+ distributors: List
) {
val internalDistributorName = stringProvider.getString(
if (fcmHelper.isFirebaseAvailable()) {
@@ -154,16 +146,8 @@ class UnifiedPushHelper @Inject constructor(
.setTitle(stringProvider.getString(R.string.unifiedpush_getdistributors_dialog_title))
.setItems(distributorsName.toTypedArray()) { _, which ->
val distributor = distributors[which]
- if (distributor == UnifiedPush.getDistributor(context)) {
- Timber.d("Same distributor selected again, no action")
- return@setItems
- }
activity.lifecycleScope.launch {
- if (unregisterFirst) {
- // Un-register first
- unregister(pushersManager)
- }
UnifiedPush.saveDistributor(context, distributor)
Timber.i("Saving distributor: $distributor")
UnifiedPush.registerApp(context)
@@ -176,7 +160,7 @@ class UnifiedPushHelper @Inject constructor(
UnifiedPush.registerApp(context)
onDoneRunnable?.run()
}
- .setCancelable(cancellable)
+ .setCancelable(true)
.show()
}
@@ -184,7 +168,10 @@ class UnifiedPushHelper @Inject constructor(
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
vectorPreferences.setFdroidSyncBackgroundMode(mode)
try {
- pushersManager?.unregisterPusher(unifiedPushStore.getEndpointOrToken().orEmpty())
+ getEndpointOrToken()?.let {
+ Timber.d("Removing $it")
+ pushersManager?.unregisterPusher(it)
+ }
} catch (e: Exception) {
Timber.d(e, "Probably unregistering a non existing pusher")
}
@@ -253,15 +240,20 @@ class UnifiedPushHelper @Inject constructor(
}
fun isEmbeddedDistributor(): Boolean {
- return UnifiedPush.getDistributor(context) == context.packageName && fcmHelper.isFirebaseAvailable()
+ return isInternalDistributor() && fcmHelper.isFirebaseAvailable()
}
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? {
- val endpoint = unifiedPushStore.getEndpointOrToken()
+ val endpoint = getEndpointOrToken()
if (endpoint.isNullOrEmpty()) return null
if (isEmbeddedDistributor()) {
return endpoint
@@ -274,4 +266,14 @@ class UnifiedPushHelper @Inject constructor(
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()
+ }
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt
index 07d291a723..d9c6bf3159 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushStore.kt
@@ -22,7 +22,8 @@ import im.vector.app.core.di.DefaultSharedPreferences
import javax.inject.Inject
class UnifiedPushStore @Inject constructor(
- context: Context,
+ val context: Context,
+ val fcmHelper: FcmHelper
) {
private val defaultPrefs = DefaultSharedPreferences.getInstance(context)
@@ -31,7 +32,7 @@ class UnifiedPushStore @Inject constructor(
*
* @return the UnifiedPush Endpoint or null if not received
*/
- fun getEndpointOrToken(): String? {
+ fun getEndpoint(): String? {
return defaultPrefs.getString(PREFS_ENDPOINT_OR_TOKEN, null)
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorPushHandler.kt
similarity index 58%
rename from vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
rename to vector/src/main/java/im/vector/app/core/pushers/VectorPushHandler.kt
index 8e88e44627..b74028d579 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/VectorPushHandler.kt
@@ -20,20 +20,16 @@ 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
@@ -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.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
-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 VectorMessagingReceiver : MessagingReceiver() {
- @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
- @Inject lateinit var notifiableEventResolver: NotifiableEventResolver
- @Inject lateinit var pushersManager: PushersManager
- @Inject lateinit var activeSessionHolder: ActiveSessionHolder
- @Inject lateinit var vectorPreferences: VectorPreferences
- @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
+class VectorPushHandler @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 actionIds: NotificationActionIds,
+ private val context: Context,
+ private val buildMeta: BuildMeta
+) {
private val coroutineScope = CoroutineScope(SupervisorJob())
@@ -81,25 +69,19 @@ class VectorMessagingReceiver : MessagingReceiver() {
/**
* Called when message is received.
*
- * @param context the Android context
- * @param message the message
- * @param instance connection, for multi-account
+ * @param pushData the data received in the push.
*/
- override fun onMessage(context: Context, message: ByteArray, instance: String) {
- Timber.tag(loggerTag.value).d("## onMessage() received")
+ fun handle(pushData: PushData) {
+ Timber.tag(loggerTag.value).d("## handling pushData")
- val sMessage = String(message)
if (buildMeta.lowPrivacyLoggingEnabled) {
- Timber.tag(loggerTag.value).d("## onMessage() $sMessage")
+ Timber.tag(loggerTag.value).d("## pushData: $pushData")
}
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)
@@ -117,51 +99,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
// 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) {
- 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")
+ coroutineScope.launch(Dispatchers.IO) { handleInternal(pushData) }
}
}
}
@@ -171,12 +109,12 @@ class VectorMessagingReceiver : MessagingReceiver() {
*
* @param pushData Object containing message data.
*/
- private suspend fun onMessageReceivedInternal(pushData: PushData) {
+ private suspend fun handleInternal(pushData: PushData) {
try {
if (buildMeta.lowPrivacyLoggingEnabled) {
- Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
+ Timber.tag(loggerTag.value).d("## handleInternal() : $pushData")
} else {
- Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
+ Timber.tag(loggerTag.value).d("## handleInternal()")
}
val session = activeSessionHolder.getOrInitializeSession(startSync = false)
@@ -196,7 +134,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
}
}
} catch (e: Exception) {
- Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed")
+ Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
}
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt
new file mode 100644
index 0000000000..838cbd5935
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/pushers/VectorUnifiedPushMessagingReceiver.kt
@@ -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")
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt
index 1b9c37ae0a..e78cdf5ff8 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt
@@ -16,8 +16,6 @@
package im.vector.app.core.pushers.model
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.MatrixPatterns
/**
@@ -32,11 +30,10 @@ import org.matrix.android.sdk.api.MatrixPatterns
*
* .
*/
-@JsonClass(generateAdapter = true)
data class PushDataFcm(
- @Json(name = "event_id") val eventId: String?,
- @Json(name = "room_id") val roomId: String?,
- @Json(name = "unread") var unread: Int?,
+ val eventId: String?,
+ val roomId: String?,
+ var unread: Int?,
)
fun PushDataFcm.toPushData() = PushData(
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 1d4cbd8427..46b7efbb97 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -128,7 +128,7 @@ class HomeActivity :
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
- @Inject lateinit var pushManager: PushersManager
+ @Inject lateinit var pushersManager: PushersManager
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var popupAlertManager: PopupAlertManager
@@ -208,7 +208,7 @@ class HomeActivity :
if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(
this,
- pushManager,
+ pushersManager,
vectorPreferences.areNotificationEnabledForDevice()
)
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
index a09bb1e6a4..37d09d02c9 100644
--- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
@@ -38,6 +38,7 @@ import im.vector.app.core.preference.VectorEditTextPreference
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory
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.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter
@@ -70,6 +71,7 @@ class VectorSettingsNotificationPreferenceFragment :
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@Inject lateinit var pushersManager: PushersManager
+ @Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var guardServiceStarter: GuardServiceStarter
@@ -106,6 +108,13 @@ class VectorSettingsNotificationPreferenceFragment :
if (isChecked) {
unifiedPushHelper.register(requireActivity()) {
// Update the summary
+ if (unifiedPushHelper.isEmbeddedDistributor()) {
+ fcmHelper.ensureFcmTokenIsRetrieved(
+ requireActivity(),
+ pushersManager,
+ vectorPreferences.areNotificationEnabledForDevice()
+ )
+ }
findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
?.summary = unifiedPushHelper.getCurrentDistributorName()
}
@@ -158,7 +167,14 @@ class VectorSettingsNotificationPreferenceFragment :
if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
it.summary = unifiedPushHelper.getCurrentDistributorName()
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()
session.pushersService().refreshPushers()
refreshBackgroundSyncPrefs()
diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt
index 66222f759e..78eb372fef 100644
--- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt
@@ -26,7 +26,6 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
-import im.vector.app.core.pushers.UnifiedPushStore
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.pushers.PusherState
import javax.inject.Inject
@@ -37,12 +36,11 @@ class TestEndpointAsTokenRegistration @Inject constructor(
private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder,
private val unifiedPushHelper: UnifiedPushHelper,
- private val unifiedPushStore: UnifiedPushStore,
) : TroubleshootTest(R.string.settings_troubleshoot_test_endpoint_registration_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher) {
// Check if we have a registered pusher for this token
- val endpoint = unifiedPushStore.getEndpointOrToken() ?: run {
+ val endpoint = unifiedPushHelper.getEndpointOrToken() ?: run {
status = TestStatus.FAILED
return
}
@@ -60,7 +58,7 @@ class TestEndpointAsTokenRegistration @Inject constructor(
)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) {
override fun doFix() {
- unifiedPushHelper.reRegister(
+ unifiedPushHelper.forceRegister(
context,
pushersManager
)
diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushGateway.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushGateway.kt
index 38f14951b4..19a4fd188f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushGateway.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushGateway.kt
@@ -19,19 +19,19 @@ package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
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 javax.inject.Inject
class TestUnifiedPushGateway @Inject constructor(
- private val unifiedPushStore: UnifiedPushStore,
+ private val unifiedPushHelper: UnifiedPushHelper,
private val stringProvider: StringProvider
) : TroubleshootTest(R.string.settings_troubleshoot_test_current_gateway_title) {
override fun perform(activityResultLauncher: ActivityResultLauncher) {
description = stringProvider.getString(
R.string.settings_troubleshoot_test_current_gateway,
- unifiedPushStore.getPushGateway()
+ unifiedPushHelper.getPushGateway()
)
status = TestStatus.SUCCESS
}
diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt
index 03577a4400..c0f6c33da8 100644
--- a/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt
+++ b/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt
@@ -35,73 +35,89 @@ class PushParserTest {
)
@Test
- fun `test edge cases`() {
- doAllEdgeTests(true)
- doAllEdgeTests(false)
+ fun `test edge cases Firebase`() {
+ val pushParser = PushParser()
+ // 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()
// Empty string
- pushParser.parseData("", firebaseFormat) shouldBe null
+ pushParser.parsePushDataUnifiedPush("".toByteArray()) shouldBe null
// Empty Json
- pushParser.parseData("{}", firebaseFormat) shouldBeEqualTo emptyData
+ pushParser.parsePushDataUnifiedPush("{}".toByteArray()) shouldBeEqualTo emptyData
// Bad Json
- pushParser.parseData("ABC", firebaseFormat) shouldBe null
+ pushParser.parsePushDataUnifiedPush("ABC".toByteArray()) shouldBe null
}
@Test
- fun `test unified push format`() {
+ fun `test UnifiedPush format`() {
val pushParser = PushParser()
-
- pushParser.parseData(UNIFIED_PUSH_DATA, false) shouldBeEqualTo validData
- pushParser.parseData(UNIFIED_PUSH_DATA, true) shouldBeEqualTo emptyData
+ pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.toByteArray()) shouldBeEqualTo validData
}
@Test
- fun `test firebase push format`() {
+ fun `test Firebase format`() {
val pushParser = PushParser()
-
- pushParser.parseData(FIREBASE_PUSH_DATA, true) shouldBeEqualTo validData
- pushParser.parseData(FIREBASE_PUSH_DATA, false) shouldBeEqualTo emptyData
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA) shouldBeEqualTo validData
}
@Test
fun `test empty roomId`() {
val pushParser = PushParser()
-
- pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", ""), true) shouldBeEqualTo validData.copy(roomId = null)
- pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", ""), false) shouldBeEqualTo validData.copy(roomId = null)
+ val expected = validData.copy(roomId = null)
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", null)) shouldBeEqualTo expected
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", "")) shouldBeEqualTo expected
+ pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "").toByteArray()) shouldBeEqualTo expected
}
@Test
fun `test invalid roomId`() {
val pushParser = PushParser()
-
- pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), true) shouldBeEqualTo validData.copy(roomId = null)
- pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), false) shouldBeEqualTo validData.copy(roomId = null)
+ val expected = validData.copy(roomId = null)
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", "aRoomId:domain")) shouldBeEqualTo expected
+ pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("!aRoomId:domain", "aRoomId:domain")) shouldBeEqualTo expected
}
@Test
fun `test empty eventId`() {
val pushParser = PushParser()
-
- pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", ""), true) shouldBeEqualTo validData.copy(eventId = null)
- pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", ""), false) shouldBeEqualTo validData.copy(eventId = null)
+ val expected = validData.copy(eventId = null)
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", null)) shouldBeEqualTo expected
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", "")) shouldBeEqualTo expected
+ pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("\$anEventId", "")) shouldBeEqualTo expected
}
@Test
fun `test invalid eventId`() {
val pushParser = PushParser()
-
- pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", "anEventId"), true) shouldBeEqualTo validData.copy(eventId = null)
- pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", "anEventId"), false) shouldBeEqualTo validData.copy(eventId = null)
+ val expected = validData.copy(eventId = null)
+ pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", "anEventId")) shouldBeEqualTo expected
+ pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("\$anEventId", "anEventId")) shouldBeEqualTo expected
}
companion object {
private const val UNIFIED_PUSH_DATA =
"{\"notification\":{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}"
- private const val FIREBASE_PUSH_DATA =
- "{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"unread\":\"1\",\"prio\":\"high\"}"
+ private val FIREBASE_PUSH_DATA = mapOf(
+ "event_id" to "\$anEventId",
+ "room_id" to "!aRoomId:domain",
+ "unread" to "1",
+ "prio" to "high",
+ )
}
}
+
+private fun Map.mutate(key: String, value: String?): Map {
+ return toMutableMap().apply { put(key, value) }
+}
+
+private fun String.mutate(oldValue: String, newValue: String): ByteArray {
+ return replace(oldValue, newValue).toByteArray()
+}