Split push module into several modules: Firebase, UnifiedPush, store

This commit is contained in:
Benoit Marty 2023-04-07 20:42:59 +02:00 committed by Benoit Marty
parent 1134f50090
commit 219b97eea7
53 changed files with 768 additions and 276 deletions

View file

@ -45,6 +45,7 @@ dependencies {
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.libraries.push.api) implementation(projects.libraries.push.api)
implementation(projects.libraries.pushproviders.api)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrixui) implementation(projects.libraries.matrixui)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)

View file

@ -46,7 +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
pushService.registerFirebasePusher(matrixClient) // TODO Register with Firebase for now
val pushProvider = pushService.getAvailablePushProviders().firstOrNull() ?: return@LaunchedEffect
pushService.registerWith(matrixClient, pushProvider, pushProvider.getDistributorNames().first())
} }
val permissionsState = postNotificationPermissionsPresenter.present() val permissionsState = postNotificationPermissionsPresenter.present()

View file

@ -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.PushProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@ -55,7 +56,11 @@ class LoggedInPresenterTest {
override fun notificationStyleChanged() { override fun notificationStyleChanged() {
} }
override suspend fun registerFirebasePusher(matrixClient: MatrixClient) { override fun getAvailablePushProviders(): List<PushProvider> {
return emptyList()
}
override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributorName: String) {
} }
override suspend fun testPush() { override suspend fun testPush() {

View file

@ -26,4 +26,5 @@ dependencies {
implementation(libs.androidx.corektx) implementation(libs.androidx.corektx)
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(projects.libraries.pushproviders.api)
} }

View file

@ -17,12 +17,21 @@
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.PushProvider
interface PushService { interface PushService {
// TODO Move away
fun notificationStyleChanged() fun notificationStyleChanged()
// Ensure pusher is registered fun getAvailablePushProviders(): List<PushProvider>
suspend fun registerFirebasePusher(matrixClient: MatrixClient)
/**
* Will unregister any previous pusher and register a new one with the provided [PushProvider].
*
* The method has effect only if the [PushProvider] is different than the current one.
*/
suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributorName: String)
// TODO Move away
suspend fun testPush() suspend fun testPush()
} }

View file

@ -43,6 +43,8 @@ dependencies {
implementation(projects.libraries.androidutils) implementation(projects.libraries.androidutils)
implementation(projects.libraries.network) implementation(projects.libraries.network)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
api(projects.libraries.pushproviders.api)
api(projects.libraries.pushstore.api)
api(projects.libraries.push.api) api(projects.libraries.push.api)
implementation(projects.services.analytics.api) implementation(projects.services.analytics.api)
@ -53,12 +55,10 @@ dependencies {
exclude(group = "com.android.support", module = "support-annotations") exclude(group = "com.android.support", module = "support-annotations")
} }
// TODO Remove
implementation(platform(libs.google.firebase.bom)) implementation(platform(libs.google.firebase.bom))
implementation("com.google.firebase:firebase-messaging-ktx") implementation("com.google.firebase:firebase-messaging-ktx")
// UnifiedPush
api(libs.unifiedpush)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.mockk) testImplementation(libs.test.mockk)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)

View file

@ -14,63 +14,15 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application> <application>
<!-- Firebase components -->
<meta-data
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<service
android:name=".firebase.VectorFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- UnifiedPush -->
<receiver
android:name=".unifiedpush.VectorUnifiedPushMessagingReceiver"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedReceiver">
<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>
<receiver
android:name=".unifiedpush.KeepInternalDistributor"
android:enabled="true"
android:exported="false">
<intent-filter>
<!--
This action is checked to track installed and uninstalled distributors.
We declare it to keep the background sync as an internal
unifiedpush distributor.
-->
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
</intent-filter>
</receiver>
<receiver <receiver
android:name=".notifications.TestNotificationReceiver" android:name=".notifications.TestNotificationReceiver"
android:exported="false" /> android:exported="false" />
<receiver <receiver
android:name=".notifications.NotificationBroadcastReceiver" android:name=".notifications.NotificationBroadcastReceiver"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
</application> </application>
</manifest> </manifest>

View file

