Benoit Marty
1 year ago
committed by
GitHub
119 changed files with 1802 additions and 1037 deletions
@ -1,49 +0,0 @@
@@ -1,49 +0,0 @@
|
||||
{ |
||||
"project_info": { |
||||
"project_number": "912726360885", |
||||
"firebase_url": "https://vector-alpha.firebaseio.com", |
||||
"project_id": "vector-alpha", |
||||
"storage_bucket": "vector-alpha.appspot.com" |
||||
}, |
||||
|
||||
"client": [ |
||||
{ |
||||
"client_info": { |
||||
"mobilesdk_app_id": "1:912726360885:android:def0a4e454042e9b00427c", |
||||
"android_client_info": { |
||||
"package_name": "io.element.android.x.debug" |
||||
} |
||||
}, |
||||
"oauth_client": [ |
||||
{ |
||||
"client_id": "912726360885-hvgoj23p6plt7hikhtdrakihojghaftv.apps.googleusercontent.com", |
||||
"client_type": 1, |
||||
"android_info": { |
||||
"package_name": "io.element.android.x.debug", |
||||
"certificate_hash": "41bd63b3b612a15d9ba36a5245c393f2a9b992d1" |
||||
} |
||||
}, |
||||
{ |
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", |
||||
"client_type": 3 |
||||
} |
||||
], |
||||
"api_key": [ |
||||
{ |
||||
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c" |
||||
} |
||||
], |
||||
"services": { |
||||
"appinvite_service": { |
||||
"other_platform_oauth_client": [ |
||||
{ |
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", |
||||
"client_type": 3 |
||||
} |
||||
] |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"configuration_version": "1" |
||||
} |
@ -1,40 +0,0 @@
@@ -1,40 +0,0 @@
|
||||
{ |
||||
"project_info": { |
||||
"project_number": "912726360885", |
||||
"firebase_url": "https://vector-alpha.firebaseio.com", |
||||
"project_id": "vector-alpha", |
||||
"storage_bucket": "vector-alpha.appspot.com" |
||||
}, |
||||
"client": [ |
||||
{ |
||||
"client_info": { |
||||
"mobilesdk_app_id": "1:912726360885:android:e17435e0beb0303000427c", |
||||
"android_client_info": { |
||||
"package_name": "io.element.android.x.nightly" |
||||
} |
||||
}, |
||||
"oauth_client": [ |
||||
{ |
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", |
||||
"client_type": 3 |
||||
} |
||||
], |
||||
"api_key": [ |
||||
{ |
||||
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c" |
||||
} |
||||
], |
||||
"services": { |
||||
"appinvite_service": { |
||||
"other_platform_oauth_client": [ |
||||
{ |
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", |
||||
"client_type": 3 |
||||
} |
||||
] |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"configuration_version": "1" |
||||
} |
@ -1,40 +0,0 @@
@@ -1,40 +0,0 @@
|
||||
{ |
||||
"project_info": { |
||||
"project_number": "912726360885", |
||||
"firebase_url": "https://vector-alpha.firebaseio.com", |
||||
"project_id": "vector-alpha", |
||||
"storage_bucket": "vector-alpha.appspot.com" |
||||
}, |
||||
"client": [ |
||||
{ |
||||
"client_info": { |
||||
"mobilesdk_app_id": "1:912726360885:android:d097de99a4c23d2700427c", |
||||
"android_client_info": { |
||||
"package_name": "io.element.android.x" |
||||
} |
||||
}, |
||||
"oauth_client": [ |
||||
{ |
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", |
||||
"client_type": 3 |
||||
} |
||||
], |
||||
"api_key": [ |
||||
{ |
||||
"current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c" |
||||
} |
||||
], |
||||
"services": { |
||||
"appinvite_service": { |
||||
"other_platform_oauth_client": [ |
||||
{ |
||||
"client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", |
||||
"client_type": 3 |
||||
} |
||||
] |
||||
} |
||||
} |
||||
} |
||||
], |
||||
"configuration_version": "1" |
||||
} |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> |
||||
<string name="error_no_compatible_app_found">"No compatible app was found to handle this action."</string> |
||||
</resources> |
@ -1,48 +0,0 @@
@@ -1,48 +0,0 @@
|
||||
/* |
||||
* 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.api.model |
||||
|
||||
/** |
||||
* Different strategies for Background sync, only applicable to F-Droid version of the app. |
||||
*/ |
||||
enum class BackgroundSyncMode { |
||||
/** |
||||
* In this mode background syncs are scheduled via Workers, meaning that the system will have control on the periodicity |
||||
* of syncs when battery is low or when the phone is idle (sync will occur in allowed maintenance windows). After completion |
||||
* the sync work will schedule another one. |
||||
*/ |
||||
FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY, |
||||
|
||||
/** |
||||
* This mode requires the app to be exempted from battery optimization. Alarms will be launched and will wake up the app |
||||
* in order to perform the background sync as a foreground service. After completion the service will schedule another alarm |
||||
*/ |
||||
FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME, |
||||
|
||||
/** |
||||
* The app won't sync in background. |
||||
*/ |
||||
FDROID_BACKGROUND_SYNC_MODE_DISABLED; |
||||
|
||||
companion object { |
||||
const val DEFAULT_SYNC_DELAY_SECONDS = 60 |
||||
const val DEFAULT_SYNC_TIMEOUT_SECONDS = 6 |
||||
|
||||
fun fromString(value: String?): BackgroundSyncMode = values().firstOrNull { it.name == value } |
||||
?: FDROID_BACKGROUND_SYNC_MODE_DISABLED |
||||
} |
||||
} |
@ -1,49 +0,0 @@
@@ -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) |
||||
*/ |
||||
} |
@ -1,104 +0,0 @@
@@ -1,104 +0,0 @@
|
||||
/* |
||||
* 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.impl |
||||
|
||||
import android.content.Context |
||||
import android.content.SharedPreferences |
||||
import android.widget.Toast |
||||
import androidx.core.content.edit |
||||
import com.google.android.gms.common.ConnectionResult |
||||
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.DefaultPreferences |
||||
import kotlinx.coroutines.runBlocking |
||||
import timber.log.Timber |
||||
import javax.inject.Inject |
||||
|
||||
/** |
||||
* This class store the FCM token in SharedPrefs and ensure this token is retrieved. |
||||
* It has an alter ego in the fdroid variant. |
||||
*/ |
||||
@ContributesBinding(AppScope::class) |
||||
class GoogleFcmHelper @Inject constructor( |
||||
@ApplicationContext private val context: Context, |
||||
@DefaultPreferences private val sharedPrefs: SharedPreferences, |
||||
) : FcmHelper { |
||||
override fun isFirebaseAvailable(): Boolean = true |
||||
|
||||
override fun getFcmToken(): String? { |
||||
return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) |
||||
} |
||||
|
||||
override fun storeFcmToken(token: String?) { |
||||
sharedPrefs.edit { |
||||
putString(PREFS_KEY_FCM_TOKEN, token) |
||||
} |
||||
} |
||||
|
||||
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' |
||||
if (checkPlayServices(context)) { |
||||
try { |
||||
FirebaseMessaging.getInstance().token |
||||
.addOnSuccessListener { token -> |
||||
storeFcmToken(token) |
||||
if (registerPusher) { |
||||
runBlocking {// TODO |
||||
pushersManager.enqueueRegisterPusherWithFcmKey(token) |
||||
} |
||||
} |
||||
} |
||||
.addOnFailureListener { e -> |
||||
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") |
||||
} |
||||
} catch (e: Throwable) { |
||||
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") |
||||
} |
||||
} else { |
||||
Toast.makeText(context, R.string.push_no_valid_google_play_services_apk_android, Toast.LENGTH_SHORT).show() |
||||
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 |
||||
* it doesn't, display a dialog that allows users to download the APK from |
||||
* the Google Play Store or enable it in the device's system settings. |
||||
*/ |
||||
private fun checkPlayServices(context: Context): Boolean { |
||||
val apiAvailability = GoogleApiAvailability.getInstance() |
||||
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) |
||||
return resultCode == ConnectionResult.SUCCESS |
||||
} |
||||
|
||||
/* |
||||
override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { |
||||
// No op |
||||
} |
||||
|
||||
override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { |
||||
// No op |
||||
} |
||||
*/ |
||||
|
||||
companion object { |
||||
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" |
||||
} |
||||
} |
@ -1,179 +0,0 @@
@@ -1,179 +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 |
||||
|
||||
import android.content.Context |
||||
import io.element.android.libraries.androidutils.system.getApplicationLabel |
||||
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 kotlinx.serialization.SerialName |
||||
import kotlinx.serialization.Serializable |
||||
import org.unifiedpush.android.connector.UnifiedPush |
||||
import timber.log.Timber |
||||
import java.net.URL |
||||
import javax.inject.Inject |
||||
|
||||
class UnifiedPushHelper @Inject constructor( |
||||
@ApplicationContext private val context: Context, |
||||
private val unifiedPushStore: UnifiedPushStore, |
||||
// private val matrix: Matrix, |
||||
private val fcmHelper: FcmHelper, |
||||
private val stringProvider: StringProvider, |
||||
) { |
||||
|
||||
/* TODO EAx |
||||
@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 custom = "${parsed.protocol}://${parsed.host}/_matrix/push/v1/notify" |
||||
Timber.i("Testing $custom") |
||||
try { |
||||
val response = matrix.rawService().getUrl(custom, CacheStrategy.NoCache) |
||||
tryOrNull { Json.decodeFromString<DiscoveryResponse>(response) } |
||||
?.let { discoveryResponse -> |
||||
if (discoveryResponse.unifiedpush.gateway == "matrix") { |
||||
Timber.d("Using custom gateway") |
||||
unifiedPushStore.storePushGateway(custom) |
||||
onDoneRunnable?.run() |
||||
return |
||||
} |
||||
} |
||||
} catch (e: Throwable) { |
||||
Timber.d(e, "Cannot try custom gateway") |
||||
} |
||||
unifiedPushStore.storePushGateway(gateway) |
||||
onDoneRunnable?.run() |
||||
|
||||
*/ |
||||
} |
||||
|
||||
fun getExternalDistributors(): List<String> { |
||||
return UnifiedPush.getDistributors(context) |
||||
.filterNot { it == context.packageName } |
||||
} |
||||
|
||||
fun getCurrentDistributorName(): String { |
||||
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 { |
||||
return isInternalDistributor() && fcmHelper.isFirebaseAvailable() |
||||
} |
||||
|
||||
fun isBackgroundSync(): Boolean { |
||||
return isInternalDistributor() && !fcmHelper.isFirebaseAvailable() |
||||
} |
||||
|
||||
private fun isInternalDistributor(): Boolean { |
||||
return UnifiedPush.getDistributor(context).isEmpty() || |
||||
UnifiedPush.getDistributor(context) == context.packageName |
||||
} |
||||
|
||||
fun getPrivacyFriendlyUpEndpoint(): String? { |
||||
val endpoint = getEndpointOrToken() |
||||
if (endpoint.isNullOrEmpty()) return null |
||||
if (isEmbeddedDistributor()) { |
||||
return endpoint |
||||
} |
||||
return try { |
||||
val parsed = URL(endpoint) |
||||
"${parsed.protocol}://${parsed.host}/***" |
||||
} catch (e: Exception) { |
||||
Timber.e(e, "Error parsing unifiedpush endpoint") |
||||
null |
||||
} |
||||
} |
||||
|
||||
fun getEndpointOrToken(): String? { |
||||
return if (isEmbeddedDistributor()) fcmHelper.getFcmToken() |
||||
else unifiedPushStore.getEndpoint() |
||||
} |
||||
|
||||
fun getPushGateway(): String? { |
||||
return if (isEmbeddedDistributor()) PushConfig.pusher_http_url |
||||
else unifiedPushStore.getPushGateway() |
||||
} |
||||
} |
@ -0,0 +1,28 @@
@@ -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) |
||||
} |
@ -0,0 +1,22 @@
@@ -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, |
||||
) |
@ -0,0 +1,21 @@
@@ -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) |
||||
} |
@ -0,0 +1,51 @@
@@ -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.api |
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient |
||||
|
||||
/** |
||||
* This is the main API for this module. |
||||
*/ |
||||
interface PushProvider { |
||||
/** |
||||
* Allow to sort providers, from lower index to higher index. |
||||
*/ |
||||
val index: Int |
||||
|
||||
/** |
||||
* User friendly name. |
||||
*/ |
||||
val name: String |
||||
|
||||
fun getDistributors(): List<Distributor> |
||||
|
||||
/** |
||||
* Register the pusher to the homeserver. |
||||
*/ |
||||
suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor) |
||||
|
||||
/** |
||||
* Unregister the pusher. |
||||
*/ |
||||
suspend fun unregister(matrixClient: MatrixClient) |
||||
|
||||
/** |
||||
* Attempt to troubleshoot the push provider. |
||||
*/ |
||||
suspend fun troubleshoot(): Result<Unit> |
||||
} |
@ -0,0 +1,24 @@
@@ -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.api |
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient |
||||
|
||||
interface PusherSubscriber { |
||||
suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) |
||||
suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
# Firebase |
||||
|
||||
## Configuration |
||||
|
||||
In order to make this module only know about Firebase, the plugin `com.google.gms.google-services` has been disabled from the `app` module. |
||||
|
||||
To be able to change the values in the file `firebase.xml` from this module, you should enable the plugin `com.google.gms.google-services` again, copy the file `google-services.json` to the folder `/app/src/main`, build the project, and check the generated file `app/build/generated/res/google-services/<buildtype>/values/values.xml` to import the generated values into the `firebase.xml` files. |
@ -0,0 +1,48 @@
@@ -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) |
||||
} |
||||
|
||||
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) |
||||
|
||||
api(platform(libs.google.firebase.bom)) |
||||
api("com.google.firebase:firebase-messaging-ktx") |
||||
|
||||
testImplementation(libs.test.junit) |
||||
testImplementation(libs.test.truth) |
||||
testImplementation(projects.libraries.matrix.test) |
||||
testImplementation(projects.tests.testutils) |
||||
} |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="google_app_id" translatable="false">1:912726360885:android:def0a4e454042e9b00427c</string> |
||||
</resources> |
@ -0,0 +1,31 @@
@@ -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> |
@ -0,0 +1,28 @@
@@ -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. |
||||
*/ |
||||
|
||||
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 index = 0 |
||||
const val name = "Firebase" |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
/* |
||||
* 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.asSessionId |
||||
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("FirebaseNewTokenHandler") |
||||
|
||||
/** |
||||
* Handle new token receive from Firebase. Will update all the sessions which are using Firebase as a push provider. |
||||
*/ |
||||
class FirebaseNewTokenHandler @Inject constructor( |
||||
private val pusherSubscriber: PusherSubscriber, |
||||
private val sessionStore: SessionStore, |
||||
private val userPushStoreFactory: UserPushStoreFactory, |
||||
private val matrixAuthenticationService: MatrixAuthenticationService, |
||||
private val firebaseStore: FirebaseStore, |
||||
) { |
||||
suspend fun handle(firebaseToken: String) { |
||||
firebaseStore.storeFcmToken(firebaseToken) |
||||
// Register the pusher for all the sessions |
||||
sessionStore.getAllSessions().toUserList() |
||||
.mapNotNull { it.asSessionId() } |
||||
.forEach { userId -> |
||||
val userDataStore = userPushStoreFactory.create(userId) |
||||
if (userDataStore.getPushProviderName() == FirebaseConfig.name) { |
||||
matrixAuthenticationService.restoreSession(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") |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
/* |
||||
* 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.Distributor |
||||
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 firebaseStore: FirebaseStore, |
||||
private val firebaseTroubleshooter: FirebaseTroubleshooter, |
||||
private val pusherSubscriber: PusherSubscriber, |
||||
) : PushProvider { |
||||
override val index = FirebaseConfig.index |
||||
override val name = FirebaseConfig.name |
||||
|
||||
override fun getDistributors(): List<Distributor> { |
||||
return listOf(Distributor("Firebase", "Firebase")) |
||||
} |
||||
|
||||
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor) { |
||||
val pushKey = firebaseStore.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) |
||||
} |
||||
|
||||
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> { |
||||
return firebaseTroubleshooter.troubleshoot() |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* 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 android.content.SharedPreferences |
||||
import androidx.core.content.edit |
||||
import io.element.android.libraries.di.DefaultPreferences |
||||
import javax.inject.Inject |
||||
|
||||
/** |
||||
* This class store the Firebase token in SharedPrefs. |
||||
*/ |
||||
class FirebaseStore @Inject constructor( |
||||
@DefaultPreferences private val sharedPrefs: SharedPreferences, |
||||
) { |
||||
fun getFcmToken(): String? { |
||||
return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) |
||||
} |
||||
|
||||
fun storeFcmToken(token: String?) { |
||||
sharedPrefs.edit { |
||||
putString(PREFS_KEY_FCM_TOKEN, token) |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN" |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
/* |
||||
* 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 android.content.Context |
||||
import com.google.android.gms.common.ConnectionResult |
||||
import com.google.android.gms.common.GoogleApiAvailability |
||||
import com.google.firebase.messaging.FirebaseMessaging |
||||
import io.element.android.libraries.di.ApplicationContext |
||||
import timber.log.Timber |
||||
import javax.inject.Inject |
||||
import kotlin.coroutines.resume |
||||
import kotlin.coroutines.resumeWithException |
||||
import kotlin.coroutines.suspendCoroutine |
||||
|
||||
/** |
||||
* This class force retrieving and storage of the Firebase token. |
||||
*/ |
||||
class FirebaseTroubleshooter @Inject constructor( |
||||
@ApplicationContext private val context: Context, |
||||
private val newTokenHandler: FirebaseNewTokenHandler, |
||||
) { |
||||
suspend fun troubleshoot(): Result<Unit> { |
||||
return runCatching { |
||||
val token = retrievedFirebaseToken() |
||||
newTokenHandler.handle(token) |
||||
} |
||||
} |
||||
|
||||
private suspend fun retrievedFirebaseToken(): String { |
||||
return suspendCoroutine { continuation -> |
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' |
||||
if (checkPlayServices(context)) { |
||||
try { |
||||
FirebaseMessaging.getInstance().token |
||||
.addOnSuccessListener { token -> |
||||
continuation.resume(token) |
||||
} |
||||
.addOnFailureListener { e -> |
||||
Timber.e(e, "## retrievedFirebaseToken() : failed") |
||||
continuation.resumeWithException(e) |
||||
} |
||||
} catch (e: Throwable) { |
||||
Timber.e(e, "## retrievedFirebaseToken() : failed") |
||||
continuation.resumeWithException(e) |
||||
} |
||||
} else { |
||||
val e = Exception("No valid Google Play Services found. Cannot use FCM.") |
||||
Timber.e(e) |
||||
continuation.resumeWithException(e) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Check the device to make sure it has the Google Play Services APK. If |
||||
* it doesn't, display a dialog that allows users to download the APK from |
||||
* the Google Play Store or enable it in the device's system settings. |
||||
*/ |
||||
private fun checkPlayServices(context: Context): Boolean { |
||||
val apiAvailability = GoogleApiAvailability.getInstance() |
||||
val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) |
||||
return resultCode == ConnectionResult.SUCCESS |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -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 |
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="default_web_client_id" translatable="false">912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com</string> |
||||
<string name="firebase_database_url" translatable="false">https://vector-alpha.firebaseio.com</string> |
||||
<string name="gcm_defaultSenderId" translatable="false">912726360885</string> |
||||
<string name="google_api_key" translatable="false">AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c</string> |
||||
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c</string> |
||||
<string name="google_storage_bucket" translatable="false">vector-alpha.appspot.com</string> |
||||
<string name="project_id" translatable="false">vector-alpha</string> |
||||
</resources> |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="google_app_id" translatable="false">1:912726360885:android:e17435e0beb0303000427c</string> |
||||
</resources> |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="google_app_id" translatable="false">1:912726360885:android:d097de99a4c23d2700427c</string> |
||||
</resources> |
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
/* |
||||
* 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 com.google.common.truth.Truth.assertThat |
||||
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.push.providers.api.PushData |
||||
import io.element.android.tests.testutils.assertNullOrThrow |
||||
import org.junit.Test |
||||
|
||||
class FirebasePushParserTest { |
||||
private val validData = PushData( |
||||
eventId = AN_EVENT_ID, |
||||
roomId = A_ROOM_ID, |
||||
unread = 1, |
||||
clientSecret = "a-secret" |
||||
) |
||||
|
||||
@Test |
||||
fun `test edge cases Firebase`() { |
||||
val pushParser = FirebasePushParser() |
||||
// Empty Json |
||||
assertThat(pushParser.parse(emptyMap())).isNull() |
||||
// Bad Json |
||||
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("unread", "str"))).isEqualTo(validData.copy(unread = null)) |
||||
// Extra data |
||||
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("extra", "5"))).isEqualTo(validData) |
||||
} |
||||
|
||||
@Test |
||||
fun `test Firebase format`() { |
||||
val pushParser = FirebasePushParser() |
||||
assertThat(pushParser.parse(FIREBASE_PUSH_DATA)).isEqualTo(validData) |
||||
} |
||||
|
||||
@Test |
||||
fun `test empty roomId`() { |
||||
val pushParser = FirebasePushParser() |
||||
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("room_id", null))).isNull() |
||||
assertNullOrThrow { pushParser.parse(FIREBASE_PUSH_DATA.mutate("room_id", "")) } |
||||
} |
||||
|
||||
@Test |
||||
fun `test invalid roomId`() { |
||||
val pushParser = FirebasePushParser() |
||||
assertNullOrThrow { pushParser.parse(FIREBASE_PUSH_DATA.mutate("room_id", "aRoomId:domain")) } |
||||
} |
||||
|
||||
@Test |
||||
fun `test empty eventId`() { |
||||
val pushParser = FirebasePushParser() |
||||
assertThat(pushParser.parse(FIREBASE_PUSH_DATA.mutate("event_id", null))).isNull() |
||||
assertNullOrThrow { pushParser.parse(FIREBASE_PUSH_DATA.mutate("event_id", "")) } |
||||
} |
||||
|
||||
@Test |
||||
fun `test invalid eventId`() { |
||||
val pushParser = FirebasePushParser() |
||||
assertNullOrThrow { pushParser.parse(FIREBASE_PUSH_DATA.mutate("event_id", "anEventId")) } |
||||
} |
||||
|
||||
companion object { |
||||
private val FIREBASE_PUSH_DATA = mapOf( |
||||
"event_id" to AN_EVENT_ID.value, |
||||
"room_id" to A_ROOM_ID.value, |
||||
"unread" to "1", |
||||
"prio" to "high", |
||||
"cs" to "a-secret", |
||||
) |
||||
} |
||||
} |
||||
|
||||
private fun Map<String, String?>.mutate(key: String, value: String?): Map<String, String?> { |
||||
return toMutableMap().apply { put(key, value) } |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
/* |
||||
* 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.androidutils) |
||||
implementation(projects.libraries.core) |
||||
implementation(projects.libraries.matrix.api) |
||||
|
||||
implementation(projects.libraries.pushstore.api) |
||||
implementation(projects.libraries.pushproviders.api) |
||||
implementation(projects.libraries.architecture) |
||||
implementation(projects.libraries.core) |
||||
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) |
||||
|
||||
// UnifiedPush library |
||||
api(libs.unifiedpush) |
||||
|
||||
testImplementation(libs.test.junit) |
||||
testImplementation(libs.test.truth) |
||||
testImplementation(projects.libraries.matrix.test) |
||||
testImplementation(projects.tests.testutils) |
||||
} |
@ -0,0 +1,47 @@
@@ -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> |
@ -0,0 +1,28 @@
@@ -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. |
||||
*/ |
||||
|
||||
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 index = 1 |
||||
const val name = "UnifiedPush" |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
/* |
||||
* 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.coroutine.CoroutineDispatchers |
||||
import io.element.android.libraries.network.RetrofitFactory |
||||
import io.element.android.libraries.push.providers.unifiedpush.network.UnifiedPushApi |
||||
import kotlinx.coroutines.withContext |
||||
import timber.log.Timber |
||||
import java.net.URL |
||||
import javax.inject.Inject |
||||
|
||||
class UnifiedPushGatewayResolver @Inject constructor( |
||||
private val retrofitFactory: RetrofitFactory, |
||||
private val coroutineDispatchers: CoroutineDispatchers, |
||||
) { |
||||
suspend fun getGateway(endpoint: String): String? { |
||||
val gateway = UnifiedPushConfig.default_push_gateway_http_url |
||||
val url = URL(endpoint) |
||||
val custom = "${url.protocol}://${url.host}/_matrix/push/v1/notify" |
||||
Timber.i("Testing $custom") |
||||
try { |
||||
return withContext(coroutineDispatchers.io) { |
||||
val api = retrofitFactory.create("${url.protocol}://${url.host}") |
||||
.create(UnifiedPushApi::class.java) |
||||
try { |
||||
val discoveryResponse = api.discover() |
||||
if (discoveryResponse.unifiedpush.gateway == "matrix") { |
||||
Timber.d("Using custom gateway") |
||||
return@withContext custom |
||||
} |
||||
} catch (throwable: Throwable) { |
||||
Timber.tag("UnifiedPushHelper").e(throwable) |
||||
} |
||||
return@withContext gateway |
||||
} |
||||
} catch (e: Throwable) { |
||||
Timber.d(e, "Cannot try custom gateway") |
||||
} |
||||
return gateway |
||||
} |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/* |
||||
* 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.push.providers.api.PusherSubscriber |
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory |
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret |
||||
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 userPushStoreFactory: UserPushStoreFactory, |
||||
private val pushClientSecret: PushClientSecret, |
||||
private val matrixAuthenticationService: MatrixAuthenticationService, |
||||
) { |
||||
suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String) { |
||||
// Register the pusher for the session with this client secret, if is it using UnifiedPush. |
||||
val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Unit.also { |
||||
Timber.w("Unable to retrieve session") |
||||
} |
||||
val userDataStore = userPushStoreFactory.create(userId) |
||||
if (userDataStore.getPushProviderName() == UnifiedPushConfig.name) { |
||||
matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> |
||||
pusherSubscriber.registerPusher(client, endpoint, pushGateway) |
||||
} |
||||
} else { |
||||
Timber.tag(loggerTag.value).d("This session is not using UnifiedPush pusher") |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
/* |
||||
* 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 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.push.providers.api.Distributor |
||||
import io.element.android.libraries.push.providers.api.PushProvider |
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret |
||||
import org.unifiedpush.android.connector.UnifiedPush |
||||
import javax.inject.Inject |
||||
|
||||
class UnifiedPushProvider @Inject constructor( |
||||
@ApplicationContext private val context: Context, |
||||
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, |
||||
private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, |
||||
private val pushClientSecret: PushClientSecret, |
||||
) : PushProvider { |
||||
override val index = UnifiedPushConfig.index |
||||
override val name = UnifiedPushConfig.name |
||||
|
||||
override fun getDistributors(): List<Distributor> { |
||||
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, distributor: Distributor) { |
||||
val clientSecret = pushClientSecret.getSecretForUser(matrixClient.sessionId) |
||||
registerUnifiedPushUseCase.execute(matrixClient, distributor, clientSecret) |
||||
} |
||||
|
||||
override suspend fun unregister(matrixClient: MatrixClient) { |
||||
val clientSecret = pushClientSecret.getSecretForUser(matrixClient.sessionId) |
||||
unRegisterUnifiedPushUseCase.execute(clientSecret) |
||||
} |
||||
|
||||
override suspend fun troubleshoot(): Result<Unit> { |
||||
TODO("Not yet implemented") |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -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 |
||||
} |
@ -0,0 +1,25 @@
@@ -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 @@
@@ -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 @@
@@ -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 |
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
/* |
||||
* 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 com.google.common.truth.Truth.assertThat |
||||
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.push.providers.api.PushData |
||||
import io.element.android.tests.testutils.assertNullOrThrow |
||||
import org.junit.Test |
||||
|
||||
class UnifiedPushParserTest { |
||||
private val aClientSecret = "a-client-secret" |
||||
private val validData = PushData( |
||||
eventId = AN_EVENT_ID, |
||||
roomId = A_ROOM_ID, |
||||
unread = 1, |
||||
clientSecret = aClientSecret |
||||
) |
||||
|
||||
@Test |
||||
fun `test edge cases UnifiedPush`() { |
||||
val pushParser = UnifiedPushParser() |
||||
// Empty string |
||||
assertThat(pushParser.parse("".toByteArray(), aClientSecret)).isNull() |
||||
// Empty Json |
||||
assertThat(pushParser.parse("{}".toByteArray(), aClientSecret)).isNull() |
||||
// Bad Json |
||||
assertThat(pushParser.parse("ABC".toByteArray(), aClientSecret)).isNull() |
||||
} |
||||
|
||||
@Test |
||||
fun `test UnifiedPush format`() { |
||||
val pushParser = UnifiedPushParser() |
||||
assertThat(pushParser.parse(UNIFIED_PUSH_DATA.toByteArray(), aClientSecret)).isEqualTo(validData) |
||||
} |
||||
|
||||
@Test |
||||
fun `test empty roomId`() { |
||||
val pushParser = UnifiedPushParser() |
||||
assertNullOrThrow { |
||||
pushParser.parse(UNIFIED_PUSH_DATA.replace(A_ROOM_ID.value, "").toByteArray(), aClientSecret) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `test invalid roomId`() { |
||||
val pushParser = UnifiedPushParser() |
||||
assertNullOrThrow { |
||||
pushParser.parse(UNIFIED_PUSH_DATA.mutate(A_ROOM_ID.value, "aRoomId:domain"), aClientSecret) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `test empty eventId`() { |
||||
val pushParser = UnifiedPushParser() |
||||
assertNullOrThrow { |
||||
pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, ""), aClientSecret) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `test invalid eventId`() { |
||||
val pushParser = UnifiedPushParser() |
||||
assertNullOrThrow { |
||||
pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, "anEventId"), aClientSecret) |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
private val UNIFIED_PUSH_DATA = |
||||
"{\"notification\":{\"event_id\":\"${AN_EVENT_ID.value}\",\"room_id\":\"${A_ROOM_ID.value}\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}" |
||||
// TODO Check client secret format? |
||||
} |
||||
} |
||||
|
||||
private fun String.mutate(oldValue: String, newValue: String): ByteArray { |
||||
return replace(oldValue, newValue).toByteArray() |
||||
} |
@ -0,0 +1,27 @@
@@ -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. |
||||
*/ |
||||
|
||||
plugins { |
||||
id("io.element.android-library") |
||||
} |
||||
|
||||
android { |
||||
namespace = "io.element.android.libraries.pushstore.api" |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(projects.libraries.matrix.api) |
||||
} |
@ -0,0 +1,26 @@
@@ -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. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.pushstore.api |
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId |
||||
|
||||
/** |
||||
* Store data related to push about a user. |
||||
*/ |
||||
interface UserPushStoreFactory { |
||||
fun create(userId: SessionId): UserPushStore |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* 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.matrix.api) |
||||
implementation(projects.libraries.pushstore.api) |
||||
implementation(projects.libraries.sessionStorage.api) |
||||
implementation(libs.androidx.corektx) |
||||
implementation(libs.androidx.datastore.preferences) |
||||
|
||||
testImplementation(libs.test.junit) |
||||
testImplementation(libs.test.mockk) |
||||
testImplementation(libs.test.truth) |
||||
testImplementation(libs.test.turbine) |
||||
testImplementation(libs.coroutines.test) |
||||
testImplementation(projects.libraries.matrix.test) |
||||
testImplementation(projects.services.appnavstate.test) |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue