Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support UnifiedPush Connector 3.x #1325

Draft
wants to merge 6 commits into
base: main-ose
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,12 @@ dependencies {
implementation(libs.okhttp.brotli)
implementation(libs.okhttp.logging)
implementation(libs.openid.appauth)
implementation(libs.unifiedpush)
implementation(libs.unifiedpush) {
// UnifiedPush connector seems to be using a workaround by importing this library.
// Will be removed after https://github.com/tink-crypto/tink-java-apps/pull/5 is merged.
// See: https://codeberg.org/UnifiedPush/android-connector/src/commit/28cb0d622ed0a972996041ab9cc85b701abc48c6/connector/build.gradle#L56-L59
exclude(group = "com.google.crypto.tink", module = "tink")
}

// for tests
androidTestImplementation(libs.androidx.arch.core.testing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import kotlinx.coroutines.runInterruptible
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import org.unifiedpush.android.connector.data.PushEndpoint
import java.io.IOException
import java.io.StringWriter
import java.time.Duration
Expand Down Expand Up @@ -67,7 +68,7 @@ class PushRegistrationWorker @AssistedInject constructor(
return Result.success()
}

private suspend fun registerPushSubscription(collection: Collection, account: Account, endpoint: String) {
private suspend fun registerPushSubscription(collection: Collection, account: Account, endpoint: PushEndpoint) {
httpClientBuilder.get()
.fromAccount(account)
.build()
Expand All @@ -87,7 +88,18 @@ class PushRegistrationWorker @AssistedInject constructor(
// subscription URL
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "web-push-subscription")) {
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "push-resource")) {
text(endpoint)
text(endpoint.url)
}
endpoint.pubKeySet?.let { pubKeySet ->
// Right now only p256dh is supported, but more can be added in
// the future.
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "client-public-key")) {
attribute(NS_WEBDAV_PUSH, "type", "p256dh")
text(pubKeySet.pubKey)
}
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "auth-secret")) {
text(pubKeySet.auth)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.unifiedpush.android.connector.FailedReason
import org.unifiedpush.android.connector.MessagingReceiver
import org.unifiedpush.android.connector.data.PushEndpoint
import org.unifiedpush.android.connector.data.PushMessage
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
Expand Down Expand Up @@ -54,22 +57,32 @@ class UnifiedPushReceiver: MessagingReceiver() {
lateinit var syncWorkerManager: SyncWorkerManager


override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
override fun onNewEndpoint(context: Context, endpoint: PushEndpoint, instance: String) {
// remember new endpoint
preferenceRepository.unifiedPushEndpoint(endpoint)

// register new endpoint at CalDAV/CardDAV servers
pushRegistrationWorkerManager.updatePeriodicWorker()
}

override fun onRegistrationFailed(context: Context, reason: FailedReason, instance: String) {
logger.warning("Unified Push registration failed: $reason")
// reset known endpoint to make sure nothing is stored when not registered
preferenceRepository.unifiedPushEndpoint(null)
}

override fun onUnregistered(context: Context, instance: String) {
// reset known endpoint
preferenceRepository.unifiedPushEndpoint(null)
}

override fun onMessage(context: Context, message: ByteArray, instance: String) {
override fun onMessage(context: Context, message: PushMessage, instance: String) {
CoroutineScope(Dispatchers.Default).launch {
val messageXml = message.toString(Charsets.UTF_8)
if (!message.decrypted) {
logger.severe("Received a push message that could not be decrypted.")
return@launch
}
val messageXml = message.content.toString(Charsets.UTF_8)
logger.log(Level.INFO, "Received push message", messageXml)

// parse push notification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import org.unifiedpush.android.connector.data.PublicKeySet
import org.unifiedpush.android.connector.data.PushEndpoint
import javax.inject.Inject

/**
Expand All @@ -24,7 +26,9 @@ class PreferenceRepository @Inject constructor(

companion object {
const val LOG_TO_FILE = "log_to_file"
const val UNIFIED_PUSH_ENDPOINT = "unified_push_endpoint"
const val UNIFIED_PUSH_ENDPOINT_URL = "unified_push_endpoint_url"
const val UNIFIED_PUSH_ENDPOINT_KEY = "unified_push_endpoint_key"
const val UNIFIED_PUSH_ENDPOINT_AUTH = "unified_push_endpoint_auth"
}

private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
Expand Down Expand Up @@ -54,17 +58,24 @@ class PreferenceRepository @Inject constructor(
}


fun unifiedPushEndpoint() =
preferences.getString(UNIFIED_PUSH_ENDPOINT, null)
fun unifiedPushEndpoint(): PushEndpoint? {
val url = preferences.getString(UNIFIED_PUSH_ENDPOINT_URL, null) ?: return null
val key = preferences.getString(UNIFIED_PUSH_ENDPOINT_KEY, null)
val auth = preferences.getString(UNIFIED_PUSH_ENDPOINT_AUTH, null)
val publicKeySet = if (key != null && auth != null) PublicKeySet(key, auth) else null
return PushEndpoint(url, publicKeySet)
}

fun unifiedPushEndpointFlow() = observeAsFlow(UNIFIED_PUSH_ENDPOINT) {
fun unifiedPushEndpointFlow() = observeAsFlow(UNIFIED_PUSH_ENDPOINT_URL) {
unifiedPushEndpoint()
}

fun unifiedPushEndpoint(endpoint: String?) {
fun unifiedPushEndpoint(endpoint: PushEndpoint?) {
preferences
.edit()
.putString(UNIFIED_PUSH_ENDPOINT, endpoint)
.putString(UNIFIED_PUSH_ENDPOINT_URL, endpoint?.url)
.putString(UNIFIED_PUSH_ENDPOINT_KEY, endpoint?.pubKeySet?.pubKey)
.putString(UNIFIED_PUSH_ENDPOINT_AUTH, endpoint?.pubKeySet?.auth)
.apply()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ class AppSettingsModel @Inject constructor(
viewModelScope.launch(Dispatchers.IO) {
if (pushDistributor == null) {
// Disable UnifiedPush if the distributor given is null
UnifiedPush.safeRemoveDistributor(context)
UnifiedPush.unregisterApp(context)
UnifiedPush.removeDistributor(context)
UnifiedPush.unregister(context)
} else {
// If a distributor was passed, store it and register the app
UnifiedPush.saveDistributor(context, pushDistributor)
UnifiedPush.registerApp(context)
UnifiedPush.register(context)
}
_pushDistributor.value = pushDistributor
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mockk = "1.13.17"
okhttp = "4.12.0"
openid-appauth = "0.11.1"
room = "2.6.1"
unifiedpush = "2.4.0"
unifiedpush = "3.0.4"

[libraries]
android-desugaring = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "android-desugaring" }
Expand Down