@ -20,27 +20,29 @@ 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.config.PushConfig
import io.element.android.libraries.push.impl.log.pushLoggerTag
import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager
import timber.log.Timber import io.element.android.libraries.push.providers.api.PushProvider
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 fcmHelper: FcmHelper, private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
) : PushService { ) : PushService {
override fun notificationStyleChanged() { override fun notificationStyleChanged() {
notificationDrawerManager.notificationStyleChanged() notificationDrawerManager.notificationStyleChanged()
} }
override suspend fun registerFirebasePusher(matrixClient: MatrixClient) { override fun getAvailablePushProviders(): List<PushProvider> {
val pushKey = fcmHelper.getFcmToken() ?: return Unit.also { // TODO Sort by priority?
Timber.tag(pushLoggerTag.value).w("Unable to register pusher, Firebase token is not known.") return pushProviders.toList()
} }
pushersManager.registerPusher(matrixClient, pushKey, PushConfig.pusher_http_url)
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
pushProvider.registerWith(matrixClient, distributorName)
} }
override suspend fun testPush() { override suspend fun testPush() {

View file

@ -1,49 +0,0 @@
/*
* 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 io.element.android.libraries.push.impl
interface FcmHelper {
fun isFirebaseAvailable(): Boolean
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM.
*/
fun getFcmToken(): String?
/**
* Store FCM token to the SharedPrefs.
*
* @param token the token to store.
*/
fun storeFcmToken(token: String?)
/**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set.
*
* @param pushersManager the instance to register the pusher on.
* @param registerPusher whether the pusher should be registered.
*/
fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean)
/*
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder)
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder)
*/
}

View file

