UnifiedPush WIP
This commit is contained in:
parent
adfcd61287
commit
95bafe4059
33 changed files with 376 additions and 215 deletions
|
|
@ -33,6 +33,7 @@ plugins {
|
||||||
id("com.google.firebase.appdistribution") version "4.0.0"
|
id("com.google.firebase.appdistribution") version "4.0.0"
|
||||||
id("org.jetbrains.kotlinx.knit") version "0.4.0"
|
id("org.jetbrains.kotlinx.knit") version "0.4.0"
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
|
// TODO Move the plugin to the firebase module?
|
||||||
id("com.google.gms.google-services")
|
id("com.google.gms.google-services")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,9 @@ class LoggedInPresenter @Inject constructor(
|
||||||
override fun present(): LoggedInState {
|
override fun present(): LoggedInState {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
// Ensure pusher is registered
|
// Ensure pusher is registered
|
||||||
// TODO Register with Firebase for now
|
// TODO Manually select push provider for now
|
||||||
val pushProvider = pushService.getAvailablePushProviders().firstOrNull() ?: return@LaunchedEffect
|
val pushProvider = pushService.getAvailablePushProviders().find { it.name == "UnifiedPush" } ?: return@LaunchedEffect
|
||||||
val distributor = pushProvider.getDistributorNames().firstOrNull() ?: return@LaunchedEffect
|
val distributor = pushProvider.getDistributors().first()
|
||||||
pushService.registerWith(matrixClient, pushProvider, distributor)
|
pushService.registerWith(matrixClient, pushProvider, distributor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||||
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
|
import io.element.android.libraries.push.providers.api.Distributor
|
||||||
import io.element.android.libraries.push.providers.api.PushProvider
|
import io.element.android.libraries.push.providers.api.PushProvider
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -60,7 +61,7 @@ class LoggedInPresenterTest {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributorName: String) {
|
override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun testPush() {
|
override suspend fun testPush() {
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,5 @@ package io.element.android.libraries.matrix.api.pusher
|
||||||
|
|
||||||
interface PushersService {
|
interface PushersService {
|
||||||
suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData): Result<Unit>
|
suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData): Result<Unit>
|
||||||
|
suspend fun unsetHttpPusher(): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,4 +53,9 @@ class RustPushersService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetHttpPusher(): Result<Unit> {
|
||||||
|
// TODO Missing client API. We need to set the pusher with Kind == null, but we do not have access to this field from the SDK.
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,5 @@ import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||||
|
|
||||||
class FakePushersService : PushersService {
|
class FakePushersService : PushersService {
|
||||||
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = Result.success(Unit)
|
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = Result.success(Unit)
|
||||||
|
override suspend fun unsetHttpPusher(): Result<Unit> = Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.libraries.push.api
|
package io.element.android.libraries.push.api
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.push.providers.api.Distributor
|
||||||
import io.element.android.libraries.push.providers.api.PushProvider
|
import io.element.android.libraries.push.providers.api.PushProvider
|
||||||
|
|
||||||
interface PushService {
|
interface PushService {
|
||||||
|
|
@ -30,7 +31,7 @@ interface PushService {
|
||||||
*
|
*
|
||||||
* The method has effect only if the [PushProvider] is different than the current one.
|
* The method has effect only if the [PushProvider] is different than the current one.
|
||||||
*/
|
*/
|
||||||
suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributorName: String)
|
suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor)
|
||||||
|
|
||||||
// TODO Move away
|
// TODO Move away
|
||||||
suspend fun testPush()
|
suspend fun testPush()
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,19 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
|
import io.element.android.libraries.push.impl.clientsecret.PushClientSecret
|
||||||
import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager
|
import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager
|
||||||
|
import io.element.android.libraries.push.providers.api.Distributor
|
||||||
import io.element.android.libraries.push.providers.api.PushProvider
|
import io.element.android.libraries.push.providers.api.PushProvider
|
||||||
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultPushService @Inject constructor(
|
class DefaultPushService @Inject constructor(
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
private val pushersManager: PushersManager,
|
private val pushersManager: PushersManager,
|
||||||
|
private val pushClientSecret: PushClientSecret,
|
||||||
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
|
private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
|
||||||
) : PushService {
|
) : PushService {
|
||||||
override fun notificationStyleChanged() {
|
override fun notificationStyleChanged() {
|
||||||
|
|
@ -38,10 +43,22 @@ class DefaultPushService @Inject constructor(
|
||||||
return pushProviders.sortedBy { it.index }
|
return pushProviders.sortedBy { it.index }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributorName: String) {
|
/**
|
||||||
// TODO Get current push provider, compare with provided one, then unregister and register if different, and store change
|
* Get current push provider, compare with provided one, then unregister and register if different, and store change
|
||||||
|
*/
|
||||||
|
override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) {
|
||||||
|
val userPushStore = userPushStoreFactory.create(matrixClient.sessionId.value)
|
||||||
|
val currentPushProviderName = userPushStore.getPushProviderName()
|
||||||
|
if (currentPushProviderName != pushProvider.name) {
|
||||||
|
// Unregister previous one if any
|
||||||
|
pushProviders.find { it.name == currentPushProviderName }?.unregister(matrixClient)
|
||||||
|
}
|
||||||
|
|
||||||
pushProvider.registerWith(matrixClient, distributorName)
|
val clientSecret = pushClientSecret.getSecretForUser(matrixClient.sessionId)
|
||||||
|
pushProvider.registerWith(matrixClient, distributor, clientSecret)
|
||||||
|
|
||||||
|
// Store new value
|
||||||
|
userPushStore.setPushProviderName(pushProvider.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun testPush() {
|
override suspend fun testPush() {
|
||||||
|
|
|
||||||
|
|
@ -148,9 +148,8 @@ class PushersManager @Inject constructor(
|
||||||
// currentSession.pushersService().removeEmailPusher(email)
|
// currentSession.pushersService().removeEmailPusher(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unregisterPusher(pushKey: String) {
|
override suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) {
|
||||||
// val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
|
matrixClient.pushersService().unsetHttpPusher()
|
||||||
// currentSession.pushersService().removeHttpPusher(pushKey, PushConfig.pusher_app_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.push.providers.api
|
||||||
|
|
||||||
|
data class Distributor(
|
||||||
|
val value: String,
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
@ -26,8 +26,23 @@ interface PushProvider {
|
||||||
* Allow to sort provider, from lower index to higher index
|
* Allow to sort provider, from lower index to higher index
|
||||||
*/
|
*/
|
||||||
val index: Int
|
val index: Int
|
||||||
fun getDistributorNames(): List<String>
|
|
||||||
suspend fun registerWith(matrixClient: MatrixClient, distributorName: String)
|
/**
|
||||||
|
* User friendly name.
|
||||||
|
*/
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
fun getDistributors(): List<Distributor>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the pusher to the homeserver
|
||||||
|
*/
|
||||||
|
suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor, clientSecret: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the pusher
|
||||||
|
*/
|
||||||
|
suspend fun unregister(matrixClient: MatrixClient)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to troubleshoot the push provider
|
* Attempt to troubleshoot the push provider
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,5 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
||||||
interface PusherSubscriber {
|
interface PusherSubscriber {
|
||||||
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String)
|
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String)
|
||||||
|
suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ dependencies {
|
||||||
implementation(projects.libraries.pushproviders.api)
|
implementation(projects.libraries.pushproviders.api)
|
||||||
|
|
||||||
implementation(platform(libs.google.firebase.bom))
|
implementation(platform(libs.google.firebase.bom))
|
||||||
implementation("com.google.firebase:firebase-messaging-ktx")
|
api("com.google.firebase:firebase-messaging-ktx")
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,6 @@ object FirebaseConfig {
|
||||||
*/
|
*/
|
||||||
const val pusher_http_url: String = "https://matrix.org/_matrix/push/v1/notify"
|
const val pusher_http_url: String = "https://matrix.org/_matrix/push/v1/notify"
|
||||||
|
|
||||||
const val internalName = "NOTIFICATION_METHOD_FIREBASE"
|
const val index = 0
|
||||||
|
const val name = "Firebase"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class FirebaseNewTokenHandler @Inject constructor(
|
||||||
// Register the pusher for all the sessions
|
// Register the pusher for all the sessions
|
||||||
sessionStore.getAllSessions().toUserList().forEach { userId ->
|
sessionStore.getAllSessions().toUserList().forEach { userId ->
|
||||||
val userDataStore = userPushStoreFactory.create(userId)
|
val userDataStore = userPushStoreFactory.create(userId)
|
||||||
if (userDataStore.getNotificationMethod() == FirebaseConfig.internalName) {
|
if (userDataStore.getPushProviderName() == FirebaseConfig.name) {
|
||||||
matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull()?.use { client ->
|
matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull()?.use { client ->
|
||||||
pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.pusher_http_url)
|
pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.pusher_http_url)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.push.providers.firebase
|
||||||
|
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.push.providers.api.Distributor
|
||||||
import io.element.android.libraries.push.providers.api.PushProvider
|
import io.element.android.libraries.push.providers.api.PushProvider
|
||||||
import io.element.android.libraries.push.providers.api.PusherSubscriber
|
import io.element.android.libraries.push.providers.api.PusherSubscriber
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -30,20 +31,27 @@ class FirebasePushProvider @Inject constructor(
|
||||||
private val firebaseTroubleshooter: FirebaseTroubleshooter,
|
private val firebaseTroubleshooter: FirebaseTroubleshooter,
|
||||||
private val pusherSubscriber: PusherSubscriber,
|
private val pusherSubscriber: PusherSubscriber,
|
||||||
) : PushProvider {
|
) : PushProvider {
|
||||||
override val index = 0
|
override val index = FirebaseConfig.index
|
||||||
|
override val name = FirebaseConfig.name
|
||||||
|
|
||||||
override fun getDistributorNames(): List<String> {
|
override fun getDistributors(): List<Distributor> {
|
||||||
// Must return an non-empty list for now
|
return listOf(Distributor("Firebase", "Firebase"))
|
||||||
return listOf("unused")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) {
|
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor, clientSecret: String) {
|
||||||
val pushKey = firebaseStore.getFcmToken() ?: return Unit.also {
|
val pushKey = firebaseStore.getFcmToken() ?: return Unit.also {
|
||||||
Timber.tag(loggerTag.value).w("Unable to register pusher, Firebase token is not known.")
|
Timber.tag(loggerTag.value).w("Unable to register pusher, Firebase token is not known.")
|
||||||
}
|
}
|
||||||
pusherSubscriber.registerPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url)
|
pusherSubscriber.registerPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun unregister(matrixClient: MatrixClient) {
|
||||||
|
val pushKey = firebaseStore.getFcmToken() ?: return Unit.also {
|
||||||
|
Timber.tag(loggerTag.value).w("Unable to unregister pusher, Firebase token is not known.")
|
||||||
|
}
|
||||||
|
pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun troubleshoot(): Result<Unit> {
|
override suspend fun troubleshoot(): Result<Unit> {
|
||||||
return firebaseTroubleshooter.troubleshoot()
|
return firebaseTroubleshooter.troubleshoot()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,21 @@ anvil {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
||||||
|
implementation(projects.libraries.pushstore.api)
|
||||||
implementation(projects.libraries.pushproviders.api)
|
implementation(projects.libraries.pushproviders.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.services.toolbox.api)
|
implementation(projects.services.toolbox.api)
|
||||||
|
|
||||||
|
implementation(projects.libraries.network)
|
||||||
|
implementation(platform(libs.network.okhttp.bom))
|
||||||
|
implementation("com.squareup.okhttp3:okhttp")
|
||||||
|
implementation(libs.network.retrofit)
|
||||||
|
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
|
|
||||||
// UnifiedPush library
|
// UnifiedPush library
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,13 @@ data class PushDataUnifiedPushCounts(
|
||||||
@SerialName("unread") val unread: Int? = null
|
@SerialName("unread") val unread: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
fun PushDataUnifiedPush.toPushData(): PushData? {
|
fun PushDataUnifiedPush.toPushData(clientSecret: String): PushData? {
|
||||||
val safeEventId = notification?.eventId?.asEventId() ?: return null
|
val safeEventId = notification?.eventId?.asEventId() ?: return null
|
||||||
val safeRoomId = notification.roomId?.asRoomId() ?: return null
|
val safeRoomId = notification.roomId?.asRoomId() ?: return null
|
||||||
return PushData(
|
return PushData(
|
||||||
eventId = safeEventId,
|
eventId = safeEventId,
|
||||||
roomId = safeRoomId,
|
roomId = safeRoomId,
|
||||||
unread = notification.counts?.unread,
|
unread = notification.counts?.unread,
|
||||||
clientSecret = null // TODO EAx check how client secret will be sent through UnifiedPush
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,45 +18,56 @@ package io.element.android.libraries.push.providers.unifiedpush
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.push.providers.api.Distributor
|
||||||
|
import io.element.android.libraries.push.providers.api.PusherSubscriber
|
||||||
import org.unifiedpush.android.connector.UnifiedPush
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RegisterUnifiedPushUseCase @Inject constructor(
|
class RegisterUnifiedPushUseCase @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
|
private val pusherSubscriber: PusherSubscriber,
|
||||||
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
sealed interface RegisterUnifiedPushResult {
|
sealed interface RegisterUnifiedPushResult {
|
||||||
object Success : RegisterUnifiedPushResult
|
object Success : RegisterUnifiedPushResult
|
||||||
object NeedToAskUserForDistributor : RegisterUnifiedPushResult
|
object NeedToAskUserForDistributor : RegisterUnifiedPushResult
|
||||||
|
object Error : RegisterUnifiedPushResult
|
||||||
}
|
}
|
||||||
|
|
||||||
fun execute(distributor: String = ""): RegisterUnifiedPushResult {
|
suspend fun execute(matrixClient: MatrixClient, distributor: Distributor, clientSecret: String): RegisterUnifiedPushResult {
|
||||||
if (distributor.isNotEmpty()) {
|
val distributorValue = distributor.value
|
||||||
saveAndRegisterApp(distributor)
|
if (distributorValue.isNotEmpty()) {
|
||||||
|
saveAndRegisterApp(distributorValue, clientSecret)
|
||||||
|
val endpoint = unifiedPushStore.getEndpoint() ?: return RegisterUnifiedPushResult.Error
|
||||||
|
val gateway = unifiedPushStore.getPushGateway() ?: return RegisterUnifiedPushResult.Error
|
||||||
|
pusherSubscriber.registerPusher(matrixClient, endpoint, gateway)
|
||||||
return RegisterUnifiedPushResult.Success
|
return RegisterUnifiedPushResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Below should never happen?
|
||||||
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
|
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
|
||||||
registerApp()
|
registerApp(clientSecret)
|
||||||
return RegisterUnifiedPushResult.Success
|
return RegisterUnifiedPushResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
val distributors = UnifiedPush.getDistributors(context)
|
val distributors = UnifiedPush.getDistributors(context)
|
||||||
|
|
||||||
return if (distributors.size == 1) {
|
return if (distributors.size == 1) {
|
||||||
saveAndRegisterApp(distributors.first())
|
saveAndRegisterApp(distributors.first(), clientSecret)
|
||||||
RegisterUnifiedPushResult.Success
|
RegisterUnifiedPushResult.Success
|
||||||
} else {
|
} else {
|
||||||
RegisterUnifiedPushResult.NeedToAskUserForDistributor
|
RegisterUnifiedPushResult.NeedToAskUserForDistributor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveAndRegisterApp(distributor: String) {
|
private fun saveAndRegisterApp(distributor: String, clientSecret: String) {
|
||||||
UnifiedPush.saveDistributor(context, distributor)
|
UnifiedPush.saveDistributor(context, distributor)
|
||||||
registerApp()
|
registerApp(clientSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerApp() {
|
private fun registerApp(clientSecret: String) {
|
||||||
UnifiedPush.registerApp(context)
|
UnifiedPush.registerApp(context = context, instance = clientSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,6 @@ object UnifiedPushConfig {
|
||||||
*/
|
*/
|
||||||
const val default_push_gateway_http_url: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
|
const val default_push_gateway_http_url: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
|
||||||
|
|
||||||
const val internalName = "NOTIFICATION_METHOD_UNIFIEDPUSH"
|
const val index = 1
|
||||||
|
const val name = "UnifiedPush"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@
|
||||||
package io.element.android.libraries.push.providers.unifiedpush
|
package io.element.android.libraries.push.providers.unifiedpush
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
|
import io.element.android.libraries.core.data.tryOrNull
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.network.RetrofitFactory
|
||||||
|
import io.element.android.libraries.push.providers.unifiedpush.network.UnifiedPushApi
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import org.unifiedpush.android.connector.UnifiedPush
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -30,132 +32,34 @@ class UnifiedPushHelper @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val unifiedPushStore: UnifiedPushStore,
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val retrofitFactory: RetrofitFactory,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
) {
|
) {
|
||||||
|
suspend fun storeCustomOrDefaultGateway(endpoint: String) {
|
||||||
/* TODO EAx
|
val gateway = UnifiedPushConfig.default_push_gateway_http_url
|
||||||
@MainThread
|
|
||||||
fun showSelectDistributorDialog(
|
|
||||||
context: Context,
|
|
||||||
onDistributorSelected: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
val internalDistributorName = stringProvider.getString(
|
|
||||||
if (fcmHelper.isFirebaseAvailable()) {
|
|
||||||
R.string.push_distributor_firebase_android
|
|
||||||
} else {
|
|
||||||
R.string.push_distributor_background_sync_android
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val distributors = UnifiedPush.getDistributors(context)
|
|
||||||
val distributorsName = distributors.map {
|
|
||||||
if (it == context.packageName) {
|
|
||||||
internalDistributorName
|
|
||||||
} else {
|
|
||||||
context.getApplicationLabel(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(stringProvider.getString(R.string.push_choose_distributor_dialog_title_android))
|
|
||||||
.setItems(distributorsName.toTypedArray()) { _, which ->
|
|
||||||
val distributor = distributors[which]
|
|
||||||
onDistributorSelected(distributor)
|
|
||||||
}
|
|
||||||
.setOnCancelListener {
|
|
||||||
// we do not want to change the distributor on behalf of the user
|
|
||||||
if (UnifiedPush.getDistributor(context).isEmpty()) {
|
|
||||||
// By default, use internal solution (fcm/background sync)
|
|
||||||
onDistributorSelected(context.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setCancelable(true)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
internal data class DiscoveryResponse(
|
|
||||||
@SerialName("unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
internal data class DiscoveryUnifiedPush(
|
|
||||||
@SerialName("gateway") val gateway: String = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun storeCustomOrDefaultGateway(
|
|
||||||
endpoint: String,
|
|
||||||
onDoneRunnable: Runnable? = null
|
|
||||||
) {
|
|
||||||
// if we use the embedded distributor,
|
|
||||||
// register app_id type upfcm on sygnal
|
|
||||||
// the pushkey if FCM key
|
|
||||||
/*
|
|
||||||
if (UnifiedPush.getDistributor(context) == context.packageName) {
|
|
||||||
unifiedPushStore.storePushGateway(PushConfig.pusher_http_url)
|
|
||||||
onDoneRunnable?.run()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
/* TODO EAx UnifiedPush
|
|
||||||
// else, unifiedpush, and pushkey is an endpoint
|
|
||||||
val gateway = PushConfig.default_push_gateway_http_url
|
|
||||||
val parsed = URL(endpoint)
|
val parsed = URL(endpoint)
|
||||||
val custom = "${parsed.protocol}://${parsed.host}/_matrix/push/v1/notify"
|
val custom = "${parsed.protocol}://${parsed.host}/_matrix/push/v1/notify"
|
||||||
Timber.i("Testing $custom")
|
Timber.i("Testing $custom")
|
||||||
try {
|
try {
|
||||||
val response = matrix.rawService().getUrl(custom, CacheStrategy.NoCache)
|
withContext(coroutineDispatchers.io) {
|
||||||
tryOrNull { Json.decodeFromString<DiscoveryResponse>(response) }
|
val api = retrofitFactory.create("${parsed.protocol}://${parsed.host}")
|
||||||
?.let { discoveryResponse ->
|
.create(UnifiedPushApi::class.java)
|
||||||
if (discoveryResponse.unifiedpush.gateway == "matrix") {
|
tryOrNull { api.discover() }
|
||||||
Timber.d("Using custom gateway")
|
?.let { discoveryResponse ->
|
||||||
unifiedPushStore.storePushGateway(custom)
|
if (discoveryResponse.unifiedpush.gateway == "matrix") {
|
||||||
onDoneRunnable?.run()
|
Timber.d("Using custom gateway")
|
||||||
return
|
unifiedPushStore.storePushGateway(custom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.d(e, "Cannot try custom gateway")
|
Timber.d(e, "Cannot try custom gateway")
|
||||||
}
|
}
|
||||||
unifiedPushStore.storePushGateway(gateway)
|
unifiedPushStore.storePushGateway(gateway)
|
||||||
onDoneRunnable?.run()
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getExternalDistributors(): List<String> {
|
private fun isEmbeddedDistributor() = false
|
||||||
return UnifiedPush.getDistributors(context)
|
|
||||||
.filterNot { it == context.packageName }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCurrentDistributorName(): String {
|
|
||||||
TODO()
|
|
||||||
/*
|
|
||||||
return when {
|
|
||||||
isEmbeddedDistributor() -> stringProvider.getString(R.string.push_distributor_firebase_android)
|
|
||||||
isBackgroundSync() -> stringProvider.getString(R.string.push_distributor_background_sync_android)
|
|
||||||
else -> context.getApplicationLabel(UnifiedPush.getDistributor(context))
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isEmbeddedDistributor(): Boolean {
|
|
||||||
TODO()
|
|
||||||
//return isInternalDistributor() && fcmHelper.isFirebaseAvailable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isBackgroundSync(): Boolean {
|
|
||||||
TODO()
|
|
||||||
//return isInternalDistributor() && !fcmHelper.isFirebaseAvailable()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isInternalDistributor(): Boolean {
|
|
||||||
return UnifiedPush.getDistributor(context).isEmpty() ||
|
|
||||||
UnifiedPush.getDistributor(context) == context.packageName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPrivacyFriendlyUpEndpoint(): String? {
|
fun getPrivacyFriendlyUpEndpoint(): String? {
|
||||||
val endpoint = getEndpointOrToken()
|
val endpoint = getEndpointOrToken()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.push.providers.unifiedpush
|
||||||
|
|
||||||
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.push.providers.api.PusherSubscriber
|
||||||
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
|
import io.element.android.libraries.sessionstorage.api.toUserList
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private val loggerTag = LoggerTag("UnifiedPushNewGatewayHandler")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle new endpoint received from UnifiedPush. Will update all the sessions which are using UnifiedPush as a push provider.
|
||||||
|
*/
|
||||||
|
class UnifiedPushNewGatewayHandler @Inject constructor(
|
||||||
|
private val pusherSubscriber: PusherSubscriber,
|
||||||
|
private val sessionStore: SessionStore,
|
||||||
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
|
) {
|
||||||
|
suspend fun handle(endpoint: String, pushGateway: String) {
|
||||||
|
// Register the pusher for all the sessions which are using UnifiedPush.
|
||||||
|
sessionStore.getAllSessions().toUserList().forEach { userId ->
|
||||||
|
val userDataStore = userPushStoreFactory.create(userId)
|
||||||
|
if (userDataStore.getPushProviderName() == UnifiedPushConfig.name) {
|
||||||
|
matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull()?.use { client ->
|
||||||
|
pusherSubscriber.registerPusher(client, endpoint, pushGateway)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).d("This session is not using UnifiedPush pusher")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ import javax.inject.Inject
|
||||||
class UnifiedPushParser @Inject constructor() {
|
class UnifiedPushParser @Inject constructor() {
|
||||||
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
private val json by lazy { Json { ignoreUnknownKeys = true } }
|
||||||
|
|
||||||
fun parse(message: ByteArray): PushData? {
|
fun parse(message: ByteArray, clientSecret: String): PushData? {
|
||||||
return tryOrNull { json.decodeFromString<PushDataUnifiedPush>(String(message)) }?.toPushData()
|
return tryOrNull { json.decodeFromString<PushDataUnifiedPush>(String(message)) }?.toPushData(clientSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,41 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.providers.unifiedpush
|
package io.element.android.libraries.push.providers.unifiedpush
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.element.android.libraries.androidutils.system.getApplicationLabel
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.push.providers.api.Distributor
|
||||||
import io.element.android.libraries.push.providers.api.PushProvider
|
import io.element.android.libraries.push.providers.api.PushProvider
|
||||||
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UnifiedPushProvider @Inject constructor() : PushProvider {
|
class UnifiedPushProvider @Inject constructor(
|
||||||
override val index = 1
|
@ApplicationContext private val context: Context,
|
||||||
|
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
|
||||||
|
private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||||
|
) : PushProvider {
|
||||||
|
override val index = UnifiedPushConfig.index
|
||||||
|
override val name = UnifiedPushConfig.name
|
||||||
|
|
||||||
override fun getDistributorNames(): List<String> {
|
override fun getDistributors(): List<Distributor> {
|
||||||
TODO("Not yet implemented")
|
val distributors = UnifiedPush.getDistributors(context)
|
||||||
|
return distributors.mapNotNull {
|
||||||
|
if (it == context.packageName) {
|
||||||
|
// Exclude self
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
Distributor(it, context.getApplicationLabel(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) {
|
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor, clientSecret: String) {
|
||||||
TODO("Not yet implemented")
|
registerUnifiedPushUseCase.execute(matrixClient, distributor, clientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unregister(matrixClient: MatrixClient) {
|
||||||
|
unRegisterUnifiedPushUseCase.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun troubleshoot(): Result<Unit> {
|
override suspend fun troubleshoot(): Result<Unit> {
|
||||||
|
|
|
||||||
|
|
@ -16,21 +16,26 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.providers.unifiedpush
|
package io.element.android.libraries.push.providers.unifiedpush
|
||||||
|
|
||||||
/*
|
import android.content.Context
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UnregisterUnifiedPushUseCase @Inject constructor(
|
class UnregisterUnifiedPushUseCase @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val pushDataStore: PushDataStore,
|
//private val pushDataStore: PushDataStore,
|
||||||
private val unifiedPushStore: UnifiedPushStore,
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
private val unifiedPushHelper: UnifiedPushHelper,
|
private val unifiedPushHelper: UnifiedPushHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(pushersManager: PushersManager?) {
|
suspend fun execute(/*pushersManager: PushersManager?*/) {
|
||||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
//val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||||
pushDataStore.setFdroidSyncBackgroundMode(mode)
|
//pushDataStore.setFdroidSyncBackgroundMode(mode)
|
||||||
try {
|
try {
|
||||||
unifiedPushHelper.getEndpointOrToken()?.let {
|
unifiedPushHelper.getEndpointOrToken()?.let {
|
||||||
Timber.d("Removing $it")
|
Timber.d("Removing $it")
|
||||||
pushersManager?.unregisterPusher(it)
|
// TODO pushersManager?.unregisterPusher(it)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.d(e, "Probably unregistering a non existing pusher")
|
Timber.d(e, "Probably unregistering a non existing pusher")
|
||||||
|
|
@ -40,4 +45,3 @@ class UnregisterUnifiedPushUseCase @Inject constructor(
|
||||||
UnifiedPush.unregisterApp(context)
|
UnifiedPush.unregisterApp(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import android.content.Intent
|
||||||
import io.element.android.libraries.architecture.bindings
|
import io.element.android.libraries.architecture.bindings
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.push.providers.api.PushHandler
|
import io.element.android.libraries.push.providers.api.PushHandler
|
||||||
|
import io.element.android.libraries.push.providers.api.PusherSubscriber
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -32,22 +33,24 @@ private val loggerTag = LoggerTag("VectorUnifiedPushMessagingReceiver")
|
||||||
|
|
||||||
class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||||
@Inject lateinit var pushParser: UnifiedPushParser
|
@Inject lateinit var pushParser: UnifiedPushParser
|
||||||
// @Inject lateinit var pushDataStore: PushDataStore
|
|
||||||
|
// @Inject lateinit var pushDataStore: PushDataStore
|
||||||
@Inject lateinit var pushHandler: PushHandler
|
@Inject lateinit var pushHandler: PushHandler
|
||||||
@Inject lateinit var guardServiceStarter: GuardServiceStarter
|
@Inject lateinit var guardServiceStarter: GuardServiceStarter
|
||||||
// @Inject lateinit var unifiedPushStore: UnifiedPushStore
|
@Inject lateinit var unifiedPushStore: UnifiedPushStore
|
||||||
// @Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||||
|
@Inject lateinit var pusherSubscriber: PusherSubscriber
|
||||||
|
@Inject lateinit var newGatewayHandler: UnifiedPushNewGatewayHandler
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
super.onReceive(context, intent)
|
|
||||||
// Inject
|
|
||||||
context.applicationContext.bindings<VectorUnifiedPushMessagingReceiverBindings>().inject(this)
|
context.applicationContext.bindings<VectorUnifiedPushMessagingReceiverBindings>().inject(this)
|
||||||
|
super.onReceive(context, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when message is received.
|
* Called when message is received. The message contains the full POST body of the push message.
|
||||||
*
|
*
|
||||||
* @param context the Android context
|
* @param context the Android context
|
||||||
* @param message the message
|
* @param message the message
|
||||||
|
|
@ -56,7 +59,7 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||||
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
||||||
Timber.tag(loggerTag.value).d("New message")
|
Timber.tag(loggerTag.value).d("New message")
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val pushData = pushParser.parse(message)
|
val pushData = pushParser.parse(message, instance)
|
||||||
if (pushData == null) {
|
if (pushData == null) {
|
||||||
Timber.tag(loggerTag.value).w("Invalid data received from UnifiedPush")
|
Timber.tag(loggerTag.value).w("Invalid data received from UnifiedPush")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -65,36 +68,36 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a new endpoint is to be used for sending push messages.
|
||||||
|
* You should send the endpoint to your application server and sync for missing notifications.
|
||||||
|
* TODO use [instance] for multi-account
|
||||||
|
*/
|
||||||
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
||||||
TODO()
|
|
||||||
/*
|
|
||||||
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
|
Timber.tag(loggerTag.value).i("onNewEndpoint: adding $endpoint")
|
||||||
if (pushDataStore.areNotificationEnabledForDevice() /* TODO EAx && activeSessionHolder.hasActiveSession() */) {
|
// If the endpoint has changed
|
||||||
// If the endpoint has changed
|
// or the gateway has changed
|
||||||
// or the gateway has changed
|
if (unifiedPushHelper.getEndpointOrToken() != endpoint) {
|
||||||
if (unifiedPushHelper.getEndpointOrToken() != endpoint) {
|
unifiedPushStore.storeUpEndpoint(endpoint)
|
||||||
unifiedPushStore.storeUpEndpoint(endpoint)
|
coroutineScope.launch {
|
||||||
coroutineScope.launch {
|
unifiedPushHelper.storeCustomOrDefaultGateway(endpoint)
|
||||||
unifiedPushHelper.storeCustomOrDefaultGateway(endpoint) {
|
unifiedPushHelper.getPushGateway()?.let { pushGateway ->
|
||||||
unifiedPushHelper.getPushGateway()?.let {
|
newGatewayHandler.handle(endpoint, pushGateway)
|
||||||
coroutineScope.launch {
|
|
||||||
pushersManager.onNewUnifiedPushEndpoint(endpoint, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Timber.tag(loggerTag.value).i("onNewEndpoint: skipped")
|
||||||
}
|
}
|
||||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
|
//val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED
|
||||||
pushDataStore.setFdroidSyncBackgroundMode(mode)
|
//pushDataStore.setFdroidSyncBackgroundMode(mode)
|
||||||
guardServiceStarter.stop()
|
guardServiceStarter.stop()
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the registration is not possible, eg. no network.
|
||||||
|
*/
|
||||||
override fun onRegistrationFailed(context: Context, instance: String) {
|
override fun onRegistrationFailed(context: Context, instance: String) {
|
||||||
TODO()
|
Timber.tag(loggerTag.value).e("onRegistrationFailed for $instance")
|
||||||
/*
|
/*
|
||||||
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show()
|
||||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||||
|
|
@ -103,10 +106,13 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when this application is unregistered from receiving push messages.
|
||||||
|
*/
|
||||||
override fun onUnregistered(context: Context, instance: String) {
|
override fun onUnregistered(context: Context, instance: String) {
|
||||||
|
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
|
||||||
TODO()
|
TODO()
|
||||||
/*
|
/*
|
||||||
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered")
|
|
||||||
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME
|
||||||
pushDataStore.setFdroidSyncBackgroundMode(mode)
|
pushDataStore.setFdroidSyncBackgroundMode(mode)
|
||||||
guardServiceStarter.start()
|
guardServiceStarter.start()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.push.providers.unifiedpush.network
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DiscoveryResponse(
|
||||||
|
@SerialName("unifiedpush") val unifiedpush: DiscoveryUnifiedPush = DiscoveryUnifiedPush()
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.push.providers.unifiedpush.network
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DiscoveryUnifiedPush(
|
||||||
|
@SerialName("gateway") val gateway: String = ""
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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 io.element.android.libraries.push.providers.unifiedpush.network
|
||||||
|
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
interface UnifiedPushApi {
|
||||||
|
@GET("_matrix/push/v1/notify")
|
||||||
|
suspend fun discover(): DiscoveryResponse
|
||||||
|
}
|
||||||
|
|
@ -20,56 +20,65 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.push.providers.api.PushData
|
import io.element.android.libraries.push.providers.api.PushData
|
||||||
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class UnifiedPushParserTest {
|
class UnifiedPushParserTest {
|
||||||
|
private val aClientSecret = "a-client-secret"
|
||||||
private val validData = PushData(
|
private val validData = PushData(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
unread = 1,
|
unread = 1,
|
||||||
// TODO handle client secret here.
|
clientSecret = aClientSecret
|
||||||
clientSecret = null
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test edge cases UnifiedPush`() {
|
fun `test edge cases UnifiedPush`() {
|
||||||
val pushParser = UnifiedPushParser()
|
val pushParser = UnifiedPushParser()
|
||||||
// Empty string
|
// Empty string
|
||||||
assertThat(pushParser.parse("".toByteArray())).isNull()
|
assertThat(pushParser.parse("".toByteArray(), aClientSecret)).isNull()
|
||||||
// Empty Json
|
// Empty Json
|
||||||
assertThat(pushParser.parse("{}".toByteArray())).isNull()
|
assertThat(pushParser.parse("{}".toByteArray(), aClientSecret)).isNull()
|
||||||
// Bad Json
|
// Bad Json
|
||||||
assertThat(pushParser.parse("ABC".toByteArray())).isNull()
|
assertThat(pushParser.parse("ABC".toByteArray(), aClientSecret)).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test UnifiedPush format`() {
|
fun `test UnifiedPush format`() {
|
||||||
val pushParser = UnifiedPushParser()
|
val pushParser = UnifiedPushParser()
|
||||||
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.toByteArray())).isEqualTo(validData)
|
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.toByteArray(), aClientSecret)).isEqualTo(validData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test empty roomId`() {
|
fun `test empty roomId`() {
|
||||||
val pushParser = UnifiedPushParser()
|
val pushParser = UnifiedPushParser()
|
||||||
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.replace(A_ROOM_ID.value, "").toByteArray())).isNull()
|
assertThrows(IllegalStateException::class.java) {
|
||||||
|
pushParser.parse(UNIFIED_PUSH_DATA.replace(A_ROOM_ID.value, "").toByteArray(), aClientSecret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test invalid roomId`() {
|
fun `test invalid roomId`() {
|
||||||
val pushParser = UnifiedPushParser()
|
val pushParser = UnifiedPushParser()
|
||||||
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.mutate(A_ROOM_ID.value, "aRoomId:domain"))).isNull()
|
assertThrows(IllegalStateException::class.java) {
|
||||||
|
pushParser.parse(UNIFIED_PUSH_DATA.mutate(A_ROOM_ID.value, "aRoomId:domain"), aClientSecret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test empty eventId`() {
|
fun `test empty eventId`() {
|
||||||
val pushParser = UnifiedPushParser()
|
val pushParser = UnifiedPushParser()
|
||||||
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, ""))).isNull()
|
assertThrows(IllegalStateException::class.java) {
|
||||||
|
pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, ""), aClientSecret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test invalid eventId`() {
|
fun `test invalid eventId`() {
|
||||||
val pushParser = UnifiedPushParser()
|
val pushParser = UnifiedPushParser()
|
||||||
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, "anEventId"))).isNull()
|
assertThrows(IllegalStateException::class.java) {
|
||||||
|
pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, "anEventId"), aClientSecret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,9 @@ package io.element.android.libraries.pushstore.api
|
||||||
* Store data related to push about a user.
|
* Store data related to push about a user.
|
||||||
*/
|
*/
|
||||||
interface UserPushStore {
|
interface UserPushStore {
|
||||||
/**
|
suspend fun getPushProviderName(): String?
|
||||||
* [NOTIFICATION_METHOD_FIREBASE] or [NOTIFICATION_METHOD_UNIFIEDPUSH].
|
|
||||||
*/
|
|
||||||
suspend fun getNotificationMethod(): String?
|
|
||||||
|
|
||||||
suspend fun setNotificationMethod(value: String)
|
suspend fun setPushProviderName(value: String)
|
||||||
|
|
||||||
suspend fun getCurrentRegisteredPushKey(): String?
|
suspend fun getCurrentRegisteredPushKey(): String?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,16 +33,16 @@ class UserPushStoreDataStore(
|
||||||
userId: String,
|
userId: String,
|
||||||
) : UserPushStore {
|
) : UserPushStore {
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "push_store_$userId")
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "push_store_$userId")
|
||||||
private val notificationMethod = stringPreferencesKey("notificationMethod")
|
private val pushProviderName = stringPreferencesKey("pushProviderName")
|
||||||
private val currentPushKey = stringPreferencesKey("currentPushKey")
|
private val currentPushKey = stringPreferencesKey("currentPushKey")
|
||||||
|
|
||||||
override suspend fun getNotificationMethod(): String? {
|
override suspend fun getPushProviderName(): String? {
|
||||||
return context.dataStore.data.first()[notificationMethod]
|
return context.dataStore.data.first()[pushProviderName]
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setNotificationMethod(value: String) {
|
override suspend fun setPushProviderName(value: String) {
|
||||||
context.dataStore.edit {
|
context.dataStore.edit {
|
||||||
it[notificationMethod] = value
|
it[pushProviderName] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ fun DependencyHandlerScope.allLibrariesImpl() {
|
||||||
// Comment to not include firebase in the project
|
// Comment to not include firebase in the project
|
||||||
implementation(project(":libraries:pushproviders:firebase"))
|
implementation(project(":libraries:pushproviders:firebase"))
|
||||||
// Comment to not include unified push in the project
|
// Comment to not include unified push in the project
|
||||||
// implementation(project(":libraries:pushproviders:unifiedpush"))
|
implementation(project(":libraries:pushproviders:unifiedpush"))
|
||||||
implementation(project(":libraries:pushstore:impl"))
|
implementation(project(":libraries:pushstore:impl"))
|
||||||
implementation(project(":libraries:architecture"))
|
implementation(project(":libraries:architecture"))
|
||||||
implementation(project(":libraries:dateformatter:impl"))
|
implementation(project(":libraries:dateformatter:impl"))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue