diff --git a/changelog.d/3448.feature b/changelog.d/3448.feature
new file mode 100644
index 0000000000..3f83f1bef5
--- /dev/null
+++ b/changelog.d/3448.feature
@@ -0,0 +1 @@
+Use UnifiedPush and allows user to have push without FCM.
diff --git a/dependencies.gradle b/dependencies.gradle
index 451ad4449b..0b29996438 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -43,6 +43,7 @@ ext.libs = [
],
jetbrains : [
+ 'kotlinReflect' : "org.jetbrains.kotlin:kotlin-reflect:$kotlin",
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
@@ -88,6 +89,7 @@ ext.libs = [
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",
+ 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi",
'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi",
'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit",
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"
diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle
index 59cefe7e89..842a235b16 100644
--- a/dependencies_groups.gradle
+++ b/dependencies_groups.gradle
@@ -12,6 +12,7 @@ ext.groups = [
'com.github.vector-im',
'com.github.yalantis',
'com.github.Zhuinden',
+ 'com.github.UnifiedPush',
]
],
jitsi : [
diff --git a/vector-config/src/main/res/values/config.xml b/vector-config/src/main/res/values/config.xml
index 78b92cbfa4..cae094f454 100755
--- a/vector-config/src/main/res/values/config.xml
+++ b/vector-config/src/main/res/values/config.xml
@@ -17,7 +17,11 @@
-->
+
https://matrix.org/_matrix/push/v1/notify
+
+
+ https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify
im.vector.app.android
diff --git a/vector/build.gradle b/vector/build.gradle
index 751d15122b..238a9f05d0 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -282,6 +282,7 @@ android {
buildConfigField "boolean", "ALLOW_FCM_USE", "true"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
+ buildConfigField "boolean", "ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB", "true"
}
fdroid {
@@ -293,6 +294,7 @@ android {
buildConfigField "boolean", "ALLOW_FCM_USE", "false"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
+ buildConfigField "boolean", "ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB", "true"
}
}
@@ -348,6 +350,7 @@ dependencies {
implementation project(":library:multipicker")
implementation 'androidx.multidex:multidex:2.0.1'
+ implementation libs.jetbrains.kotlinReflect
implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid
@@ -364,6 +367,7 @@ dependencies {
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0"
implementation libs.squareup.moshi
+ implementation libs.squareup.moshiKt
kapt libs.squareup.moshiKotlin
// Lifecycle
@@ -462,8 +466,10 @@ dependencies {
// Analytics
implementation 'com.posthog.android:posthog:1.1.2'
- // gplay flavor only
- gplayImplementation('com.google.firebase:firebase-messaging:23.0.0') {
+ // UnifiedPush
+ implementation 'com.github.UnifiedPush:android-connector:2.0.0-beta2'
+ // UnifiedPush gplay flavor only
+ gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:a0056aa939') {
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/gplay/AndroidManifest.xml b/vector/src/gplay/AndroidManifest.xml
index f541eebd83..5b384d1a0a 100755
--- a/vector/src/gplay/AndroidManifest.xml
+++ b/vector/src/gplay/AndroidManifest.xml
@@ -9,13 +9,17 @@
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
-
+
-
+
+
-
+
+
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
index b4b8a936d0..3fd80ad1c5 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
@@ -50,12 +50,12 @@ class TestPushFromPushGateway @Inject constructor(
override fun perform(activityResultLauncher: ActivityResultLauncher) {
pushReceived = false
- val fcmToken = FcmHelper.getFcmToken(context) ?: run {
+ FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
return
}
action = activeSessionHolder.getActiveSession().coroutineScope.launch {
- val result = runCatching { pushersManager.testPush(fcmToken) }
+ val result = runCatching { pushersManager.testPush(context) }
withContext(Dispatchers.Main) {
status = result
diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/EmbeddedFCMDistributor.kt b/vector/src/gplay/java/im/vector/app/push/fcm/EmbeddedFCMDistributor.kt
new file mode 100644
index 0000000000..0d0e066eb4
--- /dev/null
+++ b/vector/src/gplay/java/im/vector/app/push/fcm/EmbeddedFCMDistributor.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+ }
+}
diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
index ac2d063700..ef333bb30b 100755
--- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
+++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
@@ -98,9 +98,9 @@ object FcmHelper {
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
- private fun checkPlayServices(activity: Activity): Boolean {
+ fun checkPlayServices(context: Context): Boolean {
val apiAvailability = GoogleApiAvailability.getInstance()
- val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
+ val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
return resultCode == ConnectionResult.SUCCESS
}
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 89c600b052..87fd307647 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -410,6 +410,17 @@
+
+
+
+
+
+
+
+
+
+
+
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 c89dc7a73c..a5e03c7d14 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
@@ -16,6 +16,7 @@
package im.vector.app.core.pushers
+import android.content.Context
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.AppNameProvider
@@ -34,13 +35,13 @@ class PushersManager @Inject constructor(
private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider
) {
- suspend fun testPush(pushKey: String) {
+ suspend fun testPush(context: Context) {
val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().testPush(
- stringProvider.getString(R.string.pusher_http_url),
+ UnifiedPushHelper.getPushGateway(context),
stringProvider.getString(R.string.pusher_app_id),
- pushKey,
+ UnifiedPushHelper.getEndpointOrToken(context) ?: "",
TEST_EVENT_ID
)
}
@@ -50,19 +51,38 @@ class PushersManager @Inject constructor(
return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey))
}
+ fun enqueueRegisterPusher(
+ pushKey: String,
+ gateway: String
+ ): UUID {
+ val currentSession = activeSessionHolder.getActiveSession()
+ return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey, gateway))
+ }
+
suspend fun registerPusherWithFcmKey(pushKey: String) {
val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey))
}
- private fun createHttpPusher(pushKey: String) = HttpPusher(
+ suspend fun registerPusher(
+ pushKey: String,
+ gateway: String
+ ) {
+ val currentSession = activeSessionHolder.getActiveSession()
+ currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey, gateway))
+ }
+
+ private fun createHttpPusher(
+ pushKey: String,
+ gateway: String = stringProvider.getString(R.string.pusher_http_url)
+ ) = HttpPusher(
pushKey,
stringProvider.getString(R.string.pusher_app_id),
profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()),
localeProvider.current().language,
appNameProvider.getAppName(),
activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE",
- stringProvider.getString(R.string.pusher_http_url),
+ gateway,
append = false,
withEventIdOnly = true
)
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
new file mode 100644
index 0000000000..62b174b0f5
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.pm.PackageManager
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.edit
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import im.vector.app.BuildConfig
+import im.vector.app.R
+import im.vector.app.core.di.DefaultSharedPreferences
+import im.vector.app.push.fcm.FcmHelper
+import org.unifiedpush.android.connector.UnifiedPush
+import timber.log.Timber
+import java.net.URI
+import java.net.URL
+
+object UnifiedPushHelper {
+ private const val PREFS_ENDPOINT_OR_TOKEN = "UP_ENDPOINT_OR_TOKEN"
+ private const val PREFS_PUSH_GATEWAY = "PUSH_GATEWAY"
+ private val up = UnifiedPush
+
+ /**
+ * Retrieves the UnifiedPush Endpoint.
+ *
+ * @return the UnifiedPush Endpoint or null if not received
+ */
+ fun getEndpointOrToken(context: Context): String? {
+ return DefaultSharedPreferences.getInstance(context).getString(PREFS_ENDPOINT_OR_TOKEN, null)
+ }
+
+ /**
+ * Store UnifiedPush Endpoint to the SharedPrefs
+ * TODO Store in realm
+ *
+ * @param context android context
+ * @param endpoint the endpoint to store
+ */
+ fun storeUpEndpoint(context: Context,
+ endpoint: String?) {
+ DefaultSharedPreferences.getInstance(context).edit {
+ putString(PREFS_ENDPOINT_OR_TOKEN, endpoint)
+ }
+ }
+
+ /**
+ * Retrieves the Push Gateway.
+ *
+ * @return the Push Gateway or null if not defined
+ */
+ fun getPushGateway(context: Context): String {
+ return DefaultSharedPreferences.getInstance(context).getString(PREFS_PUSH_GATEWAY, null)!!
+ }
+
+ /**
+ * Store Push Gateway to the SharedPrefs
+ * TODO Store in realm
+ *
+ * @param context android context
+ * @param gateway the push gateway to store
+ */
+ fun storePushGateway(context: Context,
+ gateway: String?) {
+ DefaultSharedPreferences.getInstance(context).edit {
+ putString(PREFS_PUSH_GATEWAY, gateway)
+ }
+ }
+
+ fun register(context: Context, force: Boolean = false, onDoneRunnable: Runnable? = null) {
+ if (!BuildConfig.ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB) {
+ up.saveDistributor(context, context.packageName)
+ up.registerApp(context)
+ onDoneRunnable?.run()
+ return
+ }
+ if (force) {
+ // Un-register first
+ up.unregisterApp(context)
+ storeUpEndpoint(context, null)
+ storePushGateway(context, null)
+ } else if (up.getDistributor(context).isNotEmpty()) {
+ up.registerApp(context)
+ onDoneRunnable?.run()
+ return
+ }
+ val distributors = up.getDistributors(context).toMutableList()
+
+ val internalDistributorName = if (!FcmHelper.isPushSupported()) {
+ // Adding packageName for background sync
+ distributors.add(context.packageName)
+ context.getString(R.string.unifiedpush_getdistributors_dialog_background_sync)
+ } else {
+ context.getString(R.string.unifiedpush_getdistributors_dialog_fcm_fallback)
+ }
+
+ if (distributors.size == 1
+ && !force){
+ up.saveDistributor(context, distributors.first())
+ up.registerApp(context)
+ onDoneRunnable?.run()
+ } else {
+ val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(context)
+ builder.setTitle(context.getString(R.string.unifiedpush_getdistributors_dialog_title))
+
+ val distributorsArray = distributors.toTypedArray()
+ val distributorsNameArray = distributorsArray.map {
+ if (it == context.packageName) {
+ internalDistributorName
+ } else {
+ try {
+ val ai = context.packageManager.getApplicationInfo(it, 0)
+ context.packageManager.getApplicationLabel(ai)
+ } catch (e: PackageManager.NameNotFoundException) {
+ it
+ } as String
+ }
+ }.toTypedArray()
+ builder.setItems(distributorsNameArray) { _, which ->
+ val distributor = distributorsArray[which]
+ up.saveDistributor(context, distributor)
+ Timber.i("Saving distributor: $distributor")
+ up.registerApp(context)
+ onDoneRunnable?.run()
+ }
+ builder.setOnDismissListener {
+ onDoneRunnable?.run()
+ }
+ builder.setOnCancelListener {
+ onDoneRunnable?.run()
+ }
+ val dialog: AlertDialog = builder.create()
+ dialog.show()
+ }
+ }
+
+ fun unregister(context: Context) {
+ up.unregisterApp(context)
+ }
+
+ fun customOrDefaultGateway(context: Context, endpoint: String?): String {
+ // if we use the embedded distributor,
+ // register app_id type upfcm on sygnal
+ // the pushkey if FCM key
+ if (up.getDistributor(context) == context.packageName) {
+ return context.getString(R.string.pusher_http_url)
+ }
+ // else, unifiedpush, and pushkey is an endpoint
+ val default = context.getString(R.string.default_push_gateway_http_url)
+ endpoint?.let {
+ val uri = URI(it)
+ val custom = "${it.split(uri.rawPath)[0]}/_matrix/push/v1/notify"
+ Timber.i("Testing $custom")
+ /**
+ * TODO:
+ * if GET custom returns """{"unifiedpush":{"gateway":"matrix"}}"""
+ * return custom
+ */
+ }
+ return default
+ }
+
+ fun distributorExists(context: Context): Boolean {
+ return up.getDistributor(context).isNotEmpty()
+ }
+
+ fun isEmbeddedDistributor(context: Context) : Boolean {
+ return ( up.getDistributor(context) == context.packageName
+ && FcmHelper.isPushSupported())
+ }
+
+ fun isBackgroundSync(context: Context) : Boolean {
+ return ( up.getDistributor(context) == context.packageName
+ && !FcmHelper.isPushSupported())
+ }
+
+ fun getPrivacyFriendlyUpEndpoint(context: Context): String? {
+ val endpoint = getEndpointOrToken(context)
+ if (endpoint.isNullOrEmpty()) return endpoint
+ if (isEmbeddedDistributor(context)) {
+ return endpoint
+ }
+ return try {
+ val parsed = URL(endpoint)
+ "${parsed.protocol}://${parsed.host}"
+ } catch (e: Exception) {
+ Timber.e("Error parsing unifiedpush endpoint: $e")
+ null
+ }
+ }
+}
diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
old mode 100755
new mode 100644
similarity index 60%
rename from vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
rename to vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
index 8d0126d6af..4b30f802e3
--- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/VectorMessagingReceiver.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2019 New Vector Ltd
+ * 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
+ * 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,
@@ -14,27 +14,31 @@
* limitations under the License.
*/
-package im.vector.app.gplay.push.fcm
+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 com.google.firebase.messaging.FirebaseMessagingService
-import com.google.firebase.messaging.RemoteMessage
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.network.WifiDetector
-import im.vector.app.core.pushers.PushersManager
+import im.vector.app.features.badge.BadgeProxy
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils
+import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
-import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
@@ -43,18 +47,35 @@ 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.MessagingReceiver
import timber.log.Timber
import javax.inject.Inject
+@JsonClass(generateAdapter = true)
+data class UnifiedPushMessage(
+ val notification: Notification = Notification()
+)
+
+@JsonClass(generateAdapter = true)
+data class Notification(
+ @Json(name = "event_id") val eventId: String = "",
+ @Json(name = "room_id") val roomId: String = "",
+ var unread: Int = 0,
+ val counts: Counts = Counts()
+)
+
+@JsonClass(generateAdapter = true)
+data class Counts(
+ val unread: Int = 0
+)
+
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/**
- * Class extending FirebaseMessagingService.
+ * Hilt injection happen at super.onReceive().
*/
@AndroidEntryPoint
-class VectorFirebaseMessagingService : FirebaseMessagingService() {
-
+class VectorMessagingReceiver : MessagingReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
@Inject lateinit var pusherManager: PushersManager
@@ -74,21 +95,38 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* Called when message is received.
*
* @param message the message
+ * @param instance connection, for multi-account
*/
- override fun onMessageReceived(message: RemoteMessage) {
+ override fun onMessage(context: Context, message: ByteArray, instance: String) {
+ Timber.tag(loggerTag.value).d("## onMessage() received")
+ val sMessage = String(message)
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
- Timber.tag(loggerTag.value).d("## onMessageReceived() %s", message.data.toString())
+ Timber.tag(loggerTag.value).d("## onMessage() %s", sMessage)
}
- Timber.tag(loggerTag.value).d("## onMessageReceived() from FCM with priority %s", message.priority)
runBlocking {
vectorDataStore.incrementPushCounter()
}
+ val moshi: Moshi = Moshi.Builder()
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ lateinit var notification: Notification
+
+ if (UnifiedPushHelper.isEmbeddedDistributor(context)) {
+ notification = moshi.adapter(Notification::class.java)
+ .fromJson(sMessage) ?: return
+ } else {
+ val data = moshi.adapter(UnifiedPushMessage::class.java)
+ .fromJson(sMessage) ?: return
+ notification = data.notification
+ notification.unread = notification.counts.unread
+ }
+
// Diagnostic Push
- if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
+ if (notification.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(NotificationUtils.PUSH_ACTION)
- LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
return
}
@@ -102,7 +140,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
// we are in foreground, let the sync do the things?
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else {
- onMessageReceivedInternal(message.data)
+ onMessageReceivedInternal(context, notification)
}
}
}
@@ -113,55 +151,69 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* when the InstanceID token is initially generated, so this is where
* you retrieve the token.
*/
- override fun onNewToken(refreshedToken: String) {
- Timber.tag(loggerTag.value).i("onNewToken: FCM Token has been updated")
- FcmHelper.storeFcmToken(this, refreshedToken)
+ override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
+ Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
- pusherManager.enqueueRegisterPusherWithFcmKey(refreshedToken)
+ val gateway = UnifiedPushHelper.customOrDefaultGateway(context, endpoint)
+ // If the endpoint has changed
+ // or the gateway has changed
+ if (UnifiedPushHelper.getEndpointOrToken(context) != endpoint
+ || UnifiedPushHelper.getPushGateway(context) != gateway) {
+ UnifiedPushHelper.storePushGateway(context, gateway)
+ UnifiedPushHelper.storeUpEndpoint(context, endpoint)
+ pusherManager.enqueueRegisterPusher(endpoint, gateway)
+ } else {
+ Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
+ }
}
+ val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
+ vectorPreferences.setFdroidSyncBackgroundMode(mode)
}
- /**
- * Called when the FCM server deletes pending messages. This may be due to:
- * - Too many messages stored on the FCM server.
- * This can occur when an app's servers send a bunch of non-collapsible messages to FCM servers while the device is offline.
- * - The device hasn't connected in a long time and the app server has recently (within the last 4 weeks)
- * sent a message to the app on that device.
- *
- * It is recommended that the app do a full sync with the app server after receiving this call.
- */
- override fun onDeletedMessages() {
- Timber.tag(loggerTag.value).v("## onDeletedMessages()")
+ override fun onRegistrationFailed(context: Context, instance: String) {
+ Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
+ }
+
+ override fun onUnregistered(context: Context, instance: String) {
+ Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
+ val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY
+ vectorPreferences.setFdroidSyncBackgroundMode(mode)
+ runBlocking {
+ try {
+ pusherManager.unregisterPusher(UnifiedPushHelper.getEndpointOrToken(context) ?: "")
+ } catch (e: Exception) {
+ Timber.tag(loggerTag.value).d("Probably unregistering a non existant pusher")
+ }
+ }
}
/**
* Internal receive method
*
- * @param data Data map containing message data as key/value pairs.
- * For Set of keys use data.keySet().
+ * @param notification Notification containing message data.
*/
- private fun onMessageReceivedInternal(data: Map) {
+ private fun onMessageReceivedInternal(context: Context, notification: Notification) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
- Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $data")
+ Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $notification")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
}
+ // update the badge counter
+ BadgeProxy.updateBadgeCount(context, notification.unread)
+
val session = activeSessionHolder.getSafeActiveSession()
if (session == null) {
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
} else {
- val eventId = data["event_id"]
- val roomId = data["room_id"]
-
- if (isEventAlreadyKnown(eventId, roomId)) {
+ if (isEventAlreadyKnown(notification.eventId, notification.roomId)) {
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, roomId, eventId)
+ getEventFastLane(session, notification.roomId, notification.eventId)
Timber.tag(loggerTag.value).d("Requesting background sync")
session.syncService().requireBackgroundSync()
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
index fa991501ea..cf532ea744 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
@@ -21,6 +21,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig
+import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.CallService
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.CallEnded
@@ -32,7 +33,6 @@ import im.vector.app.features.call.lookup.CallUserMapper
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.vectorCallService
import im.vector.app.features.session.coroutineScope
-import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.android.sdk.api.extensions.orFalse
@@ -272,7 +272,7 @@ class WebRtcCallManager @Inject constructor(
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
// did we start background sync? so we should stop it
if (isInBackground) {
- if (FcmHelper.isPushSupported()) {
+ if (!UnifiedPushHelper.isBackgroundSync(context)) {
currentSession?.syncService()?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
@@ -378,7 +378,7 @@ class WebRtcCallManager @Inject constructor(
// and thus won't be able to received events. For example if the call is
// accepted on an other session this device will continue ringing
if (isInBackground) {
- if (FcmHelper.isPushSupported()) {
+ if (!UnifiedPushHelper.isBackgroundSync(context)) {
// only for push version as fdroid version is already doing it?
currentSession?.syncService()?.startAutomaticBackgroundSync(30, 0)
} else {
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 6f0e11f3b8..24831a67e3 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
@@ -44,6 +44,7 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.extensions.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.pushers.PushersManager
+import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
@@ -187,7 +188,15 @@ class HomeActivity :
super.onCreate(savedInstanceState)
analyticsScreenName = MobileScreen.ScreenName.Home
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
- FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
+ UnifiedPushHelper.register(this, onDoneRunnable = {
+ if (UnifiedPushHelper.isEmbeddedDistributor(this)) {
+ FcmHelper.ensureFcmTokenIsRetrieved(
+ this,
+ pushManager,
+ vectorPreferences.areNotificationEnabledForDevice()
+ )
+ }
+ })
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
views.drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) {
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 2eb62bbb1e..6a40dd2311 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.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.core.pushers.PushersManager
+import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.utils.combineLatest
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
@@ -49,7 +50,6 @@ import im.vector.app.features.settings.BackgroundSyncModeChooserDialog
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsBaseFragment
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
-import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -58,6 +58,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.api.session.pushrules.RuleIds
import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import timber.log.Timber
import javax.inject.Inject
// Referenced in vector_settings_preferences_root.xml
@@ -97,16 +98,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
findPreference(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let {
it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
- if (isChecked) {
- FcmHelper.getFcmToken(requireContext())?.let {
- pushManager.registerPusherWithFcmKey(it)
- }
- } else {
- FcmHelper.getFcmToken(requireContext())?.let {
- pushManager.unregisterPusher(it)
- session.pushersService().refreshPushers()
- }
- }
+ updateEnabledForDevice(isChecked)
}
}
@@ -222,7 +214,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
findPreference(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let {
- it.isVisible = !FcmHelper.isPushSupported()
+ it.isVisible = UnifiedPushHelper.isBackgroundSync(requireContext())
}
val backgroundSyncEnabled = vectorPreferences.isBackgroundSyncEnabled()
@@ -331,7 +323,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
private fun refreshPref() {
// This pref may have change from troubleshoot pref fragment
- if (!FcmHelper.isPushSupported()) {
+ if (UnifiedPushHelper.isBackgroundSync(requireContext())) {
findPreference(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY)
?.isChecked = vectorPreferences.autoStartOnBoot()
}
@@ -364,6 +356,26 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
}
+ private suspend fun updateEnabledForDevice(enabled: Boolean) {
+ if (enabled) {
+ UnifiedPushHelper.register(requireContext())
+ } else {
+ UnifiedPushHelper.getEndpointOrToken(requireContext())?.let {
+ try {
+ pushManager.unregisterPusher(it)
+ } catch (e: Exception) {
+ Timber.d("Probably unregistering a non existant pusher")
+ }
+ try {
+ UnifiedPushHelper.unregister(requireContext())
+ } catch (e: Exception) {
+ Timber.d("Probably unregistering to a non-saved distributor")
+ }
+ session.pushersService().refreshPushers()
+ }
+ }
+ }
+
private fun updateEnabledForAccount(preference: Preference?) {
val pushRuleService = session.pushRuleService()
val switchPref = preference as SwitchPreference
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 97ef900ce3..71ed348094 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3063,4 +3063,8 @@
${app_name} Screen Sharing
Screen sharing is in progress
+
+ Choose how to receive notifications
+ Google Services
+ Background synchronization