diff --git a/changelog.d/3448.feature b/changelog.d/3448.feature
new file mode 100644
index 00000000000..3f83f1bef5f
--- /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 7666a3bf9f8..81e3b1b4bc0 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -40,6 +40,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"
@@ -82,6 +83,7 @@ ext.libs = [
],
squareup : [
'moshi' : "com.squareup.moshi:moshi-adapters:$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 8422e05930f..b0bc881d4d6 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 78b92cbfa4f..cae094f4547 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 162d9fe81c8..9e92f113333 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -268,6 +268,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 {
@@ -279,6 +280,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"
}
}
@@ -334,6 +336,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
@@ -350,6 +353,7 @@ dependencies {
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
implementation libs.squareup.moshi
+ implementation libs.squareup.moshiKt
kapt libs.squareup.moshiKotlin
// Lifecycle
@@ -453,8 +457,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'
+ // UnifiedPush gplay flavor only
+ gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.0.0') {
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 ea9fa023ab8..ca008043c24 100644
--- a/vector/src/fdroid/AndroidManifest.xml
+++ b/vector/src/fdroid/AndroidManifest.xml
@@ -28,6 +28,19 @@
android:enabled="true"
android:exported="false" />
+
+
+
+
+
+
+
-
+
-
+
+
-
+
+
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 00000000000..14600ccbb38
--- /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 52ad4be087d..395a0daf33f 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
@@ -95,9 +95,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/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
index e96c603e608..7c9e8b8c264 100644
--- a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
+++ b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt
@@ -16,15 +16,22 @@
package im.vector.app.push.fcm
import androidx.fragment.app.Fragment
+import im.vector.app.BuildConfig
+import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
+import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
+import im.vector.app.features.settings.troubleshoot.TestCurrentUnifiedPushDistributor
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
+import im.vector.app.features.settings.troubleshoot.TestEndpointAsTokenRegistration
import im.vector.app.features.settings.troubleshoot.TestNotification
+import im.vector.app.features.settings.troubleshoot.TestPushFromPushGateway
import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings
import im.vector.app.features.settings.troubleshoot.TestSystemSettings
+import im.vector.app.features.settings.troubleshoot.TestUnifiedPushEndpoint
+import im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway
import im.vector.app.gplay.features.settings.troubleshoot.TestFirebaseToken
import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices
-import im.vector.app.gplay.features.settings.troubleshoot.TestPushFromPushGateway
import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration
import javax.inject.Inject
@@ -32,10 +39,15 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
private val testSystemSettings: TestSystemSettings,
private val testAccountSettings: TestAccountSettings,
private val testDeviceSettings: TestDeviceSettings,
- private val testBingRulesSettings: TestPushRulesSettings,
+ private val testPushRulesSettings: TestPushRulesSettings,
private val testPlayServices: TestPlayServices,
private val testFirebaseToken: TestFirebaseToken,
private val testTokenRegistration: TestTokenRegistration,
+ private val testCurrentUnifiedPushDistributor: TestCurrentUnifiedPushDistributor,
+ private val testUnifiedPushGateway: TestUnifiedPushGateway,
+ private val testUnifiedPushEndpoint: TestUnifiedPushEndpoint,
+ private val testAvailableUnifiedPushDistributors: TestAvailableUnifiedPushDistributors,
+ private val testEndpointAsTokenRegistration: TestEndpointAsTokenRegistration,
private val testPushFromPushGateway: TestPushFromPushGateway,
private val testNotification: TestNotification
) {
@@ -45,10 +57,20 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
mgr.addTest(testSystemSettings)
mgr.addTest(testAccountSettings)
mgr.addTest(testDeviceSettings)
- mgr.addTest(testBingRulesSettings)
- mgr.addTest(testPlayServices)
- mgr.addTest(testFirebaseToken)
- mgr.addTest(testTokenRegistration)
+ mgr.addTest(testPushRulesSettings)
+ if (BuildConfig.ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB) {
+ mgr.addTest(testAvailableUnifiedPushDistributors)
+ mgr.addTest(testCurrentUnifiedPushDistributor)
+ }
+ if (UnifiedPushHelper.isEmbeddedDistributor(fragment.requireContext())) {
+ mgr.addTest(testPlayServices)
+ mgr.addTest(testFirebaseToken)
+ mgr.addTest(testTokenRegistration)
+ } else {
+ mgr.addTest(testUnifiedPushGateway)
+ mgr.addTest(testUnifiedPushEndpoint)
+ mgr.addTest(testEndpointAsTokenRegistration)
+ }
mgr.addTest(testPushFromPushGateway)
mgr.addTest(testNotification)
return mgr
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 20b7c4908ae..939c3adf759 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -409,6 +409,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 b1bb4c7d882..7b37de17095 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) = PushersService.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)
+ ) = PushersService.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 00000000000..c28cd8325eb
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/pushers/UnifiedPushHelper.kt
@@ -0,0 +1,306 @@
+/*
+ * 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 com.squareup.moshi.JsonClass
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import im.vector.app.BuildConfig
+import im.vector.app.R
+import im.vector.app.core.di.DefaultSharedPreferences
+import im.vector.app.features.settings.BackgroundSyncMode
+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
+import kotlinx.coroutines.runBlocking
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.unifiedpush.android.connector.UnifiedPush
+import timber.log.Timber
+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
+ */
+ private fun storePushGateway(context: Context,
+ gateway: String?) {
+ DefaultSharedPreferences.getInstance(context).edit {
+ putString(PREFS_PUSH_GATEWAY, gateway)
+ }
+ }
+
+ fun register(context: Context, onDoneRunnable: Runnable? = null) {
+ gRegister(context,
+ onDoneRunnable = onDoneRunnable)
+ }
+
+ fun reRegister(context: Context,
+ pushersManager: PushersManager,
+ vectorPreferences: VectorPreferences,
+ onDoneRunnable: Runnable? = null) {
+ gRegister(context,
+ force = true,
+ pushersManager = pushersManager,
+ vectorPreferences = vectorPreferences,
+ onDoneRunnable = onDoneRunnable
+ )
+ }
+
+ private fun gRegister(context: Context,
+ force: Boolean = false,
+ pushersManager: PushersManager? = null,
+ vectorPreferences: VectorPreferences? = null,
+ 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
+ unregister(context, pushersManager, vectorPreferences)
+ }
+ if (up.getDistributor(context).isNotEmpty()) {
+ up.registerApp(context)
+ onDoneRunnable?.run()
+ return
+ }
+
+ // By default, use internal solution (fcm/background sync)
+ up.saveDistributor(context, context.packageName)
+ val distributors = up.getDistributors(context).toMutableList()
+
+ val internalDistributorName = if (FcmHelper.isPushSupported()) {
+ context.getString(R.string.unifiedpush_distributor_fcm_fallback)
+ } else {
+ context.getString(R.string.unifiedpush_distributor_background_sync)
+ }
+
+ 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,
+ pushersManager: PushersManager? = null,
+ vectorPreferences: VectorPreferences? = null
+ ) {
+ val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
+ vectorPreferences?.setFdroidSyncBackgroundMode(mode)
+ runBlocking {
+ try {
+ pushersManager?.unregisterPusher(getEndpointOrToken(context) ?: "")
+ } catch (e: Exception) {
+ Timber.d("Probably unregistering a non existant pusher")
+ }
+ }
+ storeUpEndpoint(context, null)
+ storePushGateway(context, null)
+ up.unregisterApp(context)
+ }
+
+ @JsonClass(generateAdapter = true)
+ internal data class DiscoveryResponse(
+ val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()
+ )
+
+ @JsonClass(generateAdapter = true)
+ internal data class DiscoveryUnifiedPush(
+ val gateway: String = ""
+ )
+
+ fun storeCustomOrDefaultGateway(
+ context: Context,
+ endpoint: String,
+ onDoneRunnable: Runnable? = null) {
+ // if we use the embedded distributor,
+ // register app_id type upfcm on sygnal
+ // the pushkey if FCM key
+ if (up.getDistributor(context) == context.packageName) {
+ context.getString(R.string.pusher_http_url).let {
+ storePushGateway(context, it)
+ onDoneRunnable?.run()
+ return
+ }
+ }
+ // else, unifiedpush, and pushkey is an endpoint
+ val gateway = context.getString(R.string.default_push_gateway_http_url)
+ val parsed = URL(endpoint)
+ val custom = "${parsed.protocol}://${parsed.host}/_matrix/push/v1/notify"
+ Timber.i("Testing $custom")
+ val thread = CoroutineScope(SupervisorJob()).launch {
+ try {
+ val moshi: Moshi = Moshi.Builder()
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ val client = OkHttpClient()
+ val request = Request.Builder()
+ .url(custom)
+ .build()
+ val sResponse = client.newCall(request).execute()
+ .body?.string() ?: ""
+ moshi.adapter(DiscoveryResponse::class.java)
+ .fromJson(sResponse)?.let { response ->
+ if (response.unifiedpush.gateway == "matrix") {
+ Timber.d("Using custom gateway")
+ storePushGateway(context, custom)
+ onDoneRunnable?.run()
+ return@launch
+ }
+ }
+ } catch (e: Exception) {
+ Timber.d("Cannot try custom gateway: $e")
+ }
+ storePushGateway(context, gateway)
+ onDoneRunnable?.run()
+ return@launch
+ }
+ thread.start()
+ }
+
+ fun getExternalDistributors(context: Context): List {
+ val distributors = up.getDistributors(context).toMutableList()
+ distributors.remove(context.packageName)
+ return distributors
+ }
+
+ fun getCurrentDistributorName(context: Context): String {
+ if (isEmbeddedDistributor(context)) {
+ return context.getString(R.string.unifiedpush_distributor_fcm_fallback)
+ }
+ if (isBackgroundSync(context)) {
+ return context.getString(R.string.unifiedpush_distributor_background_sync)
+ }
+ val distributor = up.getDistributor(context)
+ return try {
+ val ai = context.packageManager.getApplicationInfo(distributor, 0)
+ context.packageManager.getApplicationLabel(ai)
+ } catch (e: PackageManager.NameNotFoundException) {
+ distributor
+ } as String
+ }
+
+ 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 58%
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 8003b63ba0e..e1253820532
--- 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,14 +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,
@@ -17,28 +14,32 @@
* 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.core.services.GuardServiceStarter
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
@@ -47,24 +48,43 @@ 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.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
+ @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
private val coroutineScope = CoroutineScope(SupervisorJob())
@@ -77,21 +97,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
}
@@ -105,7 +142,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)
}
}
}
@@ -116,59 +153,75 @@ 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)
+ // If the endpoint has changed
+ // or the gateway has changed
+ if (UnifiedPushHelper.getEndpointOrToken(context) != endpoint) {
+ UnifiedPushHelper.storeUpEndpoint(context, endpoint)
+ UnifiedPushHelper.storeCustomOrDefaultGateway(context, endpoint) {
+ UnifiedPushHelper.getPushGateway(context)?.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()
}
- /**
- * 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()
+ 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(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
- val unreadCount = data["unread"]?.let { Integer.parseInt(it) } ?: 0
- BadgeProxy.updateBadgeCount(applicationContext, unreadCount)
+ 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.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 fe12bf1ec7f..4b96f5e12da 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
@@ -270,7 +270,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?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
@@ -375,7 +375,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?.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 d0ae7581beb..fa6aa1b4fc4 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
@@ -181,7 +182,15 @@ class HomeActivity :
super.onCreate(savedInstanceState)
analyticsScreenName = MobileScreen.ScreenName.Home
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
- FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
+ UnifiedPushHelper.register(this) {
+ 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/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 195072465bc..1d4aa4eb97a 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -136,6 +136,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY = "SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY"
const val SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY = "SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY"
+ // notification method
+ const val SETTINGS_UNIFIED_PUSH_RE_REGISTER_KEY = "SETTINGS_UNIFIED_PUSH_RE_REGISTER_KEY"
+
// Calls
const val SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY = "SETTINGS_CALL_PREVENT_ACCIDENTAL_CALL_KEY"
const val SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY"
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 d9cd5b3461a..88bdcd413e0 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
@@ -30,6 +30,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.map
import androidx.preference.Preference
import androidx.preference.SwitchPreference
+import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.registerStartForActivityResult
@@ -38,6 +39,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 +51,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
@@ -62,7 +63,7 @@ import javax.inject.Inject
// Referenced in vector_settings_preferences_root.xml
class VectorSettingsNotificationPreferenceFragment @Inject constructor(
- private val pushManager: PushersManager,
+ private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder,
private val vectorPreferences: VectorPreferences,
private val guardServiceStarter: GuardServiceStarter
@@ -98,14 +99,14 @@ 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)
- }
+ UnifiedPushHelper.register(requireContext())
} else {
- FcmHelper.getFcmToken(requireContext())?.let {
- pushManager.unregisterPusher(it)
- session.pushersService().refreshPushers()
- }
+ UnifiedPushHelper.unregister(
+ requireContext(),
+ pushersManager,
+ vectorPreferences
+ )
+ session.pushersService().refreshPushers()
}
}
}
@@ -148,6 +149,24 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
}
+ findPreference(VectorPreferences.SETTINGS_UNIFIED_PUSH_RE_REGISTER_KEY)?.let {
+ if (BuildConfig.ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB) {
+ it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ UnifiedPushHelper.reRegister(
+ requireContext(),
+ pushersManager,
+ vectorPreferences
+ ) {
+ session.pushersService().refreshPushers()
+ refreshBackgroundSyncPrefs()
+ }
+ true
+ }
+ } else {
+ it.isVisible = false
+ }
+ }
+
bindEmailNotifications()
refreshBackgroundSyncPrefs()
@@ -182,9 +201,9 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
pref.isChecked = isEnabled
pref.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
if (isChecked) {
- pushManager.registerEmailForPush(emailPid.email)
+ pushersManager.registerEmailForPush(emailPid.email)
} else {
- pushManager.unregisterEmailPusher(emailPid.email)
+ pushersManager.unregisterEmailPusher(emailPid.email)
}
}
category.addPreference(pref)
@@ -222,7 +241,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 +350,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()
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt
new file mode 100644
index 00000000000..9e25b27d0f3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.features.settings.troubleshoot
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.fragment.app.FragmentActivity
+import im.vector.app.R
+import im.vector.app.core.pushers.UnifiedPushHelper
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.push.fcm.FcmHelper
+import javax.inject.Inject
+
+class TestAvailableUnifiedPushDistributors @Inject constructor(private val context: FragmentActivity,
+ private val stringProvider: StringProvider) :
+ TroubleshootTest(R.string.settings_troubleshoot_test_distributors_title) {
+
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ val distributors = UnifiedPushHelper.getExternalDistributors(context)
+ if (distributors.isEmpty()) {
+ description = if (FcmHelper.isPushSupported()) {
+ stringProvider.getString(R.string.settings_troubleshoot_test_distributors_gplay)
+ } else {
+ stringProvider.getString(R.string.settings_troubleshoot_test_distributors_fdroid)
+ }
+ status = TestStatus.SUCCESS
+ } else {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_distributors_many,
+ distributors.size + 1)
+ status = TestStatus.SUCCESS
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestCurrentUnifiedPushDistributor.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestCurrentUnifiedPushDistributor.kt
new file mode 100644
index 00000000000..5eda48ffdc2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestCurrentUnifiedPushDistributor.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.features.settings.troubleshoot
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.fragment.app.FragmentActivity
+import im.vector.app.R
+import im.vector.app.core.pushers.UnifiedPushHelper
+import im.vector.app.core.resources.StringProvider
+import javax.inject.Inject
+
+class TestCurrentUnifiedPushDistributor @Inject constructor(private val context: FragmentActivity,
+ private val stringProvider: StringProvider) :
+ TroubleshootTest(R.string.settings_troubleshoot_test_current_distributor_title) {
+
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_current_distributor,
+ UnifiedPushHelper.getCurrentDistributorName(context))
+ status = TestStatus.SUCCESS
+ }
+}
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
new file mode 100644
index 00000000000..558b0e4fd93
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.features.settings.troubleshoot
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.Observer
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+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.resources.StringProvider
+import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.session.pushers.PusherState
+import javax.inject.Inject
+
+class TestEndpointAsTokenRegistration @Inject constructor(private val context: FragmentActivity,
+ private val stringProvider: StringProvider,
+ private val pushersManager: PushersManager,
+ private val vectorPreferences: VectorPreferences,
+ private val activeSessionHolder: ActiveSessionHolder) :
+ 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 = UnifiedPushHelper.getEndpointOrToken(context) ?: run {
+ status = TestStatus.FAILED
+ return
+ }
+ val session = activeSessionHolder.getSafeActiveSession() ?: run {
+ status = TestStatus.FAILED
+ return
+ }
+ val pushers = session.pushersService().getPushers().filter {
+ it.pushKey == endpoint && it.state == PusherState.REGISTERED
+ }
+ if (pushers.isEmpty()) {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_registration_failed,
+ stringProvider.getString(R.string.sas_error_unknown))
+ quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_endpoint_registration_quick_fix) {
+ override fun doFix() {
+ UnifiedPushHelper.reRegister(
+ context,
+ pushersManager,
+ vectorPreferences
+ )
+ val workId = pushersManager.enqueueRegisterPusherWithFcmKey(endpoint)
+ WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo ->
+ if (workInfo != null) {
+ if (workInfo.state == WorkInfo.State.SUCCEEDED) {
+ manager?.retry(activityResultLauncher)
+ } else if (workInfo.state == WorkInfo.State.FAILED) {
+ manager?.retry(activityResultLauncher)
+ }
+ }
+ })
+ }
+ }
+
+ status = TestStatus.FAILED
+ } else {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_endpoint_registration_success)
+ status = TestStatus.SUCCESS
+ }
+ }
+}
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushFromPushGateway.kt
similarity index 91%
rename from vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
rename to vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushFromPushGateway.kt
index f485de760a0..da26200df24 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushFromPushGateway.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package im.vector.app.gplay.features.settings.troubleshoot
+package im.vector.app.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
@@ -24,8 +24,6 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.session.coroutineScope
-import im.vector.app.features.settings.troubleshoot.TroubleshootTest
-import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@@ -48,12 +46,8 @@ class TestPushFromPushGateway @Inject constructor(private val context: FragmentA
override fun perform(activityResultLauncher: ActivityResultLauncher) {
pushReceived = false
- val fcmToken = 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/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushEndpoint.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushEndpoint.kt
new file mode 100644
index 00000000000..ab4e3dd7c2b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushEndpoint.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.features.settings.troubleshoot
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.fragment.app.FragmentActivity
+import im.vector.app.R
+import im.vector.app.core.pushers.UnifiedPushHelper
+import im.vector.app.core.resources.StringProvider
+import javax.inject.Inject
+
+class TestUnifiedPushEndpoint @Inject constructor(private val context: FragmentActivity,
+ private val stringProvider: StringProvider) :
+ TroubleshootTest(R.string.settings_troubleshoot_test_current_endpoint_title) {
+
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ val endpoint = UnifiedPushHelper.getPrivacyFriendlyUpEndpoint(context)
+ endpoint?.let {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_current_endpoint_success,
+ UnifiedPushHelper.getPrivacyFriendlyUpEndpoint(context))
+ status = TestStatus.SUCCESS
+ } ?: run {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_current_endpoint_failed)
+ status = TestStatus.FAILED
+ }
+ }
+}
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
new file mode 100644
index 00000000000..75e8962c84e
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushGateway.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.features.settings.troubleshoot
+
+import android.content.Intent
+import androidx.activity.result.ActivityResultLauncher
+import androidx.fragment.app.FragmentActivity
+import im.vector.app.R
+import im.vector.app.core.pushers.UnifiedPushHelper
+import im.vector.app.core.resources.StringProvider
+import javax.inject.Inject
+
+class TestUnifiedPushGateway @Inject constructor(private val context: FragmentActivity,
+ 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,
+ UnifiedPushHelper.getPushGateway(context))
+ status = TestStatus.SUCCESS
+ }
+}
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 8607e8e0bef..cccc48e9496 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -849,6 +849,10 @@
FCM token successfully registered to homeserver.
Failed to register FCM token to homeserver:\n%1$s
+ Endpoint Registration
+ Endpoint successfully registered to homeserver.
+ Failed to register endpoint token to homeserver:\n%1$s
+
Test Push
The application is waiting for the PUSH
The application is receiving PUSH
@@ -1657,6 +1661,8 @@
Register token
+ Reset notification method
+
Make a suggestion
Please write your suggestion below.
Describe your suggestion here
@@ -3038,4 +3044,20 @@
${app_name} Screen Sharing
Screen sharing is in progress
+
+ Choose how to receive notifications
+ Google Services
+ Background synchronization
+ Notification method
+ Available methods
+ No other method than Google Play Service found.
+ No other method than background synchronization found.
+ Found %d methods.
+ Method
+ Currently using %s.
+ Endpoint
+ Current endpoint: %s
+ Cannot find the endpoint.
+ Gateway
+ Current gateway: %s
diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml
index 66ac93a4f93..c8434b69200 100644
--- a/vector/src/main/res/xml/vector_settings_notifications.xml
+++ b/vector/src/main/res/xml/vector_settings_notifications.xml
@@ -77,6 +77,11 @@
android:summary="@string/settings_system_preferences_summary"
android:title="@string/settings_call_notifications_preferences" />
+
+