@ -16,7 +16,9 @@
package io.element.android.libraries.push.impl package io.element.android.libraries.push.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.log.logger.LoggerTag
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.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
@ -26,10 +28,9 @@ import io.element.android.libraries.push.impl.clientsecret.PushClientSecret
import io.element.android.libraries.push.impl.config.PushConfig import io.element.android.libraries.push.impl.config.PushConfig
import io.element.android.libraries.push.impl.log.pushLoggerTag import io.element.android.libraries.push.impl.log.pushLoggerTag
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
import io.element.android.libraries.push.impl.userpushstore.UserPushStoreFactory import io.element.android.libraries.push.providers.api.PusherSubscriber
import io.element.android.libraries.push.impl.userpushstore.isFirebase import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.api.toUserList
import io.element.android.services.toolbox.api.appname.AppNameProvider import io.element.android.services.toolbox.api.appname.AppNameProvider
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -38,8 +39,9 @@ internal const val DEFAULT_PUSHER_FILE_TAG = "mobile"
private val loggerTag = LoggerTag("PushersManager", pushLoggerTag) private val loggerTag = LoggerTag("PushersManager", pushLoggerTag)
@ContributesBinding(AppScope::class)
class PushersManager @Inject constructor( class PushersManager @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper, // private val unifiedPushHelper: UnifiedPushHelper,
// private val localeProvider: LocaleProvider, // private val localeProvider: LocaleProvider,
private val appNameProvider: AppNameProvider, private val appNameProvider: AppNameProvider,
// private val getDeviceInfoUseCase: GetDeviceInfoUseCase, // private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
@ -48,14 +50,14 @@ class PushersManager @Inject constructor(
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val matrixAuthenticationService: MatrixAuthenticationService, private val matrixAuthenticationService: MatrixAuthenticationService,
private val userPushStoreFactory: UserPushStoreFactory, private val userPushStoreFactory: UserPushStoreFactory,
private val fcmHelper: FcmHelper, ): PusherSubscriber {
) { // TODO Move this to the PushProvider API
suspend fun testPush() { suspend fun testPush() {
pushGatewayNotifyRequest.execute( pushGatewayNotifyRequest.execute(
PushGatewayNotifyRequest.Params( PushGatewayNotifyRequest.Params(
url = unifiedPushHelper.getPushGateway() ?: return, url = "TODO", // unifiedPushHelper.getPushGateway() ?: return,
appId = PushConfig.pusher_app_id, appId = PushConfig.pusher_app_id,
pushKey = unifiedPushHelper.getEndpointOrToken().orEmpty(), pushKey = "TODO", // unifiedPushHelper.getEndpointOrToken().orEmpty(),
eventId = TEST_EVENT_ID eventId = TEST_EVENT_ID
) )
) )
@ -73,26 +75,10 @@ class PushersManager @Inject constructor(
TODO() TODO()
} }
suspend fun onNewFirebaseToken(firebaseToken: String) {
fcmHelper.storeFcmToken(firebaseToken)
// Register the pusher for all the sessions
sessionStore.getAllSessions().toUserList().forEach { userId ->
val userDataStore = userPushStoreFactory.create(userId)
if (userDataStore.isFirebase()) {
matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull()?.use { client ->
registerPusher(client, firebaseToken, PushConfig.pusher_http_url)
}
} else {
Timber.tag(loggerTag.value).d("This session is not using Firebase pusher")
}
}
}
/** /**
* Register a pusher to the server if not done yet. * Register a pusher to the server if not done yet.
*/ */
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) { override suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) {
val userDataStore = userPushStoreFactory.create(matrixClient.sessionId.value) val userDataStore = userPushStoreFactory.create(matrixClient.sessionId.value)
if (userDataStore.getCurrentRegisteredPushKey() == pushKey) { if (userDataStore.getCurrentRegisteredPushKey() == pushKey) {
Timber.tag(loggerTag.value).d("Unnecessary to register again the same pusher") Timber.tag(loggerTag.value).d("Unnecessary to register again the same pusher")

View file

@ -17,25 +17,8 @@
package io.element.android.libraries.push.impl.config package io.element.android.libraries.push.impl.config
object PushConfig { object PushConfig {
/**
* It is the push gateway for FCM embedded distributor.
* Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
*/
const val pusher_http_url: String = "https://matrix.org/_matrix/push/v1/notify"
/**
* It is the push gateway for UnifiedPush.
* Note: default_push_gateway_http_url should have path '/_matrix/push/v1/notify'
*/
const val default_push_gateway_http_url: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
/** /**
* Note: pusher_app_id cannot exceed 64 chars. * Note: pusher_app_id cannot exceed 64 chars.
*/ */
const val pusher_app_id: String = "im.vector.app.android" const val pusher_app_id: String = "im.vector.app.android"
/**
* Set to true to allow external push distributor such as Ntfy.
*/
const val allowExternalUnifiedPushDistributors: Boolean = false
} }

View file

@ -21,9 +21,11 @@ import android.content.Intent
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.androidutils.network.WifiDetector import io.element.android.libraries.androidutils.network.WifiDetector
import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.push.api.store.PushDataStore import io.element.android.libraries.push.api.store.PushDataStore
@ -34,6 +36,8 @@ import io.element.android.libraries.push.impl.notifications.NotifiableEventResol
import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationActionIds
import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager
import io.element.android.libraries.push.impl.store.DefaultPushDataStore import io.element.android.libraries.push.impl.store.DefaultPushDataStore
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.push.providers.api.PushHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -43,7 +47,8 @@ import javax.inject.Inject
private val loggerTag = LoggerTag("PushHandler", pushLoggerTag) private val loggerTag = LoggerTag("PushHandler", pushLoggerTag)
class PushHandler @Inject constructor( @ContributesBinding(AppScope::class)
class DefaultPushHandler @Inject constructor(
private val notificationDrawerManager: NotificationDrawerManager, private val notificationDrawerManager: NotificationDrawerManager,
private val notifiableEventResolver: NotifiableEventResolver, private val notifiableEventResolver: NotifiableEventResolver,
private val pushDataStore: PushDataStore, private val pushDataStore: PushDataStore,
@ -53,7 +58,7 @@ class PushHandler @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val matrixAuthenticationService: MatrixAuthenticationService, private val matrixAuthenticationService: MatrixAuthenticationService,
) { ): PushHandler {
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
private val wifiDetector: WifiDetector = WifiDetector(context) private val wifiDetector: WifiDetector = WifiDetector(context)
@ -68,7 +73,7 @@ class PushHandler @Inject constructor(
* *
* @param pushData the data received in the push. * @param pushData the data received in the push.
*/ */
suspend fun handle(pushData: PushData) { override suspend fun handle(pushData: PushData) {
Timber.tag(loggerTag.value).d("## handling pushData") Timber.tag(loggerTag.value).d("## handling pushData")
if (buildMeta.lowPrivacyLoggingEnabled) { if (buildMeta.lowPrivacyLoggingEnabled) {

View file

@ -0,0 +1,28 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.push.providers.api"
}
dependencies {
implementation(projects.libraries.core)
implementation(projects.libraries.matrix.api)
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.push package io.element.android.libraries.push.providers.api
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -28,8 +28,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
* @property clientSecret data used when the pusher was configured, to be able to determine the session. * @property clientSecret data used when the pusher was configured, to be able to determine the session.
*/ */
data class PushData( data class PushData(
val eventId: EventId, val eventId: EventId,
val roomId: RoomId, val roomId: RoomId,
val unread: Int?, val unread: Int?,
val clientSecret: String?, val clientSecret: String?,
) )

View file

@ -0,0 +1,21 @@
/*
* 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
interface PushHandler {
suspend fun handle(pushData: PushData)
}

View file

@ -0,0 +1,27 @@
/*
* 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
import io.element.android.libraries.matrix.api.MatrixClient
/**
* This is the main API for this module
*/
interface PushProvider {
fun getDistributorNames(): List<String>
suspend fun registerWith(matrixClient: MatrixClient, distributorName: String)
}

View file

@ -0,0 +1,23 @@
/*
* 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
import io.element.android.libraries.matrix.api.MatrixClient
interface PusherSubscriber {
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String)
}

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
// kotlin("plugin.serialization") version "1.8.10"
}
android {
namespace = "io.element.android.libraries.push.providers.firebase"
}
anvil {
generateDaggerFactories.set(true)
}
dependencies {
implementation(libs.dagger)
implementation(libs.androidx.corektx)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.di)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.pushstore.api)
implementation(projects.libraries.pushproviders.api)
implementation(platform(libs.google.firebase.bom))
implementation("com.google.firebase:firebase-messaging-ktx")
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testImplementation(projects.libraries.matrix.test)
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- Firebase components -->
<meta-data
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<service
android:name="io.element.android.libraries.push.providers.firebase.VectorFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>

View file

@ -14,24 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.firebase package io.element.android.libraries.push.providers.firebase
import io.element.android.libraries.push.impl.FcmHelper
import io.element.android.libraries.push.impl.PushersManager
import io.element.android.libraries.push.impl.UnifiedPushHelper
import javax.inject.Inject import javax.inject.Inject
// TODO
class EnsureFcmTokenIsRetrievedUseCase @Inject constructor( class EnsureFcmTokenIsRetrievedUseCase @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper, // private val unifiedPushHelper: UnifiedPushHelper,
private val fcmHelper: FcmHelper, // private val fcmHelper: FcmHelper,
// private val activeSessionHolder: ActiveSessionHolder, // private val activeSessionHolder: ActiveSessionHolder,
) { ) {
fun execute(pushersManager: PushersManager, registerPusher: Boolean) { // fun execute(pushersManager: PushersManager, registerPusher: Boolean) {
if (unifiedPushHelper.isEmbeddedDistributor()) { // if (unifiedPushHelper.isEmbeddedDistributor()) {
fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher)) // fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher))
} // }
} // }
private fun shouldAddHttpPusher(registerPusher: Boolean) = if (registerPusher) { private fun shouldAddHttpPusher(registerPusher: Boolean) = if (registerPusher) {
/* /*

View file

@ -0,0 +1,27 @@
/*
* 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.firebase
object FirebaseConfig {
/**
* It is the push gateway for firebase.
* Note: pusher_http_url should have path '/_matrix/push/v1/notify' -->
*/
const val pusher_http_url: String = "https://matrix.org/_matrix/push/v1/notify"
const val internalName = "NOTIFICATION_METHOD_FIREBASE"
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.firebase package io.element.android.libraries.push.providers.firebase
import io.element.android.libraries.push.impl.push.PushData import io.element.android.libraries.push.providers.api.PushData
import javax.inject.Inject import javax.inject.Inject
class FirebasePushParser @Inject constructor() { class FirebasePushParser @Inject constructor() {

View file

@ -0,0 +1,44 @@
/*
* 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.firebase
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.push.providers.api.PusherSubscriber
import timber.log.Timber
import javax.inject.Inject
private val loggerTag = LoggerTag("FirebasePushProvider")
class FirebasePushProvider @Inject constructor(
private val googleFcmHelper: GoogleFcmHelper,
private val pusherSubscriber: PusherSubscriber,
) : PushProvider {
override fun getDistributorNames(): List<String> {
// Must return an non-empty list for now
return listOf("unused")
}
override suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) {
val pushKey = googleFcmHelper.getFcmToken() ?: return Unit.also {
Timber.tag(loggerTag.value).w("Unable to register pusher, Firebase token is not known.")
}
pusherSubscriber.registerPusher(matrixClient, pushKey, FirebaseConfig.pusher_http_url)
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.firebase
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("FirebaseSetPusher")
// TODO Rename
class FirebaseSetPusher @Inject constructor(
private val pusherSubscriber: PusherSubscriber,
private val sessionStore: SessionStore,
private val userPushStoreFactory: UserPushStoreFactory,
private val matrixAuthenticationService: MatrixAuthenticationService,
) {
suspend fun onNewFirebaseToken(firebaseToken: String) {
// Register the pusher for all the sessions
sessionStore.getAllSessions().toUserList().forEach { userId ->
val userDataStore = userPushStoreFactory.create(userId)
if (userDataStore.getNotificationMethod() == FirebaseConfig.internalName) {
matrixAuthenticationService.restoreSession(SessionId(userId)).getOrNull()?.use { client ->
pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.pusher_http_url)
}
} else {
Timber.tag(loggerTag.value).d("This session is not using Firebase pusher")
}
}
}
}

View file

@ -14,44 +14,37 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl package io.element.android.libraries.push.providers.firebase
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import com.google.firebase.messaging.FirebaseMessaging
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.DefaultPreferences import io.element.android.libraries.di.DefaultPreferences
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
/** /**
* This class store the FCM token in SharedPrefs and ensure this token is retrieved. * This class store the FCM token in SharedPrefs and ensure this token is retrieved.
* It has an alter ego in the fdroid variant. * It has an alter ego in the fdroid variant.
*/ */
@ContributesBinding(AppScope::class) // TODO Rename to store?
class GoogleFcmHelper @Inject constructor( class GoogleFcmHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
@DefaultPreferences private val sharedPrefs: SharedPreferences, @DefaultPreferences private val sharedPrefs: SharedPreferences,
) : FcmHelper { ) {
override fun isFirebaseAvailable(): Boolean = true fun getFcmToken(): String? {
override fun getFcmToken(): String? {
return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null)
} }
override fun storeFcmToken(token: String?) { fun storeFcmToken(token: String?) {
sharedPrefs.edit { sharedPrefs.edit {
putString(PREFS_KEY_FCM_TOKEN, token) putString(PREFS_KEY_FCM_TOKEN, token)
} }
} }
/*
override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) { override fun ensureFcmTokenIsRetrieved(pushersManager: PushersManager, registerPusher: Boolean) {
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
if (checkPlayServices(context)) { if (checkPlayServices(context)) {
@ -76,6 +69,7 @@ class GoogleFcmHelper @Inject constructor(
Timber.e("No valid Google Play Services found. Cannot use FCM.") Timber.e("No valid Google Play Services found. Cannot use FCM.")
} }
} }
*/
/** /**
* Check the device to make sure it has the Google Play Services APK. If * Check the device to make sure it has the Google Play Services APK. If

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.firebase package io.element.android.libraries.push.providers.firebase
import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.core.asEventId
import io.element.android.libraries.matrix.api.core.asRoomId import io.element.android.libraries.matrix.api.core.asRoomId
import io.element.android.libraries.push.impl.push.PushData import io.element.android.libraries.push.providers.api.PushData
/** /**
* In this case, the format is: * In this case, the format is:

View file

@ -14,27 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.firebase package io.element.android.libraries.push.providers.firebase
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
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.impl.PushersManager import io.element.android.libraries.push.providers.api.PushHandler
import io.element.android.libraries.push.impl.log.pushLoggerTag
import io.element.android.libraries.push.impl.push.PushHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("Firebase", pushLoggerTag) private val loggerTag = LoggerTag("Firebase")
class VectorFirebaseMessagingService : FirebaseMessagingService() { class VectorFirebaseMessagingService : FirebaseMessagingService() {
@Inject lateinit var pushersManager: PushersManager @Inject lateinit var firebaseSetPusher: FirebaseSetPusher
@Inject lateinit var pushParser: FirebasePushParser @Inject lateinit var pushParser: FirebasePushParser
@Inject lateinit var pushHandler: PushHandler @Inject lateinit var pushHandler: PushHandler
@Inject lateinit var googleFcmHelper: GoogleFcmHelper
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
@ -45,8 +44,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
Timber.tag(loggerTag.value).d("New Firebase token") Timber.tag(loggerTag.value).d("New Firebase token")
googleFcmHelper.storeFcmToken(token)
coroutineScope.launch { coroutineScope.launch {
pushersManager.onNewFirebaseToken(token) firebaseSetPusher.onNewFirebaseToken(token)
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.firebase package io.element.android.libraries.push.providers.firebase
import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.annotations.ContributesTo
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope

View file

@ -0,0 +1,33 @@
/*
* 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.firebase.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.push.providers.firebase.FirebasePushProvider
@Module
@ContributesTo(AppScope::class)
interface FirebaseModule {
@Binds
@IntoSet
fun bind(pushProvider: FirebasePushProvider): PushProvider
}

View file

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.firebase package io.element.android.libraries.push.providers.firebase
import com.google.common.truth.Truth.assertThat 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.impl.push.PushData import io.element.android.libraries.push.providers.api.PushData
import org.junit.Test import org.junit.Test
class FirebasePushParserTest { class FirebasePushParserTest {

View file

@ -0,0 +1,49 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.8.10"
}
android {
namespace = "io.element.android.libraries.push.providers.unifiedpush"
}
anvil {
generateDaggerFactories.set(true)
}
dependencies {
implementation(libs.dagger)
implementation(projects.libraries.core)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.pushproviders.api)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.services.toolbox.api)
implementation(libs.serialization.json)
// UnifiedPush library
api(libs.unifiedpush)
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testImplementation(projects.libraries.matrix.test)
}

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<receiver
android:name=".VectorUnifiedPushMessagingReceiver"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedReceiver">
<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>
<receiver
android:name=".KeepInternalDistributor"
android:enabled="true"
android:exported="false">
<intent-filter>
<!--
This action is checked to track installed and uninstalled distributors.
We declare it to keep the background sync as an internal
unifiedpush distributor.
-->
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
</intent-filter>
</receiver>
</application>
</manifest>

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.core.asEventId
import io.element.android.libraries.matrix.api.core.asRoomId import io.element.android.libraries.matrix.api.core.asRoomId
import io.element.android.libraries.push.impl.push.PushData import io.element.android.libraries.push.providers.api.PushData
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush 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.push.impl.config.PushConfig
import org.unifiedpush.android.connector.UnifiedPush import org.unifiedpush.android.connector.UnifiedPush
import javax.inject.Inject import javax.inject.Inject
@ -37,11 +36,6 @@ class RegisterUnifiedPushUseCase @Inject constructor(
return RegisterUnifiedPushResult.Success return RegisterUnifiedPushResult.Success
} }
if (!PushConfig.allowExternalUnifiedPushDistributors) {
saveAndRegisterApp(context.packageName)
return RegisterUnifiedPushResult.Success
}
if (UnifiedPush.getDistributor(context).isNotEmpty()) { if (UnifiedPush.getDistributor(context).isNotEmpty()) {
registerApp() registerApp()
return RegisterUnifiedPushResult.Success return RegisterUnifiedPushResult.Success

View file

@ -0,0 +1,27 @@
/*
* 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
object UnifiedPushConfig {
/**
* It is the push gateway for UnifiedPush.
* Note: default_push_gateway_http_url should have path '/_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"
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,12 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl package io.element.android.libraries.push.providers.unifiedpush
import android.content.Context import android.content.Context
import io.element.android.libraries.androidutils.system.getApplicationLabel
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.push.impl.config.PushConfig
import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -31,8 +29,6 @@ import javax.inject.Inject
class UnifiedPushHelper @Inject constructor( class UnifiedPushHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val unifiedPushStore: UnifiedPushStore, private val unifiedPushStore: UnifiedPushStore,
// private val matrix: Matrix,
private val fcmHelper: FcmHelper,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
) { ) {
@ -95,11 +91,14 @@ class UnifiedPushHelper @Inject constructor(
// if we use the embedded distributor, // if we use the embedded distributor,
// register app_id type upfcm on sygnal // register app_id type upfcm on sygnal
// the pushkey if FCM key // the pushkey if FCM key
/*
if (UnifiedPush.getDistributor(context) == context.packageName) { if (UnifiedPush.getDistributor(context) == context.packageName) {
unifiedPushStore.storePushGateway(PushConfig.pusher_http_url) unifiedPushStore.storePushGateway(PushConfig.pusher_http_url)
onDoneRunnable?.run() onDoneRunnable?.run()
return return
} }
*/
/* TODO EAx UnifiedPush /* TODO EAx UnifiedPush
// else, unifiedpush, and pushkey is an endpoint // else, unifiedpush, and pushkey is an endpoint
val gateway = PushConfig.default_push_gateway_http_url val gateway = PushConfig.default_push_gateway_http_url
@ -132,19 +131,25 @@ class UnifiedPushHelper @Inject constructor(
} }
fun getCurrentDistributorName(): String { fun getCurrentDistributorName(): String {
TODO()
/*
return when { return when {
isEmbeddedDistributor() -> stringProvider.getString(R.string.push_distributor_firebase_android) isEmbeddedDistributor() -> stringProvider.getString(R.string.push_distributor_firebase_android)
isBackgroundSync() -> stringProvider.getString(R.string.push_distributor_background_sync_android) isBackgroundSync() -> stringProvider.getString(R.string.push_distributor_background_sync_android)
else -> context.getApplicationLabel(UnifiedPush.getDistributor(context)) else -> context.getApplicationLabel(UnifiedPush.getDistributor(context))
} }
*/
} }
fun isEmbeddedDistributor(): Boolean { fun isEmbeddedDistributor(): Boolean {
return isInternalDistributor() && fcmHelper.isFirebaseAvailable() TODO()
//return isInternalDistributor() && fcmHelper.isFirebaseAvailable()
} }
fun isBackgroundSync(): Boolean { fun isBackgroundSync(): Boolean {
return isInternalDistributor() && !fcmHelper.isFirebaseAvailable() TODO()
//return isInternalDistributor() && !fcmHelper.isFirebaseAvailable()
} }
private fun isInternalDistributor(): Boolean { private fun isInternalDistributor(): Boolean {
@ -168,12 +173,13 @@ class UnifiedPushHelper @Inject constructor(
} }
fun getEndpointOrToken(): String? { fun getEndpointOrToken(): String? {
return if (isEmbeddedDistributor()) fcmHelper.getFcmToken() // TODO
return if (isEmbeddedDistributor()) "" // fcmHelper.getFcmToken()
else unifiedPushStore.getEndpoint() else unifiedPushStore.getEndpoint()
} }
fun getPushGateway(): String? { fun getPushGateway(): String? {
return if (isEmbeddedDistributor()) PushConfig.pusher_http_url return if (isEmbeddedDistributor()) "" // PushConfig.pusher_http_url
else unifiedPushStore.getPushGateway() else unifiedPushStore.getPushGateway()
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.push.impl.push.PushData import io.element.android.libraries.push.providers.api.PushData
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import javax.inject.Inject import javax.inject.Inject

View file

@ -0,0 +1,31 @@
/*
* 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.matrix.api.MatrixClient
import io.element.android.libraries.push.providers.api.PushProvider
import javax.inject.Inject
class UnifiedPushProvider @Inject constructor(): PushProvider {
override fun getDistributorNames(): List<String> {
TODO("Not yet implemented")
}
override suspend fun registerWith(matrixClient: MatrixClient, distributorName: String) {
TODO("Not yet implemented")
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl package io.element.android.libraries.push.providers.unifiedpush
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences

View file

@ -14,19 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import android.content.Context
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.push.api.model.BackgroundSyncMode
import io.element.android.libraries.push.api.store.PushDataStore
import io.element.android.libraries.push.impl.PushersManager
import io.element.android.libraries.push.impl.UnifiedPushHelper
import io.element.android.libraries.push.impl.UnifiedPushStore
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,
@ -50,3 +40,4 @@ class UnregisterUnifiedPushUseCase @Inject constructor(
UnifiedPush.unregisterApp(context) UnifiedPush.unregisterApp(context)
} }
} }
*/

View file

@ -14,40 +14,29 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.widget.Toast
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.api.model.BackgroundSyncMode import io.element.android.libraries.push.providers.api.PushHandler
import io.element.android.libraries.push.api.store.PushDataStore
import io.element.android.libraries.push.impl.PushersManager
import io.element.android.libraries.push.impl.UnifiedPushHelper
import io.element.android.libraries.push.impl.UnifiedPushStore
import io.element.android.libraries.push.impl.log.pushLoggerTag
import io.element.android.libraries.push.impl.push.PushHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.unifiedpush.android.connector.MessagingReceiver import org.unifiedpush.android.connector.MessagingReceiver
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("Unified", pushLoggerTag) private val loggerTag = LoggerTag("VectorUnifiedPushMessagingReceiver")
class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
@Inject lateinit var pushersManager: PushersManager
@Inject lateinit var pushParser: UnifiedPushParser @Inject lateinit var pushParser: UnifiedPushParser
// @Inject lateinit var pushDataStore: PushDataStore
//@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@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
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
@ -77,6 +66,8 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
} }
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 (pushDataStore.areNotificationEnabledForDevice() /* TODO EAx && activeSessionHolder.hasActiveSession() */) {
// If the endpoint has changed // If the endpoint has changed
@ -99,16 +90,22 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
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()
*/
} }
override fun onRegistrationFailed(context: Context, instance: String) { override fun onRegistrationFailed(context: Context, instance: String) {
TODO()
/*
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
pushDataStore.setFdroidSyncBackgroundMode(mode) pushDataStore.setFdroidSyncBackgroundMode(mode)
guardServiceStarter.start() guardServiceStarter.start()
*/
} }
override fun onUnregistered(context: Context, instance: String) { override fun onUnregistered(context: Context, instance: String) {
TODO()
/*
Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered") 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)
@ -120,5 +117,6 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
Timber.tag(loggerTag.value).d("Probably unregistering a non existing pusher") Timber.tag(loggerTag.value).d("Probably unregistering a non existing pusher")
} }
} }
*/
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import com.squareup.anvil.annotations.ContributesTo import com.squareup.anvil.annotations.ContributesTo
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope

View file

@ -0,0 +1,33 @@
/*
* 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.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.push.providers.unifiedpush.UnifiedPushProvider
@Module
@ContributesTo(AppScope::class)
interface UnifiedPushModule {
@Binds
@IntoSet
fun bind(pushProvider: UnifiedPushProvider): PushProvider
}

View file

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.unifiedpush package io.element.android.libraries.push.providers.unifiedpush
import com.google.common.truth.Truth.assertThat 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.impl.push.PushData import io.element.android.libraries.push.providers.api.PushData
import org.junit.Test import org.junit.Test
class UnifiedPushParserTest { class UnifiedPushParserTest {

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.pushstore.api"
}
dependencies {
}

View file

@ -14,10 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.userpushstore package io.element.android.libraries.pushstore.api
const val NOTIFICATION_METHOD_FIREBASE = "NOTIFICATION_METHOD_FIREBASE"
const val NOTIFICATION_METHOD_UNIFIEDPUSH = "NOTIFICATION_METHOD_UNIFIEDPUSH"
/** /**
* Store data related to push about a user. * Store data related to push about a user.
@ -26,7 +23,7 @@ interface UserPushStore {
/** /**
* [NOTIFICATION_METHOD_FIREBASE] or [NOTIFICATION_METHOD_UNIFIEDPUSH]. * [NOTIFICATION_METHOD_FIREBASE] or [NOTIFICATION_METHOD_UNIFIEDPUSH].
*/ */
suspend fun getNotificationMethod(): String suspend fun getNotificationMethod(): String?
suspend fun setNotificationMethod(value: String) suspend fun setNotificationMethod(value: String)
@ -36,5 +33,3 @@ interface UserPushStore {
suspend fun reset() suspend fun reset()
} }
suspend fun UserPushStore.isFirebase(): Boolean = getNotificationMethod() == NOTIFICATION_METHOD_FIREBASE

View file

@ -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.pushstore.api
/**
* Store data related to push about a user.
*/
interface UserPushStoreFactory {
fun create(userId: String): UserPushStore
}

View file

@ -0,0 +1,38 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
}
android {
namespace = "io.element.android.libraries.push.pushstore.impl"
}
anvil {
generateDaggerFactories.set(true)
}
dependencies {
implementation(libs.dagger)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.pushstore.api)
implementation(projects.libraries.sessionStorage.api)
implementation(libs.androidx.corektx)
implementation(libs.androidx.datastore.preferences)
}

View file

@ -14,28 +14,32 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.userpushstore package io.element.android.libraries.pushstore.impl
import android.content.Context import android.content.Context
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.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.pushstore.api.UserPushStore
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionListener
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import javax.inject.Inject import javax.inject.Inject
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
class UserPushStoreFactory @Inject constructor( @ContributesBinding(AppScope::class, boundType = UserPushStoreFactory::class)
class DefaultUserPushStoreFactory @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val sessionObserver: SessionObserver, private val sessionObserver: SessionObserver,
) : SessionListener { ) : UserPushStoreFactory, SessionListener {
init { init {
observeSessions() observeSessions()
} }
// We can have only one class accessing a single data store, so keep a cache of them. // We can have only one class accessing a single data store, so keep a cache of them.
private val cache = mutableMapOf<String, UserPushStore>() private val cache = mutableMapOf<String, UserPushStore>()
fun create(userId: String): UserPushStore { override fun create(userId: String): UserPushStore {
return cache.getOrPut(userId) { return cache.getOrPut(userId) {
UserPushStoreDataStore( UserPushStoreDataStore(
context = context, context = context,

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.push.impl.userpushstore package io.element.android.libraries.pushstore.impl
import android.content.Context import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
@ -22,6 +22,7 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import io.element.android.libraries.pushstore.api.UserPushStore
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
/** /**
@ -35,8 +36,8 @@ class UserPushStoreDataStore(
private val notificationMethod = stringPreferencesKey("notificationMethod") private val notificationMethod = stringPreferencesKey("notificationMethod")
private val currentPushKey = stringPreferencesKey("currentPushKey") private val currentPushKey = stringPreferencesKey("currentPushKey")
override suspend fun getNotificationMethod(): String { override suspend fun getNotificationMethod(): String? {
return context.dataStore.data.first()[notificationMethod] ?: NOTIFICATION_METHOD_FIREBASE return context.dataStore.data.first()[notificationMethod]
} }
override suspend fun setNotificationMethod(value: String) { override suspend fun setNotificationMethod(value: String) {

View file

@ -75,6 +75,12 @@ fun DependencyHandlerScope.allLibrariesImpl() {
implementation(project(":libraries:core")) implementation(project(":libraries:core"))
implementation(project(":libraries:permissions:impl")) implementation(project(":libraries:permissions:impl"))
implementation(project(":libraries:push:impl")) implementation(project(":libraries:push:impl"))
implementation(project(":libraries:push:impl"))
// Comment to not include firebase in the project
implementation(project(":libraries:pushproviders:firebase"))
// Comment to not include unified push in the project
// implementation(project(":libraries:pushproviders:unifiedpush"))
implementation(project(":libraries:pushstore:impl"))
implementation(project(":libraries:architecture")) implementation(project(":libraries:architecture"))
implementation(project(":libraries:dateformatter:impl")) implementation(project(":libraries:dateformatter:impl"))
implementation(project(":libraries:di")) implementation(project(":libraries:di"))