) {
+ private fun onMessageReceivedInternal(pushData: PushData) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
- Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $data")
+ Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
}
@@ -153,15 +181,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
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(pushData)) {
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else {
// Try to get the Event content faster
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
- getEventFastLane(session, roomId, eventId)
+ getEventFastLane(session, pushData)
Timber.tag(loggerTag.value).d("Requesting background sync")
session.syncService().requireBackgroundSync()
@@ -172,12 +197,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
}
}
- private fun getEventFastLane(session: Session, roomId: String?, eventId: String?) {
- roomId?.takeIf { it.isNotEmpty() } ?: return
- eventId?.takeIf { it.isNotEmpty() } ?: return
+ private fun getEventFastLane(session: Session, pushData: PushData) {
+ pushData.roomId ?: return
+ pushData.eventId ?: return
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
- if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(roomId)) {
+ if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(pushData.roomId)) {
return
}
@@ -188,7 +213,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
coroutineScope.launch {
Timber.tag(loggerTag.value).d("Fast lane: start request")
- val event = tryOrNull { session.eventService().getEvent(roomId, eventId) } ?: return@launch
+ val event = tryOrNull { session.eventService().getEvent(pushData.roomId, pushData.eventId) } ?: return@launch
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)
@@ -202,12 +227,12 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
// check if the event was not yet received
// a previous catchup might have already retrieved the notified event
- private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {
- if (null != eventId && null != roomId) {
+ private fun isEventAlreadyKnown(pushData: PushData): Boolean {
+ if (pushData.eventId != null && pushData.roomId != null) {
try {
val session = activeSessionHolder.getSafeActiveSession() ?: return false
- val room = session.getRoom(roomId) ?: return false
- return room.getTimelineEvent(eventId) != null
+ val room = session.getRoom(pushData.roomId) ?: return false
+ return room.getTimelineEvent(pushData.eventId) != null
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/model/PushData.kt b/vector/src/main/java/im/vector/app/core/pushers/model/PushData.kt
new file mode 100644
index 0000000000..d1d095a6fa
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/pushers/model/PushData.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.model
+
+/**
+ * Represent parsed data that the app has received from a Push content.
+ *
+ * @property eventId The Event ID. If not null, it will not be empty, and will have a valid format.
+ * @property roomId The Room ID. If not null, it will not be empty, and will have a valid format.
+ * @property unread Number of unread message.
+ */
+data class PushData(
+ val eventId: String?,
+ val roomId: String?,
+ val unread: Int?,
+)
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
new file mode 100644
index 0000000000..1b9c37ae0a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.MatrixPatterns
+
+/**
+ * In this case, the format is:
+ *
+ * {
+ * "event_id":"$anEventId",
+ * "room_id":"!aRoomId",
+ * "unread":"1",
+ * "prio":"high"
+ * }
+ *
+ * .
+ */
+@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?,
+)
+
+fun PushDataFcm.toPushData() = PushData(
+ eventId = eventId?.takeIf { MatrixPatterns.isEventId(it) },
+ roomId = roomId?.takeIf { MatrixPatterns.isRoomId(it) },
+ unread = unread
+)
diff --git a/vector/src/main/java/im/vector/app/core/pushers/model/PushDataUnifiedPush.kt b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataUnifiedPush.kt
new file mode 100644
index 0000000000..3dbd44f8ae
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataUnifiedPush.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.MatrixPatterns
+
+/**
+ * In this case, the format is:
+ *
+ * {
+ * "notification":{
+ * "event_id":"$anEventId",
+ * "room_id":"!aRoomId",
+ * "counts":{
+ * "unread":1
+ * },
+ * "prio":"high"
+ * }
+ * }
+ *
+ * .
+ */
+@JsonClass(generateAdapter = true)
+data class PushDataUnifiedPush(
+ @Json(name = "notification") val notification: PushDataUnifiedPushNotification?
+)
+
+@JsonClass(generateAdapter = true)
+data class PushDataUnifiedPushNotification(
+ @Json(name = "event_id") val eventId: String?,
+ @Json(name = "room_id") val roomId: String?,
+ @Json(name = "counts") var counts: PushDataUnifiedPushCounts?,
+)
+
+@JsonClass(generateAdapter = true)
+data class PushDataUnifiedPushCounts(
+ @Json(name = "unread") val unread: Int?
+)
+
+fun PushDataUnifiedPush.toPushData() = PushData(
+ eventId = notification?.eventId?.takeIf { MatrixPatterns.isEventId(it) },
+ roomId = notification?.roomId?.takeIf { MatrixPatterns.isRoomId(it) },
+ unread = notification?.counts?.unread
+)
diff --git a/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt b/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt
index 90558e35b7..3b6a8b595c 100644
--- a/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt
+++ b/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt
@@ -17,6 +17,7 @@
package im.vector.app.core.resources
import android.content.Context
+import im.vector.app.core.utils.getApplicationLabel
import timber.log.Timber
import javax.inject.Inject
@@ -25,9 +26,7 @@ class AppNameProvider @Inject constructor(private val context: Context) {
fun getAppName(): String {
return try {
val appPackageName = context.applicationContext.packageName
- val pm = context.packageManager
- val appInfo = pm.getApplicationInfo(appPackageName, 0)
- var appName = pm.getApplicationLabel(appInfo).toString()
+ var appName = context.getApplicationLabel(appPackageName)
// Use appPackageName instead of appName if appName contains any non-ASCII character
if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) {
diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
index 1939bdf6a9..bb38411980 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
@@ -23,6 +23,7 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.PowerManager
@@ -59,6 +60,18 @@ fun Context.isAnimationEnabled(): Boolean {
return Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) != 0f
}
+/**
+ * Return the application label of the provided package. If not found, the package is returned.
+ */
+fun Context.getApplicationLabel(packageName: String): String {
+ return try {
+ val ai = packageManager.getApplicationInfo(packageName, 0)
+ packageManager.getApplicationLabel(ai).toString()
+ } catch (e: PackageManager.NameNotFoundException) {
+ packageName
+ }
+}
+
/**
* display the system dialog for granting this permission. If previously granted, the
* system will not show it (so you should call this method).
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index 6a7a0865de..6fe4beff95 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -17,6 +17,7 @@
package im.vector.app.features
import im.vector.app.BuildConfig
+import im.vector.app.config.Config
interface VectorFeatures {
@@ -27,6 +28,7 @@ interface VectorFeatures {
fun isOnboardingPersonalizeEnabled(): Boolean
fun isOnboardingCombinedRegisterEnabled(): Boolean
fun isOnboardingCombinedLoginEnabled(): Boolean
+ fun allowExternalUnifiedPushDistributors(): Boolean
fun isScreenSharingEnabled(): Boolean
enum class OnboardingVariant {
@@ -44,5 +46,6 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isOnboardingPersonalizeEnabled() = false
override fun isOnboardingCombinedRegisterEnabled() = false
override fun isOnboardingCombinedLoginEnabled() = false
+ override fun allowExternalUnifiedPushDistributors(): Boolean = Config.ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS
override fun isScreenSharingEnabled(): Boolean = true
}
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..db03e7dc5d 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
@@ -72,7 +72,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
class WebRtcCallManager @Inject constructor(
private val context: Context,
private val activeSessionDataSource: ActiveSessionDataSource,
- private val analyticsTracker: AnalyticsTracker
+ private val analyticsTracker: AnalyticsTracker,
+ private val unifiedPushHelper: UnifiedPushHelper,
) : CallListener,
DefaultLifecycleObserver {
@@ -272,7 +273,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()) {
currentSession?.syncService()?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
@@ -378,7 +379,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()) {
// 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..f2690fa18a 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
@@ -127,6 +128,8 @@ class HomeActivity :
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
@Inject lateinit var appStateHandler: AppStateHandler
+ @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
+ @Inject lateinit var fcmHelper: FcmHelper
private val createSpaceResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
@@ -187,7 +190,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()) {
+ 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 ce9c068c9c..276317b557 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
@@ -141,6 +141,9 @@ class VectorPreferences @Inject constructor(
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_NOTIFICATION_METHOD_KEY = "SETTINGS_NOTIFICATION_METHOD_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 2eb62bbb1e..62f5823b65 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,10 +38,12 @@ 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
import im.vector.app.core.utils.requestDisablingBatteryOptimization
+import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode
@@ -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,10 +63,12 @@ import javax.inject.Inject
// Referenced in vector_settings_preferences_root.xml
class VectorSettingsNotificationPreferenceFragment @Inject constructor(
- private val pushManager: PushersManager,
+ private val unifiedPushHelper: UnifiedPushHelper,
+ private val pushersManager: PushersManager,
private val activeSessionHolder: ActiveSessionHolder,
private val vectorPreferences: VectorPreferences,
- private val guardServiceStarter: GuardServiceStarter
+ private val guardServiceStarter: GuardServiceStarter,
+ private val vectorFeatures: VectorFeatures,
) : VectorSettingsBaseFragment(),
BackgroundSyncModeChooserDialog.InteractionListener {
@@ -98,14 +101,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(requireActivity()) {
+ // Update the summary
+ findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
+ ?.summary = unifiedPushHelper.getCurrentDistributorName()
}
} else {
- FcmHelper.getFcmToken(requireContext())?.let {
- pushManager.unregisterPusher(it)
- session.pushersService().refreshPushers()
- }
+ unifiedPushHelper.unregister(pushersManager)
+ session.pushersService().refreshPushers()
}
}
}
@@ -148,6 +151,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
}
+ findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)?.let {
+ if (vectorFeatures.allowExternalUnifiedPushDistributors()) {
+ it.summary = unifiedPushHelper.getCurrentDistributorName()
+ it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ unifiedPushHelper.openDistributorDialog(requireActivity(), pushersManager) {
+ it.summary = unifiedPushHelper.getCurrentDistributorName()
+ 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()
}
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()) {
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 0000000000..acc0142924
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAvailableUnifiedPushDistributors.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 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 unifiedPushHelper: UnifiedPushHelper,
+ private val stringProvider: StringProvider,
+ private val fcmHelper: FcmHelper,
+) : TroubleshootTest(R.string.settings_troubleshoot_test_distributors_title) {
+
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ val distributors = unifiedPushHelper.getExternalDistributors()
+ description = if (distributors.isEmpty()) {
+ stringProvider.getString(
+ if (fcmHelper.isFirebaseAvailable()) {
+ R.string.settings_troubleshoot_test_distributors_gplay
+ } else {
+ R.string.settings_troubleshoot_test_distributors_fdroid
+ }
+ )
+ } else {
+ val quantity = distributors.size + 1
+ stringProvider.getQuantityString(R.plurals.settings_troubleshoot_test_distributors_many, quantity, quantity)
+ }
+ 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 0000000000..d43fb1bfe3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestCurrentUnifiedPushDistributor.kt
@@ -0,0 +1,38 @@
+/*
+ * 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 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 unifiedPushHelper: UnifiedPushHelper,
+ 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()
+ )
+ 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 0000000000..66222f759e
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestEndpointAsTokenRegistration.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.pushers.UnifiedPushStore
+import im.vector.app.core.resources.StringProvider
+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 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 {
+ 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
+ )
+ 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 83%
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 b4b8a936d0..cf2bf3d5f1 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,19 +13,16 @@
* 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
-import androidx.fragment.app.FragmentActivity
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
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
@@ -34,28 +31,22 @@ import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure
import javax.inject.Inject
/**
- * Test Push by asking the Push Gateway to send a Push back
+ * Test Push by asking the Push Gateway to send a Push back.
*/
class TestPushFromPushGateway @Inject constructor(
- private val context: FragmentActivity,
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val pushersManager: PushersManager,
- private val activeSessionHolder: ActiveSessionHolder
-) :
- TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
+ private val activeSessionHolder: ActiveSessionHolder,
+) : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
private var action: Job? = null
private var pushReceived: Boolean = false
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() }
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 0000000000..a29d1ad812
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushEndpoint.kt
@@ -0,0 +1,41 @@
+/*
+ * 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 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 stringProvider: StringProvider,
+ private val unifiedPushHelper: UnifiedPushHelper,
+) : TroubleshootTest(R.string.settings_troubleshoot_test_current_endpoint_title) {
+
+ override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ val endpoint = unifiedPushHelper.getPrivacyFriendlyUpEndpoint()
+ if (endpoint != null) {
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_current_endpoint_success, endpoint)
+ status = TestStatus.SUCCESS
+ } else {
+ 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 0000000000..38f14951b4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestUnifiedPushGateway.kt
@@ -0,0 +1,38 @@
+/*
+ * 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 im.vector.app.R
+import im.vector.app.core.pushers.UnifiedPushStore
+import im.vector.app.core.resources.StringProvider
+import javax.inject.Inject
+
+class TestUnifiedPushGateway @Inject constructor(
+ private val unifiedPushStore: UnifiedPushStore,
+ 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()
+ )
+ status = TestStatus.SUCCESS
+ }
+}
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index a7d087e71b..f60da53c09 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -855,6 +855,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
@@ -1665,6 +1669,8 @@
Register token
+ Reset notification method
+
Make a suggestion
Please write your suggestion below.
Describe your suggestion here
@@ -3063,4 +3069,23 @@
${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 method.
+ - 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 66ac93a4f9..f4d7ff8cd5 100644
--- a/vector/src/main/res/xml/vector_settings_notifications.xml
+++ b/vector/src/main/res/xml/vector_settings_notifications.xml
@@ -51,6 +51,12 @@
android:persistent="false"
android:title="@string/settings_notification_configuration">
+
+
-
\ No newline at end of file
+
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
new file mode 100644
index 0000000000..03577a4400
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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 im.vector.app.core.pushers.model.PushData
+import org.amshove.kluent.shouldBe
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+
+class PushParserTest {
+ private val validData = PushData(
+ eventId = "\$anEventId",
+ roomId = "!aRoomId:domain",
+ unread = 1
+ )
+
+ private val emptyData = PushData(
+ eventId = null,
+ roomId = null,
+ unread = null
+ )
+
+ @Test
+ fun `test edge cases`() {
+ doAllEdgeTests(true)
+ doAllEdgeTests(false)
+ }
+
+ private fun doAllEdgeTests(firebaseFormat: Boolean) {
+ val pushParser = PushParser()
+ // Empty string
+ pushParser.parseData("", firebaseFormat) shouldBe null
+ // Empty Json
+ pushParser.parseData("{}", firebaseFormat) shouldBeEqualTo emptyData
+ // Bad Json
+ pushParser.parseData("ABC", firebaseFormat) shouldBe null
+ }
+
+ @Test
+ fun `test unified push format`() {
+ val pushParser = PushParser()
+
+ pushParser.parseData(UNIFIED_PUSH_DATA, false) shouldBeEqualTo validData
+ pushParser.parseData(UNIFIED_PUSH_DATA, true) shouldBeEqualTo emptyData
+ }
+
+ @Test
+ fun `test firebase push format`() {
+ val pushParser = PushParser()
+
+ pushParser.parseData(FIREBASE_PUSH_DATA, true) shouldBeEqualTo validData
+ pushParser.parseData(FIREBASE_PUSH_DATA, false) shouldBeEqualTo emptyData
+ }
+
+ @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)
+ }
+
+ @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)
+ }
+
+ @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)
+ }
+
+ @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)
+ }
+
+ 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\"}"
+ }
+}