mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Add UnifiedPush support
This commit is contained in:
parent
928183ff64
commit
04b297b261
17 changed files with 431 additions and 73 deletions
1
changelog.d/3448.feature
Normal file
1
changelog.d/3448.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Use UnifiedPush and allows user to have push without FCM.
|
|
@ -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"
|
||||
|
|
|
@ -12,6 +12,7 @@ ext.groups = [
|
|||
'com.github.vector-im',
|
||||
'com.github.yalantis',
|
||||
'com.github.Zhuinden',
|
||||
'com.github.UnifiedPush',
|
||||
]
|
||||
],
|
||||
jitsi : [
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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-->
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
|
@ -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 {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue