Add UnifiedPush support

This commit is contained in:
sim 2022-02-25 16:25:56 +01:00 committed by Benoit Marty
parent 928183ff64
commit 04b297b261
17 changed files with 431 additions and 73 deletions

1
changelog.d/3448.feature Normal file
View File

@ -0,0 +1 @@
Use UnifiedPush and allows user to have push without FCM.

View File

@ -43,6 +43,7 @@ ext.libs = [
],
jetbrains : [
'kotlinReflect' : "org.jetbrains.kotlin:kotlin-reflect:$kotlin",
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
@ -88,6 +89,7 @@ ext.libs = [
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",
'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi",
'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi",
'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit",
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"

View File

@ -12,6 +12,7 @@ ext.groups = [
'com.github.vector-im',
'com.github.yalantis',
'com.github.Zhuinden',
'com.github.UnifiedPush',
]
],
jitsi : [

View File

@ -17,7 +17,11 @@
-->
<!-- Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
<!-- It is the push gateway for FCM embedded distributor -->
<string name="pusher_http_url" translatable="false">https://matrix.org/_matrix/push/v1/notify</string>
<!-- Note: default_push_gateway_http_url should have path '/_matrix/push/v1/notify' -->
<!-- It is the push gateway for UnifiedPush -->
<string name="default_push_gateway_http_url" translatable="false">https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify</string>
<!-- Note: pusher_app_id cannot exceed 64 chars -->
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>

View File

@ -282,6 +282,7 @@ android {
buildConfigField "boolean", "ALLOW_FCM_USE", "true"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
buildConfigField "boolean", "ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB", "true"
}
fdroid {
@ -293,6 +294,7 @@ android {
buildConfigField "boolean", "ALLOW_FCM_USE", "false"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
buildConfigField "boolean", "ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB", "true"
}
}
@ -348,6 +350,7 @@ dependencies {
implementation project(":library:multipicker")
implementation 'androidx.multidex:multidex:2.0.1'
implementation libs.jetbrains.kotlinReflect
implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid
@ -364,6 +367,7 @@ dependencies {
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0"
implementation libs.squareup.moshi
implementation libs.squareup.moshiKt
kapt libs.squareup.moshiKotlin
// Lifecycle
@ -462,8 +466,10 @@ dependencies {
// Analytics
implementation 'com.posthog.android:posthog:1.1.2'
// gplay flavor only
gplayImplementation('com.google.firebase:firebase-messaging:23.0.0') {
// UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.0.0-beta2'
// UnifiedPush gplay flavor only
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:a0056aa939') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'

View File

@ -9,13 +9,17 @@
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<service
android:name=".gplay.push.fcm.VectorFirebaseMessagingService"
<receiver
android:enabled="true"
android:name=".push.fcm.EmbeddedFCMDistributor"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="org.unifiedpush.android.distributor.REGISTER"/>
<action android:name="org.unifiedpush.android.distributor.UNREGISTER"/>
</intent-filter>
</service>
</receiver>
</application>

View File

@ -50,12 +50,12 @@ class TestPushFromPushGateway @Inject constructor(
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
pushReceived = false
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
return
}
action = activeSessionHolder.getActiveSession().coroutineScope.launch {
val result = runCatching { pushersManager.testPush(fcmToken) }
val result = runCatching { pushersManager.testPush(context) }
withContext(Dispatchers.Main) {
status = result

View File

@ -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
}
}

View File

@ -98,9 +98,9 @@ object FcmHelper {
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private fun checkPlayServices(activity: Activity): Boolean {
fun checkPlayServices(context: Context): Boolean {
val apiAvailability = GoogleApiAvailability.getInstance()
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context)
return resultCode == ConnectionResult.SUCCESS
}

View File

@ -410,6 +410,17 @@
</intent-filter>
</receiver>
<!-- UnifiedPush -->
<receiver android:exported="true" android:enabled="true" android:name=".core.pushers.VectorMessagingReceiver">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED"/>
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED"/>
</intent-filter>
</receiver>
<!-- Providers -->
<!-- Remove WorkManagerInitializer Provider because we are using on-demand initialization of WorkManager-->

View File

@ -16,6 +16,7 @@
package im.vector.app.core.pushers
import android.content.Context
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.AppNameProvider
@ -34,13 +35,13 @@ class PushersManager @Inject constructor(
private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider
) {
suspend fun testPush(pushKey: String) {
suspend fun testPush(context: Context) {
val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().testPush(
stringProvider.getString(R.string.pusher_http_url),
UnifiedPushHelper.getPushGateway(context),
stringProvider.getString(R.string.pusher_app_id),
pushKey,
UnifiedPushHelper.getEndpointOrToken(context) ?: "",
TEST_EVENT_ID
)
}
@ -50,19 +51,38 @@ class PushersManager @Inject constructor(
return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey))
}
fun enqueueRegisterPusher(
pushKey: String,
gateway: String
): UUID {
val currentSession = activeSessionHolder.getActiveSession()
return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey, gateway))
}
suspend fun registerPusherWithFcmKey(pushKey: String) {
val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey))
}
private fun createHttpPusher(pushKey: String) = HttpPusher(
suspend fun registerPusher(
pushKey: String,
gateway: String
) {
val currentSession = activeSessionHolder.getActiveSession()
currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey, gateway))
}
private fun createHttpPusher(
pushKey: String,
gateway: String = stringProvider.getString(R.string.pusher_http_url)
) = HttpPusher(
pushKey,
stringProvider.getString(R.string.pusher_app_id),
profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()),
localeProvider.current().language,
appNameProvider.getAppName(),
activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE",
stringProvider.getString(R.string.pusher_http_url),
gateway,
append = false,
withEventIdOnly = true
)

View File

@ -0,0 +1,205 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.pushers
import android.content.Context
import android.content.pm.PackageManager
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.push.fcm.FcmHelper
import org.unifiedpush.android.connector.UnifiedPush
import timber.log.Timber
import java.net.URI
import java.net.URL
object UnifiedPushHelper {
private const val PREFS_ENDPOINT_OR_TOKEN = "UP_ENDPOINT_OR_TOKEN"
private const val PREFS_PUSH_GATEWAY = "PUSH_GATEWAY"
private val up = UnifiedPush
/**
* Retrieves the UnifiedPush Endpoint.
*
* @return the UnifiedPush Endpoint or null if not received
*/
fun getEndpointOrToken(context: Context): String? {
return DefaultSharedPreferences.getInstance(context).getString(PREFS_ENDPOINT_OR_TOKEN, null)
}
/**
* Store UnifiedPush Endpoint to the SharedPrefs
* TODO Store in realm
*
* @param context android context
* @param endpoint the endpoint to store
*/
fun storeUpEndpoint(context: Context,
endpoint: String?) {
DefaultSharedPreferences.getInstance(context).edit {
putString(PREFS_ENDPOINT_OR_TOKEN, endpoint)
}
}
/**
* Retrieves the Push Gateway.
*
* @return the Push Gateway or null if not defined
*/
fun getPushGateway(context: Context): String {
return DefaultSharedPreferences.getInstance(context).getString(PREFS_PUSH_GATEWAY, null)!!
}
/**
* Store Push Gateway to the SharedPrefs
* TODO Store in realm
*
* @param context android context
* @param gateway the push gateway to store
*/
fun storePushGateway(context: Context,
gateway: String?) {
DefaultSharedPreferences.getInstance(context).edit {
putString(PREFS_PUSH_GATEWAY, gateway)
}
}
fun register(context: Context, force: Boolean = false, onDoneRunnable: Runnable? = null) {
if (!BuildConfig.ALLOW_EXTERNAL_UNIFIEDPUSH_DISTRIB) {
up.saveDistributor(context, context.packageName)
up.registerApp(context)
onDoneRunnable?.run()
return
}
if (force) {
// Un-register first
up.unregisterApp(context)
storeUpEndpoint(context, null)
storePushGateway(context, null)
} else if (up.getDistributor(context).isNotEmpty()) {
up.registerApp(context)
onDoneRunnable?.run()
return
}
val distributors = up.getDistributors(context).toMutableList()
val internalDistributorName = if (!FcmHelper.isPushSupported()) {
// Adding packageName for background sync
distributors.add(context.packageName)
context.getString(R.string.unifiedpush_getdistributors_dialog_background_sync)
} else {
context.getString(R.string.unifiedpush_getdistributors_dialog_fcm_fallback)
}
if (distributors.size == 1
&& !force){
up.saveDistributor(context, distributors.first())
up.registerApp(context)
onDoneRunnable?.run()
} else {
val builder: AlertDialog.Builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.unifiedpush_getdistributors_dialog_title))
val distributorsArray = distributors.toTypedArray()
val distributorsNameArray = distributorsArray.map {
if (it == context.packageName) {
internalDistributorName
} else {
try {
val ai = context.packageManager.getApplicationInfo(it, 0)
context.packageManager.getApplicationLabel(ai)
} catch (e: PackageManager.NameNotFoundException) {
it
} as String
}
}.toTypedArray()
builder.setItems(distributorsNameArray) { _, which ->
val distributor = distributorsArray[which]
up.saveDistributor(context, distributor)
Timber.i("Saving distributor: $distributor")
up.registerApp(context)
onDoneRunnable?.run()
}
builder.setOnDismissListener {
onDoneRunnable?.run()
}
builder.setOnCancelListener {
onDoneRunnable?.run()
}
val dialog: AlertDialog = builder.create()
dialog.show()
}
}
fun unregister(context: Context) {
up.unregisterApp(context)
}
fun customOrDefaultGateway(context: Context, endpoint: String?): String {
// if we use the embedded distributor,
// register app_id type upfcm on sygnal
// the pushkey if FCM key
if (up.getDistributor(context) == context.packageName) {
return context.getString(R.string.pusher_http_url)
}
// else, unifiedpush, and pushkey is an endpoint
val default = context.getString(R.string.default_push_gateway_http_url)
endpoint?.let {
val uri = URI(it)
val custom = "${it.split(uri.rawPath)[0]}/_matrix/push/v1/notify"
Timber.i("Testing $custom")
/**
* TODO:
* if GET custom returns """{"unifiedpush":{"gateway":"matrix"}}"""
* return custom
*/
}
return default
}
fun distributorExists(context: Context): Boolean {
return up.getDistributor(context).isNotEmpty()
}
fun isEmbeddedDistributor(context: Context) : Boolean {
return ( up.getDistributor(context) == context.packageName
&& FcmHelper.isPushSupported())
}
fun isBackgroundSync(context: Context) : Boolean {
return ( up.getDistributor(context) == context.packageName
&& !FcmHelper.isPushSupported())
}
fun getPrivacyFriendlyUpEndpoint(context: Context): String? {
val endpoint = getEndpointOrToken(context)
if (endpoint.isNullOrEmpty()) return endpoint
if (isEmbeddedDistributor(context)) {
return endpoint
}
return try {
val parsed = URL(endpoint)
"${parsed.protocol}://${parsed.host}"
} catch (e: Exception) {
Timber.e("Error parsing unifiedpush endpoint: $e")
null
}
}
}

