Loading .gitlab-ci.yml +4 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,10 @@ stages: - build before_script: - echo MURENA_CLIENT_ID=$MURENA_CLIENT_ID >> local.properties - echo MURENA_REDIRECT_URI=$MURENA_REDIRECT_URI >> local.properties - echo MURENA_BASE_URL=$MURENA_BASE_URL >> local.properties - echo MURENA_DISCOVERY_END_POINT=$MURENA_DISCOVERY_END_POINT >> local.properties - export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 - export GRADLE_USER_HOME=$(pwd)/.gradle - chmod +x ./gradlew Loading app-ose/build.gradle.kts +15 −2 Original line number Diff line number Diff line import java.io.FileInputStream import java.io.FileOutputStream import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.Properties Loading Loading @@ -94,6 +92,12 @@ android { if (versionName != null) { setProperty("archivesBaseName", "AccountManager-$versionName") } manifestPlaceholders.putAll( mapOf<String, Any>( "murenaAuthRedirectScheme" to retrieveKey("MURENA_REDIRECT_URI"), ) ) } } Loading Loading @@ -148,6 +152,15 @@ android { } } fun retrieveKey(keyName: String): String { val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) } return properties.getProperty(keyName) ?: throw GradleException("$keyName property not found in local.properties file") } dependencies { // include core subproject (manages its own dependencies itself, however from same version catalog) implementation(project(":core")) Loading app-ose/src/ose/AndroidManifest.xml +185 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,14 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /> <uses-permission android:name="foundation.e.permission.READ_TASKS"/> <uses-permission android:name="foundation.e.permission.WRITE_TASKS"/> <application> <!-- AppAuth login flow redirect --> Loading @@ -19,9 +27,186 @@ tools:ignore="AppLinkUrlError" android:scheme="${applicationId}" android:path="/oauth2/redirect"/> <!-- /e/ Specific --> <data android:scheme="${applicationId}" /> <data android:scheme="${murenaAuthRedirectScheme}" /> </intent-filter> </activity> <!-- region /e/ Specific --> <service android:name=".sync.adapter.MurenaCalendarsSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_calendars"/> </service> <service android:name=".sync.adapter.MurenaJtxSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_notes"/> </service> <service android:name=".sync.adapter.MurenaOpenTasksSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_opentasks"/> </service> <service android:name=".sync.adapter.MurenaEOpenTasksSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_e_opentasks"/> </service> <service android:name=".sync.adapter.MurenaTasksOrgSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_tasks_org"/> </service> <service android:name="foundation.e.accountmanager.sync.account.MurenaAccountAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/murena_account_authenticator"/> </service> <service android:name="foundation.e.accountmanager.sync.account.MurenaAddressBookAuthenticatorService" android:exported="true" tools:ignore="ExportedService"> <!-- Since Android 11, this must be true so that Google Contacts shows the address book accounts --> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/murena_account_authenticator_address_book"/> </service> <service android:name=".sync.adapter.MurenaContactsSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_contacts"/> <meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contacts"/> </service> <service android:name=".sync.adapter.MurenaMeteredEdriveSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_metered_edrive" /> </service> <service android:name=".sync.adapter.MurenaMediaSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_media" /> </service> <service android:name=".sync.adapter.MurenaAppDataSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_app_data" /> </service> <service android:name=".sync.adapter.MurenaEmailSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_email" /> </service> <service android:name=".sync.adapter.MurenaNotesSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_e_notes" /> </service> <!-- endregion /e/ Specific --> </application> </manifest> No newline at end of file core/build.gradle.kts +17 −0 Original line number Diff line number Diff line import java.util.Properties /* * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. */ Loading @@ -22,6 +24,11 @@ android { // include these rules in the app that uses the core library consumerProguardFile("core-proguard-rules.pro") buildConfigField("String", "MURENA_CLIENT_ID", "\"${retrieveKey("MURENA_CLIENT_ID")}\"") buildConfigField("String", "MURENA_REDIRECT_URI", "\"${retrieveKey("MURENA_REDIRECT_URI")}\"") buildConfigField("String", "MURENA_BASE_URL", "\"${retrieveKey("MURENA_BASE_URL")}\"") buildConfigField("String", "MURENA_DISCOVERY_END_POINT", "\"${retrieveKey("MURENA_DISCOVERY_END_POINT")}\"") } java { Loading Loading @@ -84,6 +91,15 @@ android { } } fun retrieveKey(keyName: String): String { val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) } return properties.getProperty(keyName) ?: throw GradleException("$keyName property not found in local.properties file") } ksp { arg("room.schemaLocation", "$projectDir/schemas") } Loading Loading @@ -180,6 +196,7 @@ dependencies { implementation(libs.commons.lang) // e-Specific dependencies implementation(libs.androidx.runtime.livedata) implementation(libs.elib) implementation(libs.ez.vcard) implementation(libs.synctools) { Loading core/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +3 −3 Original line number Diff line number Diff line Loading @@ -73,7 +73,7 @@ class AccountSettings @AssistedInject constructor( *AccountTypes.getAccountTypes().toTypedArray(), "at.bitfire.davdroid.test" // R.strings.account_type_test in androidTest ) if (!allowedAccountTypes.contains(account.type)) if (!allowedAccountTypes.any { it == account.type }) throw IllegalArgumentException("Invalid account type for AccountSettings(): ${account.type}") // synchronize because account migration must only be run one time Loading Loading @@ -157,7 +157,7 @@ class AccountSettings @AssistedInject constructor( SyncDataType.CONTACTS -> KEY_SYNC_INTERVAL_ADDRESSBOOKS SyncDataType.EVENTS -> KEY_SYNC_INTERVAL_CALENDARS SyncDataType.TASKS -> KEY_SYNC_INTERVAL_TASKS SyncDataType.DEFAULT -> KEY_SYNC_INTERVAL_DEFAULT else -> KEY_SYNC_INTERVAL_DEFAULT } val seconds = accountManager.getUserData(account, key)?.toLong() return when (seconds) { Loading @@ -178,7 +178,7 @@ class AccountSettings @AssistedInject constructor( SyncDataType.CONTACTS -> KEY_SYNC_INTERVAL_ADDRESSBOOKS SyncDataType.EVENTS -> KEY_SYNC_INTERVAL_CALENDARS SyncDataType.TASKS -> KEY_SYNC_INTERVAL_TASKS SyncDataType.DEFAULT -> KEY_SYNC_INTERVAL_DEFAULT else -> KEY_SYNC_INTERVAL_DEFAULT } val newValue = seconds ?: SYNC_INTERVAL_MANUALLY accountManager.setAndVerifyUserData(account, key, newValue.toString()) Loading Loading
.gitlab-ci.yml +4 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,10 @@ stages: - build before_script: - echo MURENA_CLIENT_ID=$MURENA_CLIENT_ID >> local.properties - echo MURENA_REDIRECT_URI=$MURENA_REDIRECT_URI >> local.properties - echo MURENA_BASE_URL=$MURENA_BASE_URL >> local.properties - echo MURENA_DISCOVERY_END_POINT=$MURENA_DISCOVERY_END_POINT >> local.properties - export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 - export GRADLE_USER_HOME=$(pwd)/.gradle - chmod +x ./gradlew Loading
app-ose/build.gradle.kts +15 −2 Original line number Diff line number Diff line import java.io.FileInputStream import java.io.FileOutputStream import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.Properties Loading Loading @@ -94,6 +92,12 @@ android { if (versionName != null) { setProperty("archivesBaseName", "AccountManager-$versionName") } manifestPlaceholders.putAll( mapOf<String, Any>( "murenaAuthRedirectScheme" to retrieveKey("MURENA_REDIRECT_URI"), ) ) } } Loading Loading @@ -148,6 +152,15 @@ android { } } fun retrieveKey(keyName: String): String { val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) } return properties.getProperty(keyName) ?: throw GradleException("$keyName property not found in local.properties file") } dependencies { // include core subproject (manages its own dependencies itself, however from same version catalog) implementation(project(":core")) Loading
app-ose/src/ose/AndroidManifest.xml +185 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,14 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /> <uses-permission android:name="foundation.e.permission.READ_TASKS"/> <uses-permission android:name="foundation.e.permission.WRITE_TASKS"/> <application> <!-- AppAuth login flow redirect --> Loading @@ -19,9 +27,186 @@ tools:ignore="AppLinkUrlError" android:scheme="${applicationId}" android:path="/oauth2/redirect"/> <!-- /e/ Specific --> <data android:scheme="${applicationId}" /> <data android:scheme="${murenaAuthRedirectScheme}" /> </intent-filter> </activity> <!-- region /e/ Specific --> <service android:name=".sync.adapter.MurenaCalendarsSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_calendars"/> </service> <service android:name=".sync.adapter.MurenaJtxSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_notes"/> </service> <service android:name=".sync.adapter.MurenaOpenTasksSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_opentasks"/> </service> <service android:name=".sync.adapter.MurenaEOpenTasksSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_e_opentasks"/> </service> <service android:name=".sync.adapter.MurenaTasksOrgSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_tasks_org"/> </service> <service android:name="foundation.e.accountmanager.sync.account.MurenaAccountAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/murena_account_authenticator"/> </service> <service android:name="foundation.e.accountmanager.sync.account.MurenaAddressBookAuthenticatorService" android:exported="true" tools:ignore="ExportedService"> <!-- Since Android 11, this must be true so that Google Contacts shows the address book accounts --> <intent-filter> <action android:name="android.accounts.AccountAuthenticator"/> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/murena_account_authenticator_address_book"/> </service> <service android:name=".sync.adapter.MurenaContactsSyncAdapterService" android:exported="true" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_contacts"/> <meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contacts"/> </service> <service android:name=".sync.adapter.MurenaMeteredEdriveSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_metered_edrive" /> </service> <service android:name=".sync.adapter.MurenaMediaSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_media" /> </service> <service android:name=".sync.adapter.MurenaAppDataSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_app_data" /> </service> <service android:name=".sync.adapter.MurenaEmailSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_email" /> </service> <service android:name=".sync.adapter.MurenaNotesSyncAdapterService" android:exported="true" android:process=":sync" tools:ignore="ExportedService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/e_sync_e_notes" /> </service> <!-- endregion /e/ Specific --> </application> </manifest> No newline at end of file
core/build.gradle.kts +17 −0 Original line number Diff line number Diff line import java.util.Properties /* * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. */ Loading @@ -22,6 +24,11 @@ android { // include these rules in the app that uses the core library consumerProguardFile("core-proguard-rules.pro") buildConfigField("String", "MURENA_CLIENT_ID", "\"${retrieveKey("MURENA_CLIENT_ID")}\"") buildConfigField("String", "MURENA_REDIRECT_URI", "\"${retrieveKey("MURENA_REDIRECT_URI")}\"") buildConfigField("String", "MURENA_BASE_URL", "\"${retrieveKey("MURENA_BASE_URL")}\"") buildConfigField("String", "MURENA_DISCOVERY_END_POINT", "\"${retrieveKey("MURENA_DISCOVERY_END_POINT")}\"") } java { Loading Loading @@ -84,6 +91,15 @@ android { } } fun retrieveKey(keyName: String): String { val properties = Properties().apply { load(rootProject.file("local.properties").inputStream()) } return properties.getProperty(keyName) ?: throw GradleException("$keyName property not found in local.properties file") } ksp { arg("room.schemaLocation", "$projectDir/schemas") } Loading Loading @@ -180,6 +196,7 @@ dependencies { implementation(libs.commons.lang) // e-Specific dependencies implementation(libs.androidx.runtime.livedata) implementation(libs.elib) implementation(libs.ez.vcard) implementation(libs.synctools) { Loading
core/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +3 −3 Original line number Diff line number Diff line Loading @@ -73,7 +73,7 @@ class AccountSettings @AssistedInject constructor( *AccountTypes.getAccountTypes().toTypedArray(), "at.bitfire.davdroid.test" // R.strings.account_type_test in androidTest ) if (!allowedAccountTypes.contains(account.type)) if (!allowedAccountTypes.any { it == account.type }) throw IllegalArgumentException("Invalid account type for AccountSettings(): ${account.type}") // synchronize because account migration must only be run one time Loading Loading @@ -157,7 +157,7 @@ class AccountSettings @AssistedInject constructor( SyncDataType.CONTACTS -> KEY_SYNC_INTERVAL_ADDRESSBOOKS SyncDataType.EVENTS -> KEY_SYNC_INTERVAL_CALENDARS SyncDataType.TASKS -> KEY_SYNC_INTERVAL_TASKS SyncDataType.DEFAULT -> KEY_SYNC_INTERVAL_DEFAULT else -> KEY_SYNC_INTERVAL_DEFAULT } val seconds = accountManager.getUserData(account, key)?.toLong() return when (seconds) { Loading @@ -178,7 +178,7 @@ class AccountSettings @AssistedInject constructor( SyncDataType.CONTACTS -> KEY_SYNC_INTERVAL_ADDRESSBOOKS SyncDataType.EVENTS -> KEY_SYNC_INTERVAL_CALENDARS SyncDataType.TASKS -> KEY_SYNC_INTERVAL_TASKS SyncDataType.DEFAULT -> KEY_SYNC_INTERVAL_DEFAULT else -> KEY_SYNC_INTERVAL_DEFAULT } val newValue = seconds ?: SYNC_INTERVAL_MANUALLY accountManager.setAndVerifyUserData(account, key, newValue.toString()) Loading