mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-10-01 17:00:45 +00:00
Implement basic Push functionality (#856)
* Move PushRegistrationWorker to push package * Add UP dependency * [WIP] UnifiedPush basic implementation * Handle endpoint unregistration * [WIP] Parse push notification message * Parse push message in PushMessageParser * Sync only affected account on push message * Only initiate sync when push message is about a syncable collection * Push registration worker: log when there's no configured endpoint * Handle invalid/non-XML push messages * app settings: show UP endpoint
This commit is contained in:
parent
28948485f6
commit
bcc16e1ab6
|
@ -198,6 +198,7 @@ dependencies {
|
|||
implementation(libs.okhttp.brotli)
|
||||
implementation(libs.okhttp.logging)
|
||||
implementation(libs.openid.appauth)
|
||||
implementation(libs.unifiedpush)
|
||||
|
||||
// for tests
|
||||
androidTestImplementation(libs.androidx.arch.core.testing)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package at.bitfire.davdroid
|
||||
|
||||
import at.bitfire.davdroid.repository.DavCollectionRepository
|
||||
import at.bitfire.davdroid.syncadapter.PushRegistrationWorker
|
||||
import at.bitfire.davdroid.push.PushRegistrationWorker
|
||||
import dagger.Module
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.Multibinds
|
||||
|
|
|
@ -288,6 +288,16 @@
|
|||
android:resource="@xml/debug_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- UnifiedPush receiver -->
|
||||
<receiver android:exported="true" android:enabled="true" android:name=".push.UnifiedPushReceiver" 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"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Widgets -->
|
||||
<receiver android:name=".ui.widget.SyncButtonWidgetReceiver"
|
||||
android:exported="true">
|
||||
|
|
|
@ -32,6 +32,9 @@ interface CollectionDao {
|
|||
@Query("SELECT * FROM collection WHERE serviceId=:serviceId AND type=:type ORDER BY displayName COLLATE NOCASE, url COLLATE NOCASE")
|
||||
fun getByServiceAndType(serviceId: Long, type: String): List<Collection>
|
||||
|
||||
@Query("SELECT * FROM collection WHERE pushTopic=:topic AND sync")
|
||||
fun getSyncableByPushTopic(topic: String): Collection?
|
||||
|
||||
@Query("SELECT COUNT(*) FROM collection WHERE serviceId=:serviceId AND type=:type")
|
||||
suspend fun anyOfType(serviceId: Long, type: String): Boolean
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import androidx.room.Dao
|
|||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.push
|
||||
|
||||
import at.bitfire.dav4jvm.XmlUtils
|
||||
import at.bitfire.dav4jvm.property.push.PushMessage
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import java.io.StringReader
|
||||
import java.util.logging.Level
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushMessageParser @Inject constructor() {
|
||||
|
||||
/**
|
||||
* Parses a WebDAV-Push message and returns the `topic` that the message is about.
|
||||
*
|
||||
* @return topic of the modified collection, or `null` if the topic couldn't be determined
|
||||
*/
|
||||
operator fun invoke(message: String): String? {
|
||||
var topic: String? = null
|
||||
|
||||
val parser = XmlUtils.newPullParser()
|
||||
try {
|
||||
parser.setInput(StringReader(message))
|
||||
|
||||
XmlUtils.processTag(parser, PushMessage.NAME) {
|
||||
val pushMessage = PushMessage.Factory.create(parser)
|
||||
topic = pushMessage.topic
|
||||
}
|
||||
} catch (e: XmlPullParserException) {
|
||||
Logger.log.log(Level.WARNING, "Couldn't parse push message", e)
|
||||
}
|
||||
|
||||
return topic
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
package at.bitfire.davdroid.syncadapter
|
||||
/*
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.push
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.Context
|
||||
|
@ -22,6 +26,7 @@ import at.bitfire.davdroid.log.Logger
|
|||
import at.bitfire.davdroid.network.HttpClient
|
||||
import at.bitfire.davdroid.repository.DavCollectionRepository
|
||||
import at.bitfire.davdroid.repository.DavServiceRepository
|
||||
import at.bitfire.davdroid.repository.PreferenceRepository
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
@ -51,6 +56,7 @@ class PushRegistrationWorker @AssistedInject constructor(
|
|||
@Assisted context: Context,
|
||||
@Assisted workerParameters: WorkerParameters,
|
||||
private val collectionRepository: DavCollectionRepository,
|
||||
private val preferenceRepository: PreferenceRepository,
|
||||
private val serviceRepository: DavServiceRepository
|
||||
) : CoroutineWorker(context, workerParameters) {
|
||||
|
||||
|
@ -118,16 +124,18 @@ class PushRegistrationWorker @AssistedInject constructor(
|
|||
override suspend fun doWork(): Result {
|
||||
Logger.log.info("Running push registration worker")
|
||||
|
||||
// We will get this endpoint from UnifiedPush:
|
||||
val sampleEndpoint = "https://endpoint.example.com"
|
||||
val endpoint = preferenceRepository.unifiedPushEndpoint()
|
||||
|
||||
for (collection in collectionRepository.getSyncEnabledAndPushCapable()) {
|
||||
Logger.log.info("Registering push for ${collection.url}")
|
||||
val service = serviceRepository.get(collection.serviceId) ?: continue
|
||||
val account = Account(service.accountName, applicationContext.getString(R.string.account_type))
|
||||
if (endpoint != null)
|
||||
for (collection in collectionRepository.getSyncableAndPushCapable()) {
|
||||
Logger.log.info("Registering push for ${collection.url}")
|
||||
val service = serviceRepository.get(collection.serviceId) ?: continue
|
||||
val account = Account(service.accountName, applicationContext.getString(R.string.account_type))
|
||||
|
||||
requestPushRegistration(collection, account, sampleEndpoint)
|
||||
}
|
||||
requestPushRegistration(collection, account, endpoint)
|
||||
}
|
||||
else
|
||||
Logger.log.info("No UnifiedPush endpoint configured")
|
||||
|
||||
return Result.success()
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.push
|
||||
|
||||
import android.content.Context
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.repository.AccountRepository
|
||||
import at.bitfire.davdroid.repository.DavCollectionRepository
|
||||
import at.bitfire.davdroid.repository.DavServiceRepository
|
||||
import at.bitfire.davdroid.repository.PreferenceRepository
|
||||
import at.bitfire.davdroid.syncadapter.OneTimeSyncWorker
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.unifiedpush.android.connector.MessagingReceiver
|
||||
import java.util.logging.Level
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class UnifiedPushReceiver: MessagingReceiver() {
|
||||
|
||||
@Inject
|
||||
lateinit var accountRepository: AccountRepository
|
||||
|
||||
@Inject
|
||||
lateinit var collectionRepository: DavCollectionRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceRepository: DavServiceRepository
|
||||
|
||||
@Inject
|
||||
lateinit var preferenceRepository: PreferenceRepository
|
||||
|
||||
@Inject
|
||||
lateinit var parsePushMessage: PushMessageParser
|
||||
|
||||
|
||||
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
||||
// remember new endpoint
|
||||
preferenceRepository.unifiedPushEndpoint(endpoint)
|
||||
|
||||
// register new endpoint at CalDAV/CardDAV servers
|
||||
PushRegistrationWorker.enqueue(context)
|
||||
}
|
||||
|
||||
override fun onUnregistered(context: Context, instance: String) {
|
||||
// reset known endpoint
|
||||
preferenceRepository.unifiedPushEndpoint(null)
|
||||
}
|
||||
|
||||
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
||||
val messageXml = message.toString(Charsets.UTF_8)
|
||||
Logger.log.log(Level.INFO, "Received push message", messageXml)
|
||||
|
||||
// parse push notification
|
||||
val topic = parsePushMessage(messageXml)
|
||||
|
||||
// sync affected collection
|
||||
if (topic != null) {
|
||||
Logger.log.info("Got push notification for topic $topic")
|
||||
|
||||
// Sync all authorities of account that the collection belongs to
|
||||
// Later: only sync affected collection and authorities
|
||||
collectionRepository.getSyncableByTopic(topic)?.let { collection ->
|
||||
serviceRepository.get(collection.serviceId)?.let { service ->
|
||||
val account = accountRepository.fromName(service.accountName)
|
||||
OneTimeSyncWorker.enqueueAllAuthorities(context, account)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Logger.log.warning("Got push message without topic, syncing all accounts")
|
||||
for (account in accountRepository.getAll())
|
||||
OneTimeSyncWorker.enqueueAllAuthorities(context, account)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -69,7 +69,7 @@ class AccountRepository @Inject constructor(
|
|||
* @return account if account creation was successful; null otherwise (for instance because an account with this name already exists)
|
||||
*/
|
||||
fun create(accountName: String, credentials: Credentials?, config: DavResourceFinder.Configuration, groupMethod: GroupMethod): Account? {
|
||||
val account = account(accountName)
|
||||
val account = fromName(accountName)
|
||||
|
||||
// create Android account
|
||||
val userData = AccountSettings.initialUserData(credentials)
|
||||
|
@ -135,7 +135,7 @@ class AccountRepository @Inject constructor(
|
|||
|
||||
suspend fun delete(accountName: String): Boolean {
|
||||
// remove account
|
||||
val future = accountManager.removeAccount(account(accountName), null, null, null)
|
||||
val future = accountManager.removeAccount(fromName(accountName), null, null, null)
|
||||
return try {
|
||||
// wait for operation to complete
|
||||
withContext(Dispatchers.Default) {
|
||||
|
@ -162,7 +162,10 @@ class AccountRepository @Inject constructor(
|
|||
else
|
||||
accountManager
|
||||
.getAccountsByType(accountType)
|
||||
.contains(Account(accountName, accountType))
|
||||
.any { it.name == accountName }
|
||||
|
||||
fun fromName(accountName: String) =
|
||||
Account(accountName, accountType)
|
||||
|
||||
fun getAll(): Array<Account> = accountManager.getAccountsByType(accountType)
|
||||
|
||||
|
@ -191,8 +194,8 @@ class AccountRepository @Inject constructor(
|
|||
* @throws Exception (or sub-classes) on other errors
|
||||
*/
|
||||
suspend fun rename(oldName: String, newName: String) {
|
||||
val oldAccount = account(oldName)
|
||||
val newAccount = account(newName)
|
||||
val oldAccount = fromName(oldName)
|
||||
val newAccount = fromName(newName)
|
||||
|
||||
// check whether new account name already exists
|
||||
if (accountManager.getAccountsByType(context.getString(R.string.account_type)).contains(newAccount))
|
||||
|
@ -286,8 +289,6 @@ class AccountRepository @Inject constructor(
|
|||
|
||||
// helpers
|
||||
|
||||
private fun account(accountName: String) = Account(accountName, accountType)
|
||||
|
||||
private fun insertService(accountName: String, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long {
|
||||
// insert service
|
||||
val service = Service(0, accountName, type, info.principal)
|
||||
|
|
|
@ -179,10 +179,12 @@ class DavCollectionRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun getSyncableByTopic(topic: String) = dao.getSyncableByPushTopic(topic)
|
||||
|
||||
fun getFlow(id: Long) = dao.getFlow(id)
|
||||
|
||||
/** Returns all collections that are both selected for synchronization and push-capable. */
|
||||
suspend fun getSyncEnabledAndPushCapable(): List<Collection> =
|
||||
suspend fun getSyncableAndPushCapable(): List<Collection> =
|
||||
dao.getPushCapableSyncCollections()
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,10 @@ class PreferenceRepository @Inject constructor(
|
|||
context: Application
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val UNIFIED_PUSH_ENDPOINT = "unified_push_endpoint"
|
||||
}
|
||||
|
||||
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
/**
|
||||
|
@ -41,6 +45,20 @@ class PreferenceRepository @Inject constructor(
|
|||
preferences.getBoolean(Logger.LOG_TO_FILE, false)
|
||||
}
|
||||
|
||||
fun unifiedPushEndpoint() =
|
||||
preferences.getString(UNIFIED_PUSH_ENDPOINT, null)
|
||||
|
||||
fun unifiedPushEndpointFlow() = observeAsFlow(UNIFIED_PUSH_ENDPOINT) {
|
||||
unifiedPushEndpoint()
|
||||
}
|
||||
|
||||
fun unifiedPushEndpoint(endpoint: String?) {
|
||||
preferences
|
||||
.edit()
|
||||
.putString(UNIFIED_PUSH_ENDPOINT, endpoint)
|
||||
.apply()
|
||||
}
|
||||
|
||||
|
||||
private fun<T> observeAsFlow(keyToObserve: String, getValue: () -> T): Flow<T> =
|
||||
callbackFlow {
|
||||
|
|
|
@ -101,4 +101,8 @@ class AppSettingsModel @Inject constructor(
|
|||
val icon = appInfoFlow.map { it?.loadIcon(pm) }
|
||||
|
||||
|
||||
// push
|
||||
|
||||
val pushEndpoint = preference.unifiedPushEndpointFlow()
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -50,6 +51,7 @@ import at.bitfire.davdroid.ui.composable.Setting
|
|||
import at.bitfire.davdroid.ui.composable.SettingsHeader
|
||||
import at.bitfire.davdroid.ui.composable.SwitchSetting
|
||||
import kotlinx.coroutines.launch
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
@Composable
|
||||
fun AppSettingsScreen(
|
||||
|
@ -92,9 +94,10 @@ fun AppSettingsScreen(
|
|||
onThemeSelected = model::updateTheme,
|
||||
onResetHints = model::resetHints,
|
||||
|
||||
// Integration (Tasks)
|
||||
// Integration (Tasks and Push)
|
||||
tasksAppName = model.appName.collectAsStateWithLifecycle(null).value ?: stringResource(R.string.app_settings_tasks_provider_none),
|
||||
tasksAppIcon = model.icon.collectAsStateWithLifecycle(null).value,
|
||||
pushEndpoint = model.pushEndpoint.collectAsStateWithLifecycle(null).value,
|
||||
onNavTasksScreen = onNavTasksScreen
|
||||
)
|
||||
}
|
||||
|
@ -133,6 +136,7 @@ fun AppSettingsScreen(
|
|||
// AppSettings Integration
|
||||
tasksAppName: String,
|
||||
tasksAppIcon: Drawable?,
|
||||
pushEndpoint: String?,
|
||||
onNavTasksScreen: () -> Unit,
|
||||
|
||||
onShowNotificationSettings: () -> Unit,
|
||||
|
@ -219,6 +223,7 @@ fun AppSettingsScreen(
|
|||
AppSettings_Integration(
|
||||
appName = tasksAppName,
|
||||
icon = tasksAppIcon,
|
||||
pushEndpoint = pushEndpoint,
|
||||
onNavTasksScreen = onNavTasksScreen
|
||||
)
|
||||
}
|
||||
|
@ -254,6 +259,7 @@ fun AppSettingsScreen_Preview() {
|
|||
onResetHints = {},
|
||||
tasksAppName = "No tasks app",
|
||||
tasksAppIcon = null,
|
||||
pushEndpoint = null,
|
||||
onNavTasksScreen = {}
|
||||
)
|
||||
}
|
||||
|
@ -464,6 +470,7 @@ fun AppSettings_UserInterface(
|
|||
@Composable
|
||||
fun AppSettings_Integration(
|
||||
appName: String,
|
||||
pushEndpoint: String?,
|
||||
icon: Drawable? = null,
|
||||
onNavTasksScreen: () -> Unit = {}
|
||||
) {
|
||||
|
@ -482,4 +489,14 @@ fun AppSettings_Integration(
|
|||
summary = appName,
|
||||
onClick = onNavTasksScreen
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
Setting(
|
||||
name = "UnifiedPush",
|
||||
summary = pushEndpoint ?: stringResource(R.string.app_settings_unifiedpush_no_endpoint),
|
||||
onClick = {
|
||||
UnifiedPush.registerAppWithDialog(context)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -12,10 +12,10 @@ import at.bitfire.davdroid.util.TaskUtils
|
|||
import at.bitfire.davdroid.util.packageChangedFlow
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TasksModel @Inject constructor(
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package at.bitfire.davdroid.ui.composable
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
|
@ -15,7 +15,6 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
|
@ -12,9 +12,9 @@ import android.net.Uri
|
|||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.compose.runtime.Composable
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import javax.inject.Inject
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPageModel.Companion.HINT_AUTOSTART_PERMISSION
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPageModel.Companion.HINT_BATTERY_OPTIMIZATIONS
|
||||
import javax.inject.Inject
|
||||
|
||||
class BatteryOptimizationsPage @Inject constructor(
|
||||
private val application: Application,
|
||||
|
|
|
@ -35,8 +35,8 @@ import at.bitfire.davdroid.Constants
|
|||
import at.bitfire.davdroid.Constants.withStatParams
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.ui.AppTheme
|
||||
import java.util.Locale
|
||||
import org.apache.commons.text.WordUtils
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun BatteryOptimizationsPageContent(
|
||||
|
|
|
@ -16,9 +16,9 @@ import at.bitfire.davdroid.settings.SettingsManager
|
|||
import at.bitfire.davdroid.util.PermissionUtils
|
||||
import at.bitfire.davdroid.util.broadcastReceiverFlow
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
class BatteryOptimizationsPageModel @Inject constructor(
|
||||
|
|
|
@ -6,7 +6,6 @@ package at.bitfire.davdroid.ui.intro
|
|||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.ui.PermissionsModel
|
||||
import at.bitfire.davdroid.ui.PermissionsScreen
|
||||
import at.bitfire.davdroid.util.PermissionUtils
|
||||
|
|
|
@ -209,6 +209,8 @@
|
|||
<string name="app_settings_integration">Integration</string>
|
||||
<string name="app_settings_tasks_provider">Tasks app</string>
|
||||
<string name="app_settings_tasks_provider_none">No compatible tasks app found</string>
|
||||
<string name="app_settings_unifiedpush" translatable="false">UnifiedPush</string>
|
||||
<string name="app_settings_unifiedpush_no_endpoint">No endpoint configured</string>
|
||||
|
||||
<!-- AccountScreen -->
|
||||
<string name="account_carddav">CardDAV</string>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.push
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class PushMessageParserTest {
|
||||
|
||||
private val parse = PushMessageParser()
|
||||
|
||||
@Test
|
||||
fun testInvalidXml() {
|
||||
assertNull(parse("Non-XML content"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWithXmlDeclAndTopic() {
|
||||
val topic = parse(
|
||||
"<?xml version=\"1.0\" ?>" +
|
||||
"<push-message xmlns='DAV:Push'>" +
|
||||
"<topic>sample-topic</topic>" +
|
||||
"</push-message>"
|
||||
)
|
||||
assertEquals("sample-topic", topic)
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@ androidx-test-junit = "1.1.5"
|
|||
androidx-work = "2.9.0"
|
||||
appIntro = "7.0.0-beta02"
|
||||
bitfire-cert4android = "f1cc9b9ca3"
|
||||
bitfire-dav4jvm = "fa173ab215"
|
||||
bitfire-dav4jvm = "b8be778202"
|
||||
bitfire-ical4android = "ba5a013d69"
|
||||
bitfire-vcard4android = "03a37a8284"
|
||||
commons-collections = "4.4"
|
||||
|
@ -48,6 +48,7 @@ mockk = "1.13.11"
|
|||
okhttp = "4.12.0"
|
||||
openid-appauth = "0.11.1"
|
||||
room = "2.6.1"
|
||||
unifiedpush = "2.4.0"
|
||||
|
||||
[libraries]
|
||||
android-desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "android-desugaring" }
|
||||
|
@ -110,6 +111,7 @@ room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
|||
room-paging = { module = "androidx.room:room-paging", version.ref = "room" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||
room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
|
||||
unifiedpush = { module = "com.github.UnifiedPush:android-connector", version.ref = "unifiedpush" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "android-agp" }
|
||||
|
|
Loading…
Reference in a new issue