View File

@ -1,11 +1,11 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,27 +14,31 @@
* limitations under the License.
*/
package im.vector.app.gplay.push.fcm
package im.vector.app.core.pushers
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.PushersManager
import im.vector.app.features.badge.BadgeProxy
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
@ -43,18 +47,35 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.unifiedpush.android.connector.MessagingReceiver
import timber.log.Timber
import javax.inject.Inject
@JsonClass(generateAdapter = true)
data class UnifiedPushMessage(
val notification: Notification = Notification()
)
@JsonClass(generateAdapter = true)
data class Notification(
@Json(name = "event_id") val eventId: String = "",
@Json(name = "room_id") val roomId: String = "",
var unread: Int = 0,
val counts: Counts = Counts()
)
@JsonClass(generateAdapter = true)
data class Counts(
val unread: Int = 0
)
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/**
* Class extending FirebaseMessagingService.
* Hilt injection happen at super.onReceive().
*/
@AndroidEntryPoint
class VectorFirebaseMessagingService : FirebaseMessagingService() {
class VectorMessagingReceiver : MessagingReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var notifiableEventResolver: NotifiableEventResolver
@Inject lateinit var pusherManager: PushersManager
@ -74,21 +95,38 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* Called when message is received.
*
* @param message the message
* @param instance connection, for multi-account
*/
override fun onMessageReceived(message: RemoteMessage) {
override fun onMessage(context: Context, message: ByteArray, instance: String) {
Timber.tag(loggerTag.value).d("## onMessage() received")
val sMessage = String(message)
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.tag(loggerTag.value).d("## onMessageReceived() %s", message.data.toString())
Timber.tag(loggerTag.value).d("## onMessage() %s", sMessage)
}
Timber.tag(loggerTag.value).d("## onMessageReceived() from FCM with priority %s", message.priority)
runBlocking {
vectorDataStore.incrementPushCounter()
}
val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
lateinit var notification: Notification
if (UnifiedPushHelper.isEmbeddedDistributor(context)) {
notification = moshi.adapter(Notification::class.java)
.fromJson(sMessage) ?: return
} else {
val data = moshi.adapter(UnifiedPushMessage::class.java)
.fromJson(sMessage) ?: return
notification = data.notification
notification.unread = notification.counts.unread
}
// Diagnostic Push
if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
if (notification.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(NotificationUtils.PUSH_ACTION)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
return
}
@ -102,7 +140,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
// we are in foreground, let the sync do the things?
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else {
onMessageReceivedInternal(message.data)
onMessageReceivedInternal(context, notification)
}
}
}
@ -113,55 +151,69 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* when the InstanceID token is initially generated, so this is where
* you retrieve the token.
*/
override fun onNewToken(refreshedToken: String) {
Timber.tag(loggerTag.value).i("onNewToken: FCM Token has been updated")
FcmHelper.storeFcmToken(this, refreshedToken)
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
pusherManager.enqueueRegisterPusherWithFcmKey(refreshedToken)
val gateway = UnifiedPushHelper.customOrDefaultGateway(context, endpoint)
// If the endpoint has changed
// or the gateway has changed
if (UnifiedPushHelper.getEndpointOrToken(context) != endpoint
|| UnifiedPushHelper.getPushGateway(context) != gateway) {
UnifiedPushHelper.storePushGateway(context, gateway)
UnifiedPushHelper.storeUpEndpoint(context, endpoint)
pusherManager.enqueueRegisterPusher(endpoint, gateway)
} else {
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
}
}
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
vectorPreferences.setFdroidSyncBackgroundMode(mode)
}
/**
* Called when the FCM server deletes pending messages. This may be due to:
* - Too many messages stored on the FCM server.
* This can occur when an app's servers send a bunch of non-collapsible messages to FCM servers while the device is offline.
* - The device hasn't connected in a long time and the app server has recently (within the last 4 weeks)
* sent a message to the app on that device.
*
* It is recommended that the app do a full sync with the app server after receiving this call.
*/
override fun onDeletedMessages() {
Timber.tag(loggerTag.value).v("## onDeletedMessages()")
override fun onRegistrationFailed(context: Context, instance: String) {
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
}
override fun onUnregistered(context: Context, instance: String) {
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY
vectorPreferences.setFdroidSyncBackgroundMode(mode)
runBlocking {
try {
pusherManager.unregisterPusher(UnifiedPushHelper.getEndpointOrToken(context) ?: "")
} catch (e: Exception) {
Timber.tag(loggerTag.value).d("Probably unregistering a non existant pusher")
}
}
}
/**
* Internal receive method
*
* @param data Data map containing message data as key/value pairs.
* For Set of keys use data.keySet().
* @param notification Notification containing message data.
*/
private fun onMessageReceivedInternal(data: Map<String, String>) {
private fun onMessageReceivedInternal(context: Context, notification: Notification) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $data")
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $notification")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
}
// update the badge counter
BadgeProxy.updateBadgeCount(context, notification.unread)
val session = activeSessionHolder.getSafeActiveSession()
if (session == null) {
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
} else {
val eventId = data["event_id"]
val roomId = data["room_id"]
if (isEventAlreadyKnown(eventId, roomId)) {
if (isEventAlreadyKnown(notification.eventId, notification.roomId)) {
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else {
// Try to get the Event content faster
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
getEventFastLane(session, roomId, eventId)
getEventFastLane(session, notification.roomId, notification.eventId)
Timber.tag(loggerTag.value).d("Requesting background sync")
session.syncService().requireBackgroundSync()

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.CallService
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.CallEnded
@ -32,7 +33,6 @@ import im.vector.app.features.call.lookup.CallUserMapper
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.vectorCallService
import im.vector.app.features.session.coroutineScope
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.android.sdk.api.extensions.orFalse
@ -272,7 +272,7 @@ class WebRtcCallManager @Inject constructor(
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
// did we start background sync? so we should stop it
if (isInBackground) {
if (FcmHelper.isPushSupported()) {
if (!UnifiedPushHelper.isBackgroundSync(context)) {
currentSession?.syncService()?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
@ -378,7 +378,7 @@ class WebRtcCallManager @Inject constructor(
// and thus won't be able to received events. For example if the call is
// accepted on an other session this device will continue ringing
if (isInBackground) {
if (FcmHelper.isPushSupported()) {
if (!UnifiedPushHelper.isBackgroundSync(context)) {
// only for push version as fdroid version is already doing it?
currentSession?.syncService()?.startAutomaticBackgroundSync(30, 0)
} else {

View File

@ -44,6 +44,7 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.extensions.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.databinding.ActivityHomeBinding
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
@ -187,7 +188,15 @@ class HomeActivity :
super.onCreate(savedInstanceState)
analyticsScreenName = MobileScreen.ScreenName.Home
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
UnifiedPushHelper.register(this, onDoneRunnable = {
if (UnifiedPushHelper.isEmbeddedDistributor(this)) {
FcmHelper.ensureFcmTokenIsRetrieved(
this,
pushManager,
vectorPreferences.areNotificationEnabledForDevice()
)
}
})
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
views.drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) {

View File

@ -38,6 +38,7 @@ import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.preference.VectorPreferenceCategory
import im.vector.app.core.preference.VectorSwitchPreference
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.utils.combineLatest
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
@ -49,7 +50,6 @@ import im.vector.app.features.settings.BackgroundSyncModeChooserDialog
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsBaseFragment
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
@ -58,6 +58,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.api.session.pushrules.RuleIds
import org.matrix.android.sdk.api.session.pushrules.RuleKind
import timber.log.Timber
import javax.inject.Inject
// Referenced in vector_settings_preferences_root.xml
@ -97,16 +98,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let {
it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
if (isChecked) {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.registerPusherWithFcmKey(it)
}
} else {
FcmHelper.getFcmToken(requireContext())?.let {
pushManager.unregisterPusher(it)
session.pushersService().refreshPushers()
}
}
updateEnabledForDevice(isChecked)
}
}
@ -222,7 +214,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
findPreference<VectorPreferenceCategory>(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let {
it.isVisible = !FcmHelper.isPushSupported()
it.isVisible = UnifiedPushHelper.isBackgroundSync(requireContext())
}
val backgroundSyncEnabled = vectorPreferences.isBackgroundSyncEnabled()
@ -331,7 +323,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
private fun refreshPref() {
// This pref may have change from troubleshoot pref fragment
if (!FcmHelper.isPushSupported()) {
if (UnifiedPushHelper.isBackgroundSync(requireContext())) {
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY)
?.isChecked = vectorPreferences.autoStartOnBoot()
}
@ -364,6 +356,26 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
}
}
private suspend fun updateEnabledForDevice(enabled: Boolean) {
if (enabled) {
UnifiedPushHelper.register(requireContext())
} else {
UnifiedPushHelper.getEndpointOrToken(requireContext())?.let {
try {
pushManager.unregisterPusher(it)
} catch (e: Exception) {
Timber.d("Probably unregistering a non existant pusher")
}
try {
UnifiedPushHelper.unregister(requireContext())
} catch (e: Exception) {
Timber.d("Probably unregistering to a non-saved distributor")
}
session.pushersService().refreshPushers()
}
}
}
private fun updateEnabledForAccount(preference: Preference?) {
val pushRuleService = session.pushRuleService()
val switchPref = preference as SwitchPreference

View File

@ -3063,4 +3063,8 @@
<!-- Screen sharing -->
<string name="screen_sharing_notification_title">${app_name} Screen Sharing</string>
<string name="screen_sharing_notification_description">Screen sharing is in progress</string>
<string name="unifiedpush_getdistributors_dialog_title">Choose how to receive notifications</string>
<string name="unifiedpush_getdistributors_dialog_fcm_fallback">Google Services</string>
<string name="unifiedpush_getdistributors_dialog_background_sync">Background synchronization</string>
</resources>