Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bitfireAT/ical4android.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPatrick Lang <72232737+patrickunterwegs@users.noreply.github.com>2022-01-11 14:32:39 +0300
committerGitHub <noreply@github.com>2022-01-11 14:32:39 +0300
commite31cefd42f5bc299ba5cf16c9ad3710cc9a2a84f (patch)
treef031d4289ab178ea00d0cbce5d4ebf083ebbd78e /src
parent7fd764d2a86199a3e09e24d0f438651c2239f5c4 (diff)
jtx Board support (see bitfireAT/davx5#38)
Initial support for jtx Board
Diffstat (limited to 'src')
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AbstractTasksTest.kt6
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidCalendarTest.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidTaskListTest.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidTaskTest.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AndroidTimeZonesTest.kt4
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/AttendeeMappingsTest.kt4
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/Ical4jSettingsTest.kt4
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/JtxCollectionTest.kt96
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt866
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/LocaleNonWesternDigitsTest.kt4
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/MiscUtilsAndroidTest.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/TestUtils.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/UnknownPropertyTest.kt4
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/impl/TestCalendar.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/impl/TestEvent.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/impl/TestJtxCollection.kt51
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/impl/TestJtxIcalObject.kt19
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/impl/TestTask.kt10
-rw-r--r--src/androidTest/java/at/bitfire/ical4android/impl/TestTaskList.kt10
-rw-r--r--src/androidTest/resources/jtx/vjournal/all-day.ics10
-rw-r--r--src/androidTest/resources/jtx/vjournal/default-example-note.ics18
-rw-r--r--src/androidTest/resources/jtx/vjournal/default-example.ics19
-rw-r--r--src/androidTest/resources/jtx/vjournal/dst-only-vtimezone.ics21
-rw-r--r--src/androidTest/resources/jtx/vjournal/journal-on-that-day.ics11
-rw-r--r--src/androidTest/resources/jtx/vjournal/latin1.ics9
-rw-r--r--src/androidTest/resources/jtx/vjournal/outlook-theoretical.ics71
-rw-r--r--src/androidTest/resources/jtx/vjournal/outlook-theoretical2.ics29
-rw-r--r--src/androidTest/resources/jtx/vjournal/recurring.ics9
-rw-r--r--src/androidTest/resources/jtx/vjournal/two-events-without-exceptions.ics15
-rw-r--r--src/androidTest/resources/jtx/vjournal/two-line-description-without-crlf.ics14
-rw-r--r--src/androidTest/resources/jtx/vjournal/utf8.ics13
-rw-r--r--src/androidTest/resources/jtx/vtodo/empty-priority.ics14
-rw-r--r--src/androidTest/resources/jtx/vtodo/latin1.ics9
-rw-r--r--src/androidTest/resources/jtx/vtodo/most-fields1.ics32
-rw-r--r--src/androidTest/resources/jtx/vtodo/most-fields2.ics9
-rw-r--r--src/androidTest/resources/jtx/vtodo/rfc5545-sample1.ics22
-rw-r--r--src/androidTest/resources/jtx/vtodo/utf8.ics10
-rw-r--r--src/main/AndroidManifest.xml5
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidCalendar.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidCalendarFactory.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidEvent.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidEventFactory.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidTask.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidTaskFactory.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidTaskList.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AndroidTaskListFactory.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/AttendeeMappings.kt4
-rw-r--r--src/main/java/at/bitfire/ical4android/BatchOperation.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/CalendarStorageException.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/Css3Color.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/DateUtils.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/Event.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/ICalPreprocessor.kt4
-rw-r--r--src/main/java/at/bitfire/ical4android/ICalendar.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/Ical4Android.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/InvalidCalendarException.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/JtxCollection.kt265
-rw-r--r--src/main/java/at/bitfire/ical4android/JtxCollectionFactory.kt14
-rw-r--r--src/main/java/at/bitfire/ical4android/JtxICalObject.kt1796
-rw-r--r--src/main/java/at/bitfire/ical4android/JtxICalObjectFactory.kt13
-rw-r--r--src/main/java/at/bitfire/ical4android/MiscUtils.kt10
-rw-r--r--src/main/java/at/bitfire/ical4android/Task.kt12
-rw-r--r--src/main/java/at/bitfire/ical4android/TaskProvider.kt20
-rw-r--r--src/main/java/at/bitfire/ical4android/UnknownProperty.kt4
-rw-r--r--src/main/java/at/bitfire/ical4android/UsesThreadContextClassLoader.kt4
-rw-r--r--src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt4
-rw-r--r--src/main/java/at/bitfire/ical4android/util/TimeApiExtensions.kt4
-rw-r--r--src/main/java/at/techbee/jtx/JtxContract.kt1409
-rw-r--r--src/test/java/at/bitfire/ical4android/AndroidTimeUtilsTest.kt4
-rw-r--r--src/test/java/at/bitfire/ical4android/Css3ColorTest.kt10
-rw-r--r--src/test/java/at/bitfire/ical4android/DateUtilsTest.kt10
-rw-r--r--src/test/java/at/bitfire/ical4android/EventTest.kt10
-rw-r--r--src/test/java/at/bitfire/ical4android/ICalPreprocessorTest.kt4
-rw-r--r--src/test/java/at/bitfire/ical4android/ICalendarTest.kt10
-rw-r--r--src/test/java/at/bitfire/ical4android/Ical4jTest.kt4
-rw-r--r--src/test/java/at/bitfire/ical4android/MiscUtilsTest.kt4
-rw-r--r--src/test/java/at/bitfire/ical4android/TaskTest.kt10
-rw-r--r--src/test/java/at/bitfire/ical4android/TimeApiExtensionsTest.kt4
79 files changed, 5048 insertions, 243 deletions
diff --git a/src/androidTest/java/at/bitfire/ical4android/AbstractTasksTest.kt b/src/androidTest/java/at/bitfire/ical4android/AbstractTasksTest.kt
index fe5425f..16431eb 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AbstractTasksTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AbstractTasksTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import androidx.test.platform.app.InstrumentationRegistry
@@ -16,7 +20,7 @@ abstract class AbstractTasksTest(
companion object {
@Parameterized.Parameters(name="{0}")
@JvmStatic
- fun taskProviders() = TaskProvider.ProviderName.values()
+ fun taskProviders() = listOf(TaskProvider.ProviderName.OpenTasks,TaskProvider.ProviderName.TasksOrg)
}
private val providerOrNull: TaskProvider? by lazy {
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidCalendarTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidCalendarTest.kt
index 09c587f..d754f9d 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AndroidCalendarTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidCalendarTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt
index a4bba3b..00ac8ce 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidEventTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
import android.Manifest
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidTaskListTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidTaskListTest.kt
index 8d6afa3..f7e9398 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AndroidTaskListTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidTaskListTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidTaskTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidTaskTest.kt
index 7b6f945..c907439 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AndroidTaskTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidTaskTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/androidTest/java/at/bitfire/ical4android/AndroidTimeZonesTest.kt b/src/androidTest/java/at/bitfire/ical4android/AndroidTimeZonesTest.kt
index 974910b..d51c1ad 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AndroidTimeZonesTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AndroidTimeZonesTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import org.junit.Assert
diff --git a/src/androidTest/java/at/bitfire/ical4android/AttendeeMappingsTest.kt b/src/androidTest/java/at/bitfire/ical4android/AttendeeMappingsTest.kt
index df507e8..69e0842 100644
--- a/src/androidTest/java/at/bitfire/ical4android/AttendeeMappingsTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/AttendeeMappingsTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import android.content.ContentValues
diff --git a/src/androidTest/java/at/bitfire/ical4android/Ical4jSettingsTest.kt b/src/androidTest/java/at/bitfire/ical4android/Ical4jSettingsTest.kt
index 7f43b8a..7386ef4 100644
--- a/src/androidTest/java/at/bitfire/ical4android/Ical4jSettingsTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/Ical4jSettingsTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import net.fortuna.ical4j.util.TimeZones
diff --git a/src/androidTest/java/at/bitfire/ical4android/JtxCollectionTest.kt b/src/androidTest/java/at/bitfire/ical4android/JtxCollectionTest.kt
new file mode 100644
index 0000000..e1478fe
--- /dev/null
+++ b/src/androidTest/java/at/bitfire/ical4android/JtxCollectionTest.kt
@@ -0,0 +1,96 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import android.accounts.Account
+import android.content.ContentProviderClient
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import androidx.test.platform.app.InstrumentationRegistry
+import at.bitfire.ical4android.MiscUtils.ContentProviderClientHelper.closeCompat
+import at.bitfire.ical4android.impl.TestJtxCollection
+import at.techbee.jtx.JtxContract
+import at.techbee.jtx.JtxContract.asSyncAdapter
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertNotNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class JtxCollectionTest {
+
+ private val testAccount = Account("TEST", JtxContract.JtxCollection.TEST_ACCOUNT_TYPE)
+ private lateinit var contentResolver: ContentResolver
+ private lateinit var client: ContentProviderClient
+ var collection: JtxCollection<JtxICalObject>? = null
+ lateinit var context: Context
+
+ private val url = "https://jtx.techbee.at"
+ private val displayname = "jtx"
+ private val syncversion = JtxContract.VERSION
+
+ private val cv = ContentValues().apply {
+ put(JtxContract.JtxCollection.ACCOUNT_TYPE, testAccount.type)
+ put(JtxContract.JtxCollection.ACCOUNT_NAME, testAccount.name)
+ put(JtxContract.JtxCollection.URL, url)
+ put(JtxContract.JtxCollection.DISPLAYNAME, displayname)
+ put(JtxContract.JtxCollection.SYNC_VERSION, syncversion)
+ }
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ contentResolver = context.contentResolver
+ TestUtils.requestPermissions(TaskProvider.ProviderName.JtxBoard.permissions)
+ client = contentResolver.acquireContentProviderClient(JtxContract.AUTHORITY)!!
+ }
+
+ @After
+ fun tearDown() {
+ var collections = JtxCollection.find(testAccount, client, context, TestJtxCollection.Factory, null, null)
+ collections.forEach { collection ->
+ collection.delete()
+ }
+ collections = JtxCollection.find(testAccount, client, context, TestJtxCollection.Factory, null, null)
+ assertEquals(0, collections.size)
+ client.closeCompat()
+ }
+
+
+ @Test
+ fun create_populate_find() {
+ val collectionUri = JtxCollection.create(testAccount, client, cv)
+ assertNotNull(collectionUri)
+ val collections = JtxCollection.find(testAccount, client, context, TestJtxCollection.Factory, null, null)
+
+ assertEquals(1, collections.size)
+ assertEquals(testAccount.type, collections[0].account.type)
+ assertEquals(testAccount.name, collections[0].account.name)
+ assertEquals(url, collections[0].url)
+ assertEquals(displayname, collections[0].displayname)
+ assertEquals(syncversion.toString(), collections[0].syncstate)
+ }
+
+ @Test
+ fun queryICalObjects() {
+ val collectionUri = JtxCollection.create(testAccount, client, cv)
+ assertNotNull(collectionUri)
+
+ val collections = JtxCollection.find(testAccount, client, context, TestJtxCollection.Factory, null, null)
+ val items = collections[0].queryICalObjects(null, null)
+ assertEquals(0, items.size)
+
+ val cv = ContentValues().apply {
+ put(JtxContract.JtxICalObject.SUMMARY, "summary")
+ put(JtxContract.JtxICalObject.COMPONENT, JtxContract.JtxICalObject.Component.VJOURNAL.name)
+ put(JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID, collections[0].id)
+ }
+ client.insert(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)
+ val icalobjects = collections[0].queryICalObjects(null, null)
+
+ assertEquals(1, icalobjects.size)
+ }
+}
diff --git a/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt b/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt
new file mode 100644
index 0000000..9527dba
--- /dev/null
+++ b/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt
@@ -0,0 +1,866 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import android.accounts.Account
+import android.content.ContentProviderClient
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.database.DatabaseUtils
+import android.os.ParcelFileDescriptor
+import androidx.test.platform.app.InstrumentationRegistry
+import at.bitfire.ical4android.impl.TestJtxCollection
+import at.techbee.jtx.JtxContract
+import at.techbee.jtx.JtxContract.JtxICalObject
+import at.techbee.jtx.JtxContract.JtxICalObject.Component
+import at.techbee.jtx.JtxContract.asSyncAdapter
+import junit.framework.TestCase.*
+import net.fortuna.ical4j.model.Calendar
+import net.fortuna.ical4j.model.Property
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import java.io.ByteArrayOutputStream
+import java.io.InputStreamReader
+
+class JtxICalObjectTest {
+
+ private val testAccount = Account("TEST", JtxContract.JtxCollection.TEST_ACCOUNT_TYPE)
+ private lateinit var contentResolver: ContentResolver
+ private lateinit var client: ContentProviderClient
+ var collection: JtxCollection<at.bitfire.ical4android.JtxICalObject>? = null
+ var sample: at.bitfire.ical4android.JtxICalObject? = null
+ lateinit var context: Context
+
+ private val url = "https://jtx.techbee.at"
+ private val displayname = "jtxTest"
+ private val syncversion = JtxContract.VERSION
+
+ private val cvCollection = ContentValues().apply {
+ put(JtxContract.JtxCollection.ACCOUNT_TYPE, testAccount.type)
+ put(JtxContract.JtxCollection.ACCOUNT_NAME, testAccount.name)
+ put(JtxContract.JtxCollection.URL, url)
+ put(JtxContract.JtxCollection.DISPLAYNAME, displayname)
+ put(JtxContract.JtxCollection.SYNC_VERSION, syncversion)
+ }
+
+ @Before
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().targetContext
+ contentResolver = context.contentResolver
+ TestUtils.requestPermissions(TaskProvider.ProviderName.JtxBoard.permissions)
+ client = contentResolver.acquireContentProviderClient(JtxContract.AUTHORITY)!!
+
+ val collectionUri = JtxCollection.create(testAccount, client, cvCollection)
+ assertNotNull(collectionUri)
+ collection = JtxCollection.find(testAccount, client, context, TestJtxCollection.Factory, null, null)[0]
+ assertNotNull(collection)
+
+ sample = JtxICalObject(collection!!).apply {
+ this.summary = "summ"
+ this.description = "desc"
+ this.dtstart = System.currentTimeMillis()
+ this.dtstartTimezone = "Europe/Vienna"
+ this.dtend = System.currentTimeMillis()
+ this.dtendTimezone = "Europe/Paris"
+ this.status = JtxICalObject.StatusJournal.FINAL.name
+ this.classification = JtxICalObject.Classification.PUBLIC.name
+ this.url = "https://jtx.techbee.at"
+ this.contact = "jtx@techbee.at"
+ this.geoLat = 48.2082
+ this.geoLong = 16.3738
+ this.location = "Vienna"
+ this.locationAltrep = "Wien"
+ this.percent = 99
+ this.priority = 1
+ this.due = System.currentTimeMillis()
+ this.dueTimezone = "Europe/Berlin"
+ this.completed = System.currentTimeMillis()
+ this.completedTimezone = "Europe/Budapest"
+ this.duration = "P15DT5H0M20S"
+ this.rrule = "FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30"
+ this.exdate = System.currentTimeMillis().toString()
+ this.rdate = System.currentTimeMillis().toString()
+ this.recurid = "1635796608864-b228364a-e633-449a-aeb2-d1a96941377c@at.techbee.jtx"
+ this.uid = "1635796608864-b228364a-e633-449a-aeb2-d1a96941377c@at.techbee.jtx"
+ this.created = System.currentTimeMillis()
+ this.lastModified = System.currentTimeMillis()
+ this.dtstamp = System.currentTimeMillis()
+ this.sequence = 1
+ this.color = -2298423
+ this.dirty = false
+ this.deleted = false
+ this.fileName = "test.ics"
+ this.eTag = "0"
+ this.scheduleTag = "0"
+ this.flags = 0
+ }
+
+ }
+
+ @After
+ fun tearDown() {
+
+ collection?.delete()
+ val collections = JtxCollection.find(testAccount, client, context, TestJtxCollection.Factory, null, null)
+ assertEquals(0, collections.size)
+ }
+
+
+ @Test fun check_SUMMARY() = insertRetrieveAssertString(JtxICalObject.SUMMARY, sample?.summary, Component.VJOURNAL.name)
+ @Test fun check_DESCRIPTION() = insertRetrieveAssertString(JtxICalObject.DESCRIPTION, sample?.description, Component.VJOURNAL.name)
+ @Test fun check_DTSTART() = insertRetrieveAssertLong(JtxICalObject.DTSTART, sample?.dtstart, Component.VJOURNAL.name)
+ @Test fun check_DTSTART_TIMEZONE() = insertRetrieveAssertString(JtxICalObject.DTSTART_TIMEZONE, sample?.dtstartTimezone, Component.VJOURNAL.name)
+ @Test fun check_DTEND() = insertRetrieveAssertLong(JtxICalObject.DTEND, sample?.dtend, Component.VJOURNAL.name)
+ @Test fun check_DTEND_TIMEZONE() = insertRetrieveAssertString(JtxICalObject.DTEND_TIMEZONE, sample?.dtendTimezone, Component.VJOURNAL.name)
+ @Test fun check_STATUS() = insertRetrieveAssertString(JtxICalObject.STATUS, sample?.status, Component.VJOURNAL.name)
+ @Test fun check_CLASSIFICATION() = insertRetrieveAssertString(JtxICalObject.CLASSIFICATION, sample?.classification, Component.VJOURNAL.name)
+ @Test fun check_URL() = insertRetrieveAssertString(JtxICalObject.URL, sample?.url, Component.VJOURNAL.name)
+ @Test fun check_CONTACT() = insertRetrieveAssertString(JtxICalObject.CONTACT, sample?.contact, Component.VJOURNAL.name)
+ @Test fun check_GEO_LAT() = insertRetrieveAssertDouble(JtxICalObject.GEO_LAT, sample?.geoLat, Component.VJOURNAL.name)
+ @Test fun check_GEO_LONG() = insertRetrieveAssertDouble(JtxICalObject.GEO_LONG, sample?.geoLong, Component.VJOURNAL.name)
+ @Test fun check_LOCATION() = insertRetrieveAssertString(JtxICalObject.LOCATION, sample?.location, Component.VJOURNAL.name)
+ @Test fun check_LOCATION_ALTREP() = insertRetrieveAssertString(JtxICalObject.LOCATION_ALTREP, sample?.locationAltrep, Component.VJOURNAL.name)
+ @Test fun check_PERCENT() = insertRetrieveAssertInt(JtxICalObject.PERCENT, sample?.percent, Component.VJOURNAL.name)
+ @Test fun check_PRIORITY() = insertRetrieveAssertInt(JtxICalObject.PRIORITY, sample?.priority, Component.VJOURNAL.name)
+ @Test fun check_DUE() = insertRetrieveAssertLong(JtxICalObject.DUE, sample?.due, Component.VJOURNAL.name)
+ @Test fun check_DUE_TIMEZONE() = insertRetrieveAssertString(JtxICalObject.DUE_TIMEZONE, sample?.dueTimezone, Component.VJOURNAL.name)
+ @Test fun check_COMPLETED() = insertRetrieveAssertLong(JtxICalObject.COMPLETED, sample?.completed, Component.VJOURNAL.name)
+ @Test fun check_COMPLETED_TIMEZONE() = insertRetrieveAssertString(JtxICalObject.COMPLETED_TIMEZONE, sample?.completedTimezone, Component.VJOURNAL.name)
+ @Test fun check_DURATION() = insertRetrieveAssertString(JtxICalObject.DURATION, sample?.duration, Component.VJOURNAL.name)
+ @Test fun check_RRULE() = insertRetrieveAssertString(JtxICalObject.RRULE, sample?.rrule, Component.VJOURNAL.name)
+ @Test fun check_RDATE() = insertRetrieveAssertString(JtxICalObject.RDATE, sample?.rdate, Component.VJOURNAL.name)
+ @Test fun check_EXDATE() = insertRetrieveAssertString(JtxICalObject.EXDATE, sample?.exdate, Component.VJOURNAL.name)
+ @Test fun check_RECURID() = insertRetrieveAssertString(JtxICalObject.RECURID, sample?.recurid, Component.VJOURNAL.name)
+ @Test fun check_UID() = insertRetrieveAssertString(JtxICalObject.UID, sample?.uid, Component.VJOURNAL.name)
+ @Test fun check_CREATED() = insertRetrieveAssertLong(JtxICalObject.CREATED, sample?.created, Component.VJOURNAL.name)
+ @Test fun check_DTSTAMP() = insertRetrieveAssertLong(JtxICalObject.DTSTAMP, sample?.dtstamp, Component.VJOURNAL.name)
+ @Test fun check_LAST_MODIFIED() = insertRetrieveAssertLong(JtxICalObject.LAST_MODIFIED, sample?.lastModified, Component.VJOURNAL.name)
+ @Test fun check_SEQUENCE() = insertRetrieveAssertLong(JtxICalObject.SEQUENCE, sample?.sequence, Component.VJOURNAL.name)
+ @Test fun check_COLOR() = insertRetrieveAssertInt(JtxICalObject.COLOR, sample?.color, Component.VJOURNAL.name)
+ @Test fun check_DIRTY() = insertRetrieveAssertBoolean(JtxICalObject.DIRTY, sample?.dirty, Component.VJOURNAL.name)
+ @Test fun check_DELETED() = insertRetrieveAssertBoolean(JtxICalObject.DELETED,sample?.deleted, Component.VJOURNAL.name)
+ @Test fun check_FILENAME() = insertRetrieveAssertString(JtxICalObject.FILENAME, sample?.fileName, Component.VJOURNAL.name)
+ @Test fun check_ETAG() = insertRetrieveAssertString(JtxICalObject.ETAG, sample?.eTag, Component.VJOURNAL.name)
+ @Test fun check_SCHEDULETAG() = insertRetrieveAssertString(JtxICalObject.SCHEDULETAG, sample?.scheduleTag, Component.VJOURNAL.name)
+ @Test fun check_FLAGS() = insertRetrieveAssertInt(JtxICalObject.FLAGS, sample?.flags, Component.VJOURNAL.name)
+
+
+ private fun insertRetrieveAssertString(field: String, fieldContent: String?, component: String) {
+
+ assertNotNull(fieldContent) // fieldContent should not be null, check if the testcase was built correctly
+
+ val cv = ContentValues().apply {
+ put(field, fieldContent)
+ put(JtxICalObject.COMPONENT, component)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ client.query(uri, null, null, null, null)?.use {
+ val itemCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, itemCV)
+ assertEquals(fieldContent, itemCV.getAsString(field))
+ }
+ }
+
+ private fun insertRetrieveAssertBoolean(field: String, fieldContent: Boolean?, component: String) {
+
+ assertNotNull(fieldContent) // fieldContent should not be null, check if the testcase was built correctly
+
+ val cv = ContentValues().apply {
+ put(field, fieldContent)
+ put(JtxICalObject.COMPONENT, component)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ client.query(uri, null, null, null, null)?.use {
+ val itemCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, itemCV)
+ assertEquals(fieldContent, itemCV.getAsBoolean(field))
+ }
+ }
+
+ private fun insertRetrieveAssertLong(field: String, fieldContent: Long?, component: String) {
+
+ assertNotNull(fieldContent) // fieldContent should not be null, check if the testcase was built correctly
+
+ val cv = ContentValues().apply {
+ put(field, fieldContent)
+ put(JtxICalObject.COMPONENT, component)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ client.query(uri, null, null, null, null)?.use {
+ val itemCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, itemCV)
+ assertEquals(fieldContent, itemCV.getAsLong(field))
+ }
+ }
+
+ private fun insertRetrieveAssertInt(field: String, fieldContent: Int?, component: String) {
+
+ assertNotNull(fieldContent) // fieldContent should not be null, check if the testcase was built correctly
+
+ val cv = ContentValues().apply {
+ put(field, fieldContent)
+ put(JtxICalObject.COMPONENT, component)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ client.query(uri, null, null, null, null)?.use {
+ val itemCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, itemCV)
+ assertEquals(fieldContent, itemCV.getAsInteger(field))
+ }
+ }
+
+ private fun insertRetrieveAssertDouble(field: String, fieldContent: Double?, component: String) {
+
+ assertNotNull(fieldContent) // fieldContent should not be null, check if the testcase was built correctly
+
+ val cv = ContentValues().apply {
+ put(field, fieldContent)
+ put(JtxICalObject.COMPONENT, component)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ client.query(uri, null, null, null, null)?.use {
+ val itemCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, itemCV)
+ assertEquals(fieldContent, itemCV.getAsDouble(field))
+ }
+ }
+
+ @Test
+ fun assertComment() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val comment = at.bitfire.ical4android.JtxICalObject.Comment(
+ text = "comment",
+ altrep = "Kommentar",
+ language = "de",
+ other = "X-OTHER:Test"
+ )
+
+ val commentCV = ContentValues().apply {
+ put(JtxContract.JtxComment.TEXT, comment.text)
+ put(JtxContract.JtxComment.ALTREP, comment.altrep)
+ put(JtxContract.JtxComment.LANGUAGE, comment.language)
+ put(JtxContract.JtxComment.OTHER, comment.other)
+ put(JtxContract.JtxComment.ICALOBJECT_ID, id)
+ }
+
+ val commentUri = client.insert(JtxContract.JtxComment.CONTENT_URI.asSyncAdapter(testAccount), commentCV)!!
+ client.query(commentUri, null, null, null, null)?.use {
+ val retrievedCommentCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedCommentCV)
+ assertEquals(comment.text, retrievedCommentCV.getAsString(JtxContract.JtxComment.TEXT))
+ assertEquals(comment.altrep, retrievedCommentCV.getAsString(JtxContract.JtxComment.ALTREP))
+ assertEquals(comment.language, retrievedCommentCV.getAsString(JtxContract.JtxComment.LANGUAGE))
+ assertEquals(comment.other, retrievedCommentCV.getAsString(JtxContract.JtxComment.OTHER))
+ }
+ }
+
+ @Test
+ fun assertResource() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val resource = at.bitfire.ical4android.JtxICalObject.Resource(
+ text = "projector",
+ altrep = "Projektor",
+ language = "de",
+ other = "X-OTHER:Test"
+ )
+
+ val resourceCV = ContentValues().apply {
+ put(JtxContract.JtxResource.TEXT, resource.text)
+ put(JtxContract.JtxResource.LANGUAGE, resource.language)
+ put(JtxContract.JtxResource.OTHER, resource.other)
+ put(JtxContract.JtxResource.ICALOBJECT_ID, id)
+ }
+
+ val resourceUri = client.insert(JtxContract.JtxResource.CONTENT_URI.asSyncAdapter(testAccount), resourceCV)!!
+ client.query(resourceUri, null, null, null, null)?.use {
+ val retrievedResourceCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedResourceCV)
+ assertEquals(resource.text, retrievedResourceCV.getAsString(JtxContract.JtxResource.TEXT))
+ assertEquals(resource.language, retrievedResourceCV.getAsString(JtxContract.JtxResource.LANGUAGE))
+ assertEquals(resource.other, retrievedResourceCV.getAsString(JtxContract.JtxResource.OTHER))
+ }
+ }
+
+ @Test
+ fun assertAttendee() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val attendee = at.bitfire.ical4android.JtxICalObject.Attendee(
+ caladdress = "jtx@techbee.at",
+ cutype = JtxContract.JtxAttendee.Cutype.INDIVIDUAL.name,
+ member = "group",
+ partstat = "0",
+ role = JtxContract.JtxAttendee.Role.`REQ-PARTICIPANT`.name,
+ rsvp = false,
+ delegatedfrom = "jtx@techbee.at",
+ delegatedto = "jtx@techbee.at",
+ sentby = "jtx@techbee.at",
+ cn = "jtx Board",
+ dir = "dir",
+ language = "de",
+ other = "X-OTHER:Test"
+ )
+
+ val attendeeCV = ContentValues().apply {
+ put(JtxContract.JtxAttendee.CALADDRESS, attendee.caladdress)
+ put(JtxContract.JtxAttendee.CUTYPE, attendee.cutype)
+ put(JtxContract.JtxAttendee.MEMBER, attendee.member)
+ put(JtxContract.JtxAttendee.PARTSTAT, attendee.partstat)
+ put(JtxContract.JtxAttendee.ROLE, attendee.role)
+ put(JtxContract.JtxAttendee.RSVP, attendee.rsvp)
+ put(JtxContract.JtxAttendee.DELEGATEDFROM, attendee.delegatedfrom)
+ put(JtxContract.JtxAttendee.DELEGATEDTO, attendee.delegatedto)
+ put(JtxContract.JtxAttendee.SENTBY, attendee.sentby)
+ put(JtxContract.JtxAttendee.CN, attendee.cn)
+ put(JtxContract.JtxAttendee.DIR, attendee.dir)
+ put(JtxContract.JtxAttendee.LANGUAGE, attendee.language)
+ put(JtxContract.JtxAttendee.OTHER, attendee.other)
+ put(JtxContract.JtxAttendee.ICALOBJECT_ID, id)
+ }
+
+ val attendeeUri = client.insert(JtxContract.JtxAttendee.CONTENT_URI.asSyncAdapter(testAccount), attendeeCV)!!
+ client.query(attendeeUri, null, null, null, null)?.use {
+ val retrievedAttendeeCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAttendeeCV)
+ assertEquals(attendee.caladdress, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.CALADDRESS))
+ assertEquals(attendee.cutype, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.CUTYPE))
+ assertEquals(attendee.member, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.MEMBER))
+ assertEquals(attendee.partstat, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.PARTSTAT))
+ assertEquals(attendee.role, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.ROLE))
+ assertEquals(attendee.rsvp, retrievedAttendeeCV.getAsBoolean(JtxContract.JtxAttendee.RSVP))
+ assertEquals(attendee.delegatedfrom, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.DELEGATEDFROM))
+ assertEquals(attendee.delegatedto, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.DELEGATEDTO))
+ assertEquals(attendee.sentby, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.SENTBY))
+ assertEquals(attendee.cn, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.CN))
+ assertEquals(attendee.dir, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.DIR))
+ assertEquals(attendee.language, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.LANGUAGE))
+ assertEquals(attendee.other, retrievedAttendeeCV.getAsString(JtxContract.JtxAttendee.OTHER))
+ }
+ }
+
+ @Test
+ fun assertCategory() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val category = at.bitfire.ical4android.JtxICalObject.Category(
+ text = "projector",
+ )
+
+ val categoryCV = ContentValues().apply {
+ put(JtxContract.JtxCategory.TEXT, category.text)
+ put(JtxContract.JtxCategory.ICALOBJECT_ID, id)
+ }
+
+ val categoryUri = client.insert(JtxContract.JtxCategory.CONTENT_URI.asSyncAdapter(testAccount), categoryCV)!!
+ client.query(categoryUri, null, null, null, null)?.use {
+ val retrievedCategoryCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedCategoryCV)
+ assertEquals(category.text, retrievedCategoryCV.getAsString(JtxContract.JtxCategory.TEXT))
+ }
+ }
+
+ @Test
+ fun assertAttachment_without_binary() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val attachment = at.bitfire.ical4android.JtxICalObject.Attachment(
+ uri = "https://jtx.techbee.at/sample.pdf",
+ fmttype = "application/pdf",
+ other = "X-OTHER:other",
+ )
+
+ val attachmentCV = ContentValues().apply {
+ put(JtxContract.JtxAttachment.URI, attachment.uri)
+ put(JtxContract.JtxAttachment.FMTTYPE, attachment.fmttype)
+ put(JtxContract.JtxAttachment.OTHER, attachment.other)
+ put(JtxContract.JtxAttachment.ICALOBJECT_ID, id)
+ }
+
+ val attachmentUri = client.insert(JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(testAccount), attachmentCV)!!
+ client.query(attachmentUri, null, null, null, null)?.use {
+ val retrievedAttachmentCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAttachmentCV)
+ assertEquals(attachment.uri, retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.URI))
+ assertEquals(attachment.fmttype, retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.FMTTYPE))
+ assertEquals(attachment.other, retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.OTHER))
+ }
+ }
+
+
+ @Test
+ fun assertAttachment_without_binary_and_uri() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val attachment = at.bitfire.ical4android.JtxICalObject.Attachment(
+ fmttype = "application/pdf"
+ )
+
+ val attachmentCV = ContentValues().apply {
+ put(JtxContract.JtxAttachment.FMTTYPE, attachment.fmttype)
+ put(JtxContract.JtxAttachment.ICALOBJECT_ID, id)
+ }
+
+ val attachmentUri = client.insert(JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(testAccount), attachmentCV)!!
+ client.query(attachmentUri, null, null, null, null)?.use {
+ val retrievedAttachmentCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAttachmentCV)
+ assertEquals(attachment.fmttype, retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.FMTTYPE))
+ assertNotNull(retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.URI))
+ }
+
+ val textIn = "jtx Board rulz"
+ val pfd = client.openFile(attachmentUri, "w", null)
+ ParcelFileDescriptor.AutoCloseOutputStream(pfd).write(textIn.toByteArray())
+
+ val pfd2 = client.openFile(attachmentUri, "r", null)
+ val textCompare = String(ParcelFileDescriptor.AutoCloseInputStream(pfd2).readBytes())
+
+ Assert.assertEquals(textIn, textCompare)
+
+ }
+
+ @Test
+ fun assertAttachment_with_binary() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val attachment = at.bitfire.ical4android.JtxICalObject.Attachment(
+ //uri = "https://jtx.techbee.at/sample.pdf",
+ binary = "anR4IEJvYXJk",
+ fmttype = "application/pdf",
+ other = "X-OTHER:other",
+ )
+
+ val attachmentCV = ContentValues().apply {
+ //put(JtxContract.JtxAttachment.URI, attachment.uri)
+ put(JtxContract.JtxAttachment.BINARY, attachment.binary)
+ put(JtxContract.JtxAttachment.FMTTYPE, attachment.fmttype)
+ put(JtxContract.JtxAttachment.OTHER, attachment.other)
+ put(JtxContract.JtxAttachment.ICALOBJECT_ID, id)
+ }
+
+ val attachmentUri = client.insert(JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(testAccount), attachmentCV)!!
+ client.query(attachmentUri, null, null, null, null)?.use {
+ val retrievedAttachmentCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAttachmentCV)
+ assertTrue(retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.URI).startsWith("content://")) // binary was replaced by content uri
+ assertNull(retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.BINARY))
+ assertEquals(attachment.fmttype, retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.FMTTYPE))
+ assertEquals(attachment.other, retrievedAttachmentCV.getAsString(JtxContract.JtxAttachment.OTHER))
+ }
+ }
+
+ @Test
+ fun assertRelatedto() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val relatedto = at.bitfire.ical4android.JtxICalObject.RelatedTo(
+ text = "1635164243187-3fd0f89e-d017-471e-a046-71ff1844d58e@at.techbee.jtx",
+ reltype = JtxContract.JtxRelatedto.Reltype.CHILD.name,
+ other = "X-OTHER: other"
+ )
+
+ val relatedtoCV = ContentValues().apply {
+ put(JtxContract.JtxRelatedto.TEXT, relatedto.text)
+ put(JtxContract.JtxRelatedto.RELTYPE, relatedto.reltype)
+ put(JtxContract.JtxRelatedto.OTHER, relatedto.other)
+ put(JtxContract.JtxRelatedto.ICALOBJECT_ID, id)
+ }
+
+ val relatedtoUri = client.insert(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(testAccount), relatedtoCV)!!
+ client.query(relatedtoUri, null, null, null, null)?.use {
+ val retrievedRelatedtoCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedRelatedtoCV)
+ assertEquals(relatedto.text, retrievedRelatedtoCV.getAsString(JtxContract.JtxRelatedto.TEXT))
+ assertEquals(relatedto.reltype, retrievedRelatedtoCV.getAsString(JtxContract.JtxRelatedto.RELTYPE))
+ assertEquals(relatedto.other, retrievedRelatedtoCV.getAsString(JtxContract.JtxRelatedto.OTHER))
+ }
+ }
+
+ @Test
+ fun assertAlarm_basic() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val alarm = at.bitfire.ical4android.JtxICalObject.Alarm(
+ action = JtxContract.JtxAlarm.AlarmAction.AUDIO.name,
+ description = "desc",
+ summary = "summary",
+ duration = "PT15M",
+ triggerTime = 1641557428506L,
+ repeat = "4",
+ attach = "ftp://example.com/pub/sounds/bell-01.aud",
+ other = "X-OTHER: other",
+ )
+
+ val alarmCV = ContentValues().apply {
+ put(JtxContract.JtxAlarm.ACTION, alarm.action)
+ put(JtxContract.JtxAlarm.DESCRIPTION, alarm.description)
+ put(JtxContract.JtxAlarm.SUMMARY, alarm.summary)
+ put(JtxContract.JtxAlarm.DURATION, alarm.duration)
+ put(JtxContract.JtxAlarm.TRIGGER_TIME, alarm.triggerTime)
+ put(JtxContract.JtxAlarm.REPEAT, alarm.repeat)
+ put(JtxContract.JtxAlarm.ATTACH, alarm.attach)
+ put(JtxContract.JtxAlarm.OTHER, alarm.other)
+ put(JtxContract.JtxAlarm.ICALOBJECT_ID, id)
+ }
+
+ val alarmUri = client.insert(JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(testAccount), alarmCV)!!
+ client.query(alarmUri, null, null, null, null)?.use {
+ val retrievedAlarmCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAlarmCV)
+ assertEquals(alarm.action, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.ACTION))
+ assertEquals(alarm.description, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.DESCRIPTION))
+ assertEquals(alarm.summary, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.SUMMARY))
+ assertEquals(alarm.duration, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.DURATION))
+ assertEquals(alarm.repeat, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.REPEAT))
+ assertEquals(alarm.attach, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.ATTACH))
+ assertEquals(alarm.other, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.OTHER))
+ }
+ }
+
+
+ @Test
+ fun assertAlarm_trigger_duration() {
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VTODO.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val alarm = at.bitfire.ical4android.JtxICalObject.Alarm(
+ action = JtxContract.JtxAlarm.AlarmAction.DISPLAY.name,
+ description = "desc",
+ triggerRelativeDuration = "-PT5M",
+ triggerRelativeTo = JtxContract.JtxAlarm.AlarmRelativeTo.START.name
+ )
+
+ val alarmCV = ContentValues().apply {
+ put(JtxContract.JtxAlarm.ACTION, alarm.action)
+ put(JtxContract.JtxAlarm.DESCRIPTION, alarm.description)
+ put(JtxContract.JtxAlarm.TRIGGER_RELATIVE_DURATION, alarm.triggerRelativeDuration)
+ put(JtxContract.JtxAlarm.TRIGGER_RELATIVE_TO, alarm.triggerRelativeTo)
+ put(JtxContract.JtxAlarm.ICALOBJECT_ID, id)
+ }
+
+ val alarmUri = client.insert(JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(testAccount), alarmCV)!!
+ client.query(alarmUri, null, null, null, null)?.use {
+ val retrievedAlarmCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAlarmCV)
+ assertEquals(alarm.action, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.ACTION))
+ assertEquals(alarm.description, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.DESCRIPTION))
+ assertEquals(alarm.triggerRelativeTo, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.TRIGGER_RELATIVE_TO))
+ assertEquals(alarm.triggerRelativeDuration, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.TRIGGER_RELATIVE_DURATION))
+ }
+ }
+
+ @Test
+ fun assertAlarm_trigger_time_withoutTZ() {
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VTODO.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val alarm = at.bitfire.ical4android.JtxICalObject.Alarm(
+ action = JtxContract.JtxAlarm.AlarmAction.DISPLAY.name,
+ description = "desc",
+ triggerTime = 1641557428506L
+ )
+
+ val alarmCV = ContentValues().apply {
+ put(JtxContract.JtxAlarm.ACTION, alarm.action)
+ put(JtxContract.JtxAlarm.DESCRIPTION, alarm.description)
+ put(JtxContract.JtxAlarm.TRIGGER_TIME, alarm.triggerTime)
+ put(JtxContract.JtxAlarm.ICALOBJECT_ID, id)
+ }
+
+ val alarmUri = client.insert(JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(testAccount), alarmCV)!!
+ client.query(alarmUri, null, null, null, null)?.use {
+ val retrievedAlarmCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAlarmCV)
+ assertEquals(alarm.action, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.ACTION))
+ assertEquals(alarm.description, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.DESCRIPTION))
+ assertEquals(alarm.triggerTime, retrievedAlarmCV.getAsLong(JtxContract.JtxAlarm.TRIGGER_TIME))
+ }
+ }
+
+ @Test
+ fun assertAlarm_trigger_time_UTC() {
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VTODO.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val alarm = at.bitfire.ical4android.JtxICalObject.Alarm(
+ action = JtxContract.JtxAlarm.AlarmAction.DISPLAY.name,
+ description = "desc",
+ triggerTime = 1641557428506L,
+ triggerTimezone = "UTC"
+ )
+
+ val alarmCV = ContentValues().apply {
+ put(JtxContract.JtxAlarm.ACTION, alarm.action)
+ put(JtxContract.JtxAlarm.DESCRIPTION, alarm.description)
+ put(JtxContract.JtxAlarm.TRIGGER_TIME, alarm.triggerTime)
+ put(JtxContract.JtxAlarm.TRIGGER_TIMEZONE, alarm.triggerTimezone)
+ put(JtxContract.JtxAlarm.ICALOBJECT_ID, id)
+ }
+
+ val alarmUri = client.insert(JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(testAccount), alarmCV)!!
+ client.query(alarmUri, null, null, null, null)?.use {
+ val retrievedAlarmCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAlarmCV)
+ assertEquals(alarm.action, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.ACTION))
+ assertEquals(alarm.description, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.DESCRIPTION))
+ assertEquals(alarm.triggerTime, retrievedAlarmCV.getAsLong(JtxContract.JtxAlarm.TRIGGER_TIME))
+ assertEquals(alarm.triggerTimezone, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.TRIGGER_TIMEZONE))
+ }
+ }
+
+
+ @Test
+ fun assertAlarm_trigger_time_Vienna() {
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VTODO.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val alarm = at.bitfire.ical4android.JtxICalObject.Alarm(
+ action = "DISPLAY",
+ description = "desc",
+ triggerTime = 1641557428506L,
+ triggerTimezone = "Europe/Vienna"
+ )
+
+ val alarmCV = ContentValues().apply {
+ put(JtxContract.JtxAlarm.ACTION, alarm.action)
+ put(JtxContract.JtxAlarm.DESCRIPTION, alarm.description)
+ put(JtxContract.JtxAlarm.TRIGGER_TIME, alarm.triggerTime)
+ put(JtxContract.JtxAlarm.TRIGGER_TIMEZONE, alarm.triggerTimezone)
+ put(JtxContract.JtxAlarm.ICALOBJECT_ID, id)
+ }
+
+ val alarmUri = client.insert(JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(testAccount), alarmCV)!!
+ client.query(alarmUri, null, null, null, null)?.use {
+ val retrievedAlarmCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedAlarmCV)
+ assertEquals(alarm.action, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.ACTION))
+ assertEquals(alarm.description, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.DESCRIPTION))
+ assertEquals(alarm.triggerTime, retrievedAlarmCV.getAsLong(JtxContract.JtxAlarm.TRIGGER_TIME))
+ assertEquals(alarm.triggerTimezone, retrievedAlarmCV.getAsString(JtxContract.JtxAlarm.TRIGGER_TIMEZONE))
+ }
+ }
+
+
+ @Test
+ fun assertUnknown() {
+
+ val cv = ContentValues().apply {
+ put(JtxICalObject.COMPONENT, Component.VJOURNAL.name)
+ put(JtxICalObject.ICALOBJECT_COLLECTIONID, collection?.id)
+ }
+ val uri = client.insert(JtxICalObject.CONTENT_URI.asSyncAdapter(testAccount), cv)!!
+ val id = uri.lastPathSegment
+
+ val unknown = at.bitfire.ical4android.JtxICalObject.Unknown(
+ value = "X-PROP:my value"
+ )
+
+ val unknownCV = ContentValues().apply {
+ put(JtxContract.JtxUnknown.UNKNOWN_VALUE, unknown.value)
+ put(JtxContract.JtxUnknown.ICALOBJECT_ID, id)
+ }
+
+ val unknownUri = client.insert(JtxContract.JtxUnknown.CONTENT_URI.asSyncAdapter(testAccount), unknownCV)!!
+ client.query(unknownUri, null, null, null, null)?.use {
+ val retrievedUnknownCV = ContentValues()
+ it.moveToFirst()
+ DatabaseUtils.cursorRowToContentValues(it, retrievedUnknownCV)
+ assertEquals(unknown.value, retrievedUnknownCV.getAsString(JtxContract.JtxUnknown.UNKNOWN_VALUE))
+ }
+ }
+
+
+
+
+ /** TESTS TO READ A FILE; INSERT IT IN THE CONTENT PROVIDER; READ THE CONTENT PROVIDER AND THEN COMPARE IF THE CONTENT OF THE GENERATED ICAL IS STILL THE SAME AS FROM THE SERVER */
+
+ // VTODO
+ @Test fun check_input_equals_output_vtodo_most_fields1() = compare_properties("jtx/vtodo/most-fields1.ics", listOf("EXDATE", "RDATE"))
+ @Test fun check_input_equals_output_vtodo_most_fields2() = compare_properties("jtx/vtodo/most-fields2.ics", null)
+ @Test fun check_input_equals_output_vtodo_utf8() = compare_properties("jtx/vtodo/utf8.ics", null)
+ @Test fun check_input_equals_output_vtodo_rfc5545_sample() = compare_properties("jtx/vtodo/rfc5545-sample1.ics", null)
+ @Test fun check_input_equals_output_vtodo_empty_priority() = compare_properties("jtx/vtodo/empty-priority.ics", null)
+ @Test fun check_input_equals_output_vtodo_latin1() = compare_properties("jtx/vtodo/latin1.ics", null)
+
+ // VJOURNAL
+ @Test fun check_input_equals_output_vjournal_default_example() = compare_properties("jtx/vjournal/default-example.ics", null)
+ @Test fun check_input_equals_output_vjournal_default_example_note() = compare_properties("jtx/vjournal/default-example-note.ics", null)
+ @Test fun check_input_equals_output_vjournal_utf8() = compare_properties("jtx/vjournal/utf8.ics", null)
+ @Test fun check_input_equals_output_vjournal_two_line() = compare_properties("jtx/vjournal/two-line-description-without-crlf.ics", listOf("CREATED", "LAST-MODIFIED", "DTSTART")) // expected:<CREATED;VALUE=DATE-TIME:20131008T205713Z > but was:<CREATED:20131008T205713Z is ignored here; expected:<LAST-MODIFIED;VALUE=DATE-TIME:20131008T205740> but was:<LAST-MODIFIED:20131008T205740Z is ignored here as the actual is the correct result
+ //@Test fun check_input_equals_output_vjournal_two_journals() = compare_properties("jtx/vjournal/two-events-without-exceptions.ics", null) // this file contains two events, the direct comparison through the given method would not work
+ @Test fun check_input_equals_output_vjournal_recurring() = compare_properties("jtx/vjournal/recurring.ics", null)
+ //@Test fun check_input_equals_output_vjournal_outlook_theoretical() = compare_properties("jtx/vjournal/outlook-theoretical.ics", null) // includes custom timezones, ignored for now
+ @Test fun check_input_equals_output_vjournal_outlook_theoretical2() = compare_properties("jtx/vjournal/outlook-theoretical2.ics", null)
+ @Test fun check_input_equals_output_vjournal_latin1() = compare_properties("jtx/vjournal/latin1.ics", null)
+ @Test fun check_input_equals_output_vjournal_journalonthatday() = compare_properties("jtx/vjournal/journal-on-that-day.ics", null)
+ //@Test fun check_input_equals_output_vjournal_dst_only_vtimezone() = compare_properties("jtx/vjournal/dst-only-vtimezone.ics", null) // includes custom timezones, ignored for now
+ @Test fun check_input_equals_output_vjournal_all_day() = compare_properties("jtx/vjournal/all-day.ics", null)
+
+ /**
+ * This function takes a file asserts if the ICalendar is the same before and after processing with getIncomingIcal and getOutgoingIcal
+ * @param filename the filename to be processed
+ * @param exceptions a list of property names that should not cause the assertion to fail (DTSTAMP is taken in any case)
+ */
+ private fun compare_properties(filename: String, exceptions: List<String>?) {
+
+ val iCalIn = getIncomingIcal(filename)
+ val iCalOut = getOutgoingIcal(filename)
+
+ //assertEquals(iCalIn.components[0].getProperty(Component.VTODO), iCalOut.components[0].getProperty(Component.VTODO))
+
+ // there should only be one component for VJOURNAL and VTODO!
+ for(i in 0 until iCalIn.components.size) {
+
+ iCalIn.components[i].properties.forEach { inProp ->
+
+ if(inProp.name == "DTSTAMP" || exceptions?.contains(inProp.name) == true)
+ return@forEach
+ val outProp = iCalOut.components[i].properties.getProperty<Property>(inProp.name)
+ assertEquals(inProp, outProp)
+ }
+ }
+ }
+
+
+ /**
+ * This function takes a file and returns the parsed ical4j Calendar object
+ * @param filename: The filename of the ics-file
+ * @return the ICalendar with the parsed information from the file
+ */
+ private fun getIncomingIcal(filename: String): Calendar {
+
+ val stream = javaClass.classLoader!!.getResourceAsStream(filename)
+ val reader = InputStreamReader(stream, Charsets.UTF_8)
+
+ val iCalIn = ICalendar.fromReader(reader)
+
+ stream.close()
+ reader.close()
+
+ return iCalIn
+ }
+
+ /**
+ * This function takes a filename and creates a JtxICalObject.
+ * Then it uses the object to create an ical4j Calendar again.
+ * @param filename: The filename of the ics-file
+ * @return The ICalendar after applying all functionalities of JtxICalObject.fromReader(...)
+ */
+ private fun getOutgoingIcal(filename: String): Calendar {
+
+ val stream = javaClass.classLoader!!.getResourceAsStream(filename)
+ val reader = InputStreamReader(stream, Charsets.UTF_8)
+ val iCalObject = at.bitfire.ical4android.JtxICalObject.fromReader(reader, collection!!)
+
+ val os = ByteArrayOutputStream()
+
+ iCalObject[0].write(os)
+
+ val iCalOut = ICalendar.fromReader(os.toByteArray().inputStream().reader())
+
+ stream.close()
+ reader.close()
+
+ return iCalOut
+ }
+}
diff --git a/src/androidTest/java/at/bitfire/ical4android/LocaleNonWesternDigitsTest.kt b/src/androidTest/java/at/bitfire/ical4android/LocaleNonWesternDigitsTest.kt
index a8c33b4..81f0783 100644
--- a/src/androidTest/java/at/bitfire/ical4android/LocaleNonWesternDigitsTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/LocaleNonWesternDigitsTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import net.fortuna.ical4j.model.property.TzOffsetFrom
diff --git a/src/androidTest/java/at/bitfire/ical4android/MiscUtilsAndroidTest.kt b/src/androidTest/java/at/bitfire/ical4android/MiscUtilsAndroidTest.kt
index 5c51c94..67016e1 100644
--- a/src/androidTest/java/at/bitfire/ical4android/MiscUtilsAndroidTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/MiscUtilsAndroidTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/androidTest/java/at/bitfire/ical4android/TestUtils.kt b/src/androidTest/java/at/bitfire/ical4android/TestUtils.kt
index 57bbd94..3387920 100644
--- a/src/androidTest/java/at/bitfire/ical4android/TestUtils.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/TestUtils.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/androidTest/java/at/bitfire/ical4android/UnknownPropertyTest.kt b/src/androidTest/java/at/bitfire/ical4android/UnknownPropertyTest.kt
index 6ba223c..b4a8be5 100644
--- a/src/androidTest/java/at/bitfire/ical4android/UnknownPropertyTest.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/UnknownPropertyTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import androidx.test.filters.SmallTest
diff --git a/src/androidTest/java/at/bitfire/ical4android/impl/TestCalendar.kt b/src/androidTest/java/at/bitfire/ical4android/impl/TestCalendar.kt
index e127ec6..19d37cb 100644
--- a/src/androidTest/java/at/bitfire/ical4android/impl/TestCalendar.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/impl/TestCalendar.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android.impl
diff --git a/src/androidTest/java/at/bitfire/ical4android/impl/TestEvent.kt b/src/androidTest/java/at/bitfire/ical4android/impl/TestEvent.kt
index 3fd19c9..f9c9571 100644
--- a/src/androidTest/java/at/bitfire/ical4android/impl/TestEvent.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/impl/TestEvent.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android.impl
diff --git a/src/androidTest/java/at/bitfire/ical4android/impl/TestJtxCollection.kt b/src/androidTest/java/at/bitfire/ical4android/impl/TestJtxCollection.kt
new file mode 100644
index 0000000..5a69060
--- /dev/null
+++ b/src/androidTest/java/at/bitfire/ical4android/impl/TestJtxCollection.kt
@@ -0,0 +1,51 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android.impl
+
+import android.accounts.Account
+import android.content.ContentProviderClient
+import at.bitfire.ical4android.JtxCollection
+import at.bitfire.ical4android.JtxCollectionFactory
+import at.bitfire.ical4android.JtxICalObject
+import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues
+import at.techbee.jtx.JtxContract
+import java.util.*
+
+class TestJtxCollection(
+ account: Account,
+ provider: ContentProviderClient,
+ id: Long
+): JtxCollection<JtxICalObject>(account, provider, TestJtxIcalObject.Factory, id) {
+
+ /**
+ * Queries [JtxContract.JtxICalObject] from this collection. Adds a WHERE clause that restricts the
+ * query to [JtxContract.JtxCollection.ID] = [id].
+ * @param _where selection
+ * @param _whereArgs arguments for selection
+ * @return events from this calendar which match the selection
+ */
+ fun queryICalObjects(_where: String? = null, _whereArgs: Array<String>? = null): List<JtxICalObject> {
+ val where = "(${_where ?: "1"}) AND ${JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID} = ?"
+ val whereArgs = (_whereArgs ?: arrayOf()) + id.toString()
+
+ val iCalObjects = LinkedList<JtxICalObject>()
+ client.query(jtxSyncURI(), null, where, whereArgs, null)?.use { cursor ->
+ while (cursor.moveToNext())
+ iCalObjects += TestJtxIcalObject.Factory.fromProvider(this, cursor.toValues())
+ }
+ return iCalObjects
+ }
+
+
+ object Factory: JtxCollectionFactory<TestJtxCollection> {
+
+ override fun newInstance(
+ account: Account,
+ client: ContentProviderClient,
+ id: Long
+ ): TestJtxCollection = TestJtxCollection(account, client, id)
+ }
+
+}
diff --git a/src/androidTest/java/at/bitfire/ical4android/impl/TestJtxIcalObject.kt b/src/androidTest/java/at/bitfire/ical4android/impl/TestJtxIcalObject.kt
new file mode 100644
index 0000000..57615fb
--- /dev/null
+++ b/src/androidTest/java/at/bitfire/ical4android/impl/TestJtxIcalObject.kt
@@ -0,0 +1,19 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android.impl
+
+import android.content.ContentValues
+import at.bitfire.ical4android.*
+
+class TestJtxIcalObject(testCollection: JtxCollection<JtxICalObject>): JtxICalObject(testCollection) {
+
+ object Factory: JtxICalObjectFactory<JtxICalObject> {
+
+ override fun fromProvider(
+ collection: JtxCollection<JtxICalObject>,
+ values: ContentValues
+ ): JtxICalObject = TestJtxIcalObject(collection)
+ }
+}
diff --git a/src/androidTest/java/at/bitfire/ical4android/impl/TestTask.kt b/src/androidTest/java/at/bitfire/ical4android/impl/TestTask.kt
index ddb2d66..6c03dab 100644
--- a/src/androidTest/java/at/bitfire/ical4android/impl/TestTask.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/impl/TestTask.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android.impl
diff --git a/src/androidTest/java/at/bitfire/ical4android/impl/TestTaskList.kt b/src/androidTest/java/at/bitfire/ical4android/impl/TestTaskList.kt
index 6ae8479..0f4ef1e 100644
--- a/src/androidTest/java/at/bitfire/ical4android/impl/TestTaskList.kt
+++ b/src/androidTest/java/at/bitfire/ical4android/impl/TestTaskList.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android.impl
diff --git a/src/androidTest/resources/jtx/vjournal/all-day.ics b/src/androidTest/resources/jtx/vjournal/all-day.ics
new file mode 100644
index 0000000..1300862
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/all-day.ics
@@ -0,0 +1,10 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//hacksw/handcal//NONSGML v1.0//EN
+BEGIN:VJOURNAL
+UID:all-day-1day@example.com
+DTSTAMP:20140101T000000Z
+DTSTART;VALUE=DATE:19970714
+SUMMARY:All-Day 1 Day
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vjournal/default-example-note.ics b/src/androidTest/resources/jtx/vjournal/default-example-note.ics
new file mode 100644
index 0000000..054cf68
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/default-example-note.ics
@@ -0,0 +1,18 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//hacksw/handcal//NONSGML v1.0//EN
+BEGIN:VJOURNAL
+UID:19970901T130000Z-123405@example.com
+DTSTAMP:19970901T130000Z
+SUMMARY:Staff meeting minutes
+DESCRIPTION:1. Staff meeting: Participants include Joe\,
+ Lisa\, and Bob. Aurora project plans were reviewed.
+ There is currently no budget reserves for this project.
+ Lisa will escalate to management. Next meeting on Tuesday.\n
+ 2. Telephone Conference: ABC Corp. sales representative
+ called to discuss new printer. Promised to get us a demo by
+ Friday.\n3. Henry Miller (Handsoff Insurance): Car was
+ totaled by tree. Is looking into a loaner car. 555-2323
+ (tel).
+END:VJOURNAL
+END:VCALENDAR \ No newline at end of file
diff --git a/src/androidTest/resources/jtx/vjournal/default-example.ics b/src/androidTest/resources/jtx/vjournal/default-example.ics
new file mode 100644
index 0000000..0a88565
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/default-example.ics
@@ -0,0 +1,19 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//hacksw/handcal//NONSGML v1.0//EN
+BEGIN:VJOURNAL
+UID:19970901T130000Z-123405@example.com
+DTSTAMP:19970901T130000Z
+DTSTART;VALUE=DATE:19970317
+SUMMARY:Staff meeting minutes
+DESCRIPTION:1. Staff meeting: Participants include Joe\,
+ Lisa\, and Bob. Aurora project plans were reviewed.
+ There is currently no budget reserves for this project.
+ Lisa will escalate to management. Next meeting on Tuesday.\n
+ 2. Telephone Conference: ABC Corp. sales representative
+ called to discuss new printer. Promised to get us a demo by
+ Friday.\n3. Henry Miller (Handsoff Insurance): Car was
+ totaled by tree. Is looking into a loaner car. 555-2323
+ (tel).
+END:VJOURNAL
+END:VCALENDAR \ No newline at end of file
diff --git a/src/androidTest/resources/jtx/vjournal/dst-only-vtimezone.ics b/src/androidTest/resources/jtx/vjournal/dst-only-vtimezone.ics
new file mode 100644
index 0000000..e798232
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/dst-only-vtimezone.ics
@@ -0,0 +1,21 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+DTSTART:20180325T030000
+TZNAME:CEST
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VJOURNAL
+UID:only-dst@example.com
+DTSTAMP:20180329T084939Z
+DTSTART;TZID=Europe/Berlin:20180403T090000
+DTEND;TZID=Europe/Berlin:20180403T101500
+SUMMARY:Sample Event
+CREATED:20180329T084939Z
+LAST-MODIFIED:20180329T084939Z
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vjournal/journal-on-that-day.ics b/src/androidTest/resources/jtx/vjournal/journal-on-that-day.ics
new file mode 100644
index 0000000..20d8889
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/journal-on-that-day.ics
@@ -0,0 +1,11 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//hacksw/handcal//NONSGML v1.0//EN
+BEGIN:VJOURNAL
+UID:event-on-that-day@example.com
+DTSTAMP:19970714T170000Z
+ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
+DTSTART;VALUE=DATE:19970714
+SUMMARY:Bastille Day Party
+END:VJOURNAL
+END:VCALENDAR \ No newline at end of file
diff --git a/src/androidTest/resources/jtx/vjournal/latin1.ics b/src/androidTest/resources/jtx/vjournal/latin1.ics
new file mode 100644
index 0000000..969b2bf
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/latin1.ics
@@ -0,0 +1,9 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VJOURNAL
+UID:latin1@ical4android.EventTest
+DTSTAMP:20150826T132300Z
+SUMMARY:
+DTSTART:20131009T170000T
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vjournal/outlook-theoretical.ics b/src/androidTest/resources/jtx/vjournal/outlook-theoretical.ics
new file mode 100644
index 0000000..1f372bb
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/outlook-theoretical.ics
@@ -0,0 +1,71 @@
+BEGIN:VCALENDAR
+METHOD:PUBLISH
+PRODID:Microsoft Exchange Server 2010
+VERSION:2.0
+X-WR-CALNAME:Calendar
+BEGIN:VTIMEZONE
+TZID:China Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T000000
+TZOFFSETFROM:+0800
+TZOFFSETTO:+0800
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VTIMEZONE
+TZID:W. Europe Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T030000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T020000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VTIMEZONE
+TZID:India Standard Time
+BEGIN:STANDARD
+DTSTART:16010101T000000
+TZOFFSETFROM:+0530
+TZOFFSETTO:+0530
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:16010101T000000
+TZOFFSETFROM:+0530
+TZOFFSETTO:+0530
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VJOURNAL
+DESCRIPTION:\n.............................................................
+ .................................\n\n\n
+RRULE:FREQ=WEEKLY;UNTIL=20191218T080000Z;INTERVAL=1;BYDAY=WE;WKST=MO
+UID:040000008200E00074C5B7101A82E00800000000907682BE2B88D501000000000000000
+ 01000000077F6CDA3B634104B9BC1ED539119F558
+SUMMARY:Weekly Meeting - Test
+DTSTART;TZID=W. Europe Standard Time:20191023T090000
+CLASS:PUBLIC
+PRIORITY:5
+DTSTAMP:20191119T090349Z
+TRANSP:OPAQUE
+STATUS:DRAFT
+SEQUENCE:1
+LOCATION:Skype-Besprechung
+X-MICROSOFT-CDO-APPT-SEQUENCE:1
+X-MICROSOFT-CDO-BUSYSTATUS:BUSY
+X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
+X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
+X-MICROSOFT-CDO-IMPORTANCE:1
+X-MICROSOFT-CDO-INSTTYPE:1
+X-MICROSOFT-DONOTFORWARDMEETING:FALSE
+X-MICROSOFT-DISALLOW-COUNTER:FALSE
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vjournal/outlook-theoretical2.ics b/src/androidTest/resources/jtx/vjournal/outlook-theoretical2.ics
new file mode 100644
index 0000000..eea376d
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/outlook-theoretical2.ics
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+METHOD:PUBLISH
+PRODID:Microsoft Exchange Server 2010
+VERSION:2.0
+X-WR-CALNAME:Calendar
+BEGIN:VJOURNAL
+DESCRIPTION:\n.............................................................
+ .................................\n\n\n
+RRULE:FREQ=WEEKLY;UNTIL=20191218T080000Z;INTERVAL=1;BYDAY=WE;WKST=MO
+UID:040000008200E00074C5B7101A82E00800000000907682BE2B88D501000000000000000
+ 01000000077F6CDA3B634104B9BC1ED539119F558
+SUMMARY:Weekly Meeting - Test
+DTSTART:20191023T090000
+CLASS:PUBLIC
+DTSTAMP:20191119T090349Z
+TRANSP:OPAQUE
+STATUS:DRAFT
+SEQUENCE:1
+LOCATION:Skype-Besprechung
+X-MICROSOFT-CDO-APPT-SEQUENCE:1
+X-MICROSOFT-CDO-BUSYSTATUS:BUSY
+X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
+X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
+X-MICROSOFT-CDO-IMPORTANCE:1
+X-MICROSOFT-CDO-INSTTYPE:1
+X-MICROSOFT-DONOTFORWARDMEETING:FALSE
+X-MICROSOFT-DISALLOW-COUNTER:FALSE
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vjournal/recurring.ics b/src/androidTest/resources/jtx/vjournal/recurring.ics
new file mode 100644
index 0000000..c071186
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/recurring.ics
@@ -0,0 +1,9 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VJOURNAL
+UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc
+SUMMARY:Recurring event with one exception
+RRULE:FREQ=DAILY;COUNT=5
+DTSTART;VALUE=DATE:20150501
+END:VJOURNAL
+END:VCALENDAR \ No newline at end of file
diff --git a/src/androidTest/resources/jtx/vjournal/two-events-without-exceptions.ics b/src/androidTest/resources/jtx/vjournal/two-events-without-exceptions.ics
new file mode 100644
index 0000000..ff7d460
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/two-events-without-exceptions.ics
@@ -0,0 +1,15 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VJOURNAL
+UID:event1
+DTSTAMP:20150826T132300Z
+SUMMARY:Event 1
+DTSTART:20131009T170000T
+END:VJOURNAL
+BEGIN:VJOURNAL
+UID:event2
+DTSTAMP:20150826T132300Z
+SUMMARY:Event 2
+DTSTART:20131009T180000T
+END:VJOURNAL
+END:VCALENDAR \ No newline at end of file
diff --git a/src/androidTest/resources/jtx/vjournal/two-line-description-without-crlf.ics b/src/androidTest/resources/jtx/vjournal/two-line-description-without-crlf.ics
new file mode 100644
index 0000000..5b6a49a
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/two-line-description-without-crlf.ics
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:Blabla
+BEGIN:VJOURNAL
+CLASS:PUBLIC
+CREATED;VALUE=DATE-TIME:20131008T205713
+LAST-MODIFIED;VALUE=DATE-TIME:20131008T205740
+SUMMARY:online Anmeldung
+DESCRIPTION:http://www.tgbornheim.de/index.php?sessionid=&page=&id=&sportce
+ ntergroup=&day=6
+UID:b99c41704b
+DTSTART;VALUE=DATE-TIME;TZID=Europe/Berlin:20131019T060000
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vjournal/utf8.ics b/src/androidTest/resources/jtx/vjournal/utf8.ics
new file mode 100644
index 0000000..8f39860
--- /dev/null
+++ b/src/androidTest/resources/jtx/vjournal/utf8.ics
@@ -0,0 +1,13 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VJOURNAL
+UID:utf8@ical4android.EventTest
+DTSTAMP:20150826T132300Z
+SUMMARY:© äö — üß
+DESCRIPTION:Test Description
+LOCATION:中华人民共和国
+COLOR:aliceblue
+ATTENDEE;CN=Cyrus Daboo;EMAIL=cyrus@example.com:mailto:opaque-token-1234@example.com
+X-UNKNOWN-PROP;param1=xxx:Unknown Value
+END:VJOURNAL
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vtodo/empty-priority.ics b/src/androidTest/resources/jtx/vtodo/empty-priority.ics
new file mode 100644
index 0000000..69ad1ae
--- /dev/null
+++ b/src/androidTest/resources/jtx/vtodo/empty-priority.ics
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+X-WR-CALNAME:Wieseclan
+PRODID:-//The Horde Project//Horde iCalendar Library//EN
+BEGIN:VTODO
+UID:1d58e8e0-4fac-4632-8d43-ec79757490d2.1519164997000
+SUMMARY:Alex Geschenkeliste
+DESCRIPTION:[ ] Tip Toi Rund um die Uhr\n[ ] Ravensburger Wort für Wort\n[
+ ] Hula Hoop Reifen\n[ ] Pfeil und Bogen mit Zielscheibe\n[ ] Tip Toi
+ Spielwelt Autorennen
+PRIORITY:
+STATUS:NEEDS-ACTION
+END:VTODO
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vtodo/latin1.ics b/src/androidTest/resources/jtx/vtodo/latin1.ics
new file mode 100644
index 0000000..b3502de
--- /dev/null
+++ b/src/androidTest/resources/jtx/vtodo/latin1.ics
@@ -0,0 +1,9 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VTODO
+UID:latin1@ical4android.EventTest
+DTSTAMP:20150826T132300Z
+SUMMARY:
+DTSTART:20131009T170000T
+END:VTODO
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vtodo/most-fields1.ics b/src/androidTest/resources/jtx/vtodo/most-fields1.ics
new file mode 100644
index 0000000..ff4dbb0
--- /dev/null
+++ b/src/androidTest/resources/jtx/vtodo/most-fields1.ics
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//ABC Corporation//NONSGML My Product//EN
+BEGIN:VTODO
+SEQUENCE:1
+UID:most-fields1@example.com
+LOCATION;ALTREP="http://xyzcorp.com/conf-rooms/f123.vcf":
+ Conference Room - F123\, Bldg. 002
+GEO:37.386013;-122.082932
+DESCRIPTION:Meeting to provide technical review for "Phoenix"
+ design.\nHappy Face Conference Room. Phoenix design team
+ MUST attend this meeting.\nRSVP to team leader.
+URL:http://example.com/pub/calendars/jsmith/mytime.ics
+ORGANIZER:http://example.com/principals/jsmith
+PRIORITY:1
+CLASS:CONFIDENTIAL
+STATUS:IN-PROCESS
+PERCENT-COMPLETE:25
+DTSTART;VALUE=DATE:20100101
+DUE;VALUE=DATE:20101001
+CATEGORIES:Test,Sample
+RRULE:FREQ=YEARLY;INTERVAL=2
+EXDATE;VALUE=DATE:20120101
+EXDATE;VALUE=DATE:20140101,20180101
+RDATE;VALUE=DATE:20100310,20100315
+RDATE;VALUE=DATE:20100810
+RELATED-TO;RELTYPE=SIBLING:most-fields2@example.com
+X-UNKNOWN-PROP;param1=xxx:Unknown Value
+CREATED:19960329T133000Z
+LAST-MODIFIED:19960817T133000Z
+END:VTODO
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vtodo/most-fields2.ics b/src/androidTest/resources/jtx/vtodo/most-fields2.ics
new file mode 100644
index 0000000..f6c7215
--- /dev/null
+++ b/src/androidTest/resources/jtx/vtodo/most-fields2.ics
@@ -0,0 +1,9 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//ABC Corporation//NONSGML My Product//EN
+BEGIN:VTODO
+UID:most-fields2@example.com
+DTSTART:20100101T101010Z
+DURATION:P4DT3H2M1S
+END:VTODO
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vtodo/rfc5545-sample1.ics b/src/androidTest/resources/jtx/vtodo/rfc5545-sample1.ics
new file mode 100644
index 0000000..18d53df
--- /dev/null
+++ b/src/androidTest/resources/jtx/vtodo/rfc5545-sample1.ics
@@ -0,0 +1,22 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//ABC Corporation//NONSGML My Product//EN
+BEGIN:VTODO
+DTSTAMP:19980130T134500Z
+SEQUENCE:2
+UID:uid4@example.com
+ORGANIZER:mailto:unclesam@example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:jqpublic@example.com
+DUE:19980415T000000
+STATUS:NEEDS-ACTION
+SUMMARY:Submit Income Taxes
+BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;VALUE=DATE-TIME:19980403T120000Z
+ATTACH;FMTTYPE=audio/basic:http://example.com/pub/audio-
+ files/ssbanner.aud
+REPEAT:4
+DURATION:PT1H
+END:VALARM
+END:VTODO
+END:VCALENDAR
diff --git a/src/androidTest/resources/jtx/vtodo/utf8.ics b/src/androidTest/resources/jtx/vtodo/utf8.ics
new file mode 100644
index 0000000..67aaa1e
--- /dev/null
+++ b/src/androidTest/resources/jtx/vtodo/utf8.ics
@@ -0,0 +1,10 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VTODO
+UID:utf8@ical4android.TaskTest
+DTSTAMP:20150826T132300Z
+SUMMARY:© äö — üß
+LOCATION:中华人民共和国
+DTSTART:20131009T170000T
+END:VTODO
+END:VCALENDAR
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 8f4f2c5..442d0ed 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -25,10 +25,15 @@
<uses-permission android:name="org.tasks.permission.READ_TASKS" />
<uses-permission android:name="org.tasks.permission.WRITE_TASKS" />
+ <!-- read/write tasks & journals (jtx Board) -->
+ <uses-permission android:name="at.techbee.jtx.permission.READ" />
+ <uses-permission android:name="at.techbee.jtx.permission.WRITE" />
+
<application />
<queries>
<!-- task providers -->
+ <package android:name="at.techbee.jtx" />
<package android:name="org.dmfs.tasks" />
<package android:name="org.tasks" />
</queries>
diff --git a/src/main/java/at/bitfire/ical4android/AndroidCalendar.kt b/src/main/java/at/bitfire/ical4android/AndroidCalendar.kt
index e0ac1bb..5736bde 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidCalendar.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidCalendar.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidCalendarFactory.kt b/src/main/java/at/bitfire/ical4android/AndroidCalendarFactory.kt
index 071ba9f..a0ba2b2 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidCalendarFactory.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidCalendarFactory.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidEvent.kt b/src/main/java/at/bitfire/ical4android/AndroidEvent.kt
index 3fed6b6..ec5981b 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidEvent.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidEvent.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidEventFactory.kt b/src/main/java/at/bitfire/ical4android/AndroidEventFactory.kt
index cd5f7e4..3136956 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidEventFactory.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidEventFactory.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidTask.kt b/src/main/java/at/bitfire/ical4android/AndroidTask.kt
index c776321..84af609 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidTask.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidTask.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidTaskFactory.kt b/src/main/java/at/bitfire/ical4android/AndroidTaskFactory.kt
index 0e35d7b..41d3f70 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidTaskFactory.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidTaskFactory.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidTaskList.kt b/src/main/java/at/bitfire/ical4android/AndroidTaskList.kt
index dd37586..4cd784b 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidTaskList.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidTaskList.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AndroidTaskListFactory.kt b/src/main/java/at/bitfire/ical4android/AndroidTaskListFactory.kt
index bc62a60..f7f33bd 100644
--- a/src/main/java/at/bitfire/ical4android/AndroidTaskListFactory.kt
+++ b/src/main/java/at/bitfire/ical4android/AndroidTaskListFactory.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/AttendeeMappings.kt b/src/main/java/at/bitfire/ical4android/AttendeeMappings.kt
index d6dea03..69d4baf 100644
--- a/src/main/java/at/bitfire/ical4android/AttendeeMappings.kt
+++ b/src/main/java/at/bitfire/ical4android/AttendeeMappings.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import android.content.ContentValues
diff --git a/src/main/java/at/bitfire/ical4android/BatchOperation.kt b/src/main/java/at/bitfire/ical4android/BatchOperation.kt
index 392b303..a7b881e 100644
--- a/src/main/java/at/bitfire/ical4android/BatchOperation.kt
+++ b/src/main/java/at/bitfire/ical4android/BatchOperation.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/CalendarStorageException.kt b/src/main/java/at/bitfire/ical4android/CalendarStorageException.kt
index 6ab3a67..85f463b 100644
--- a/src/main/java/at/bitfire/ical4android/CalendarStorageException.kt
+++ b/src/main/java/at/bitfire/ical4android/CalendarStorageException.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/Css3Color.kt b/src/main/java/at/bitfire/ical4android/Css3Color.kt
index b1b363f..57df5a2 100644
--- a/src/main/java/at/bitfire/ical4android/Css3Color.kt
+++ b/src/main/java/at/bitfire/ical4android/Css3Color.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/DateUtils.kt b/src/main/java/at/bitfire/ical4android/DateUtils.kt
index 52f8a98..c9ceffa 100644
--- a/src/main/java/at/bitfire/ical4android/DateUtils.kt
+++ b/src/main/java/at/bitfire/ical4android/DateUtils.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/Event.kt b/src/main/java/at/bitfire/ical4android/Event.kt
index c432511..d96ac73 100644
--- a/src/main/java/at/bitfire/ical4android/Event.kt
+++ b/src/main/java/at/bitfire/ical4android/Event.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/ICalPreprocessor.kt b/src/main/java/at/bitfire/ical4android/ICalPreprocessor.kt
index d43baae..ae7293c 100644
--- a/src/main/java/at/bitfire/ical4android/ICalPreprocessor.kt
+++ b/src/main/java/at/bitfire/ical4android/ICalPreprocessor.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import net.fortuna.ical4j.model.Calendar
diff --git a/src/main/java/at/bitfire/ical4android/ICalendar.kt b/src/main/java/at/bitfire/ical4android/ICalendar.kt
index aaffa1c..619f193 100644
--- a/src/main/java/at/bitfire/ical4android/ICalendar.kt
+++ b/src/main/java/at/bitfire/ical4android/ICalendar.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/Ical4Android.kt b/src/main/java/at/bitfire/ical4android/Ical4Android.kt
index f97752f..806e981 100644
--- a/src/main/java/at/bitfire/ical4android/Ical4Android.kt
+++ b/src/main/java/at/bitfire/ical4android/Ical4Android.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/InvalidCalendarException.kt b/src/main/java/at/bitfire/ical4android/InvalidCalendarException.kt
index 00f1467..6c0da58 100644
--- a/src/main/java/at/bitfire/ical4android/InvalidCalendarException.kt
+++ b/src/main/java/at/bitfire/ical4android/InvalidCalendarException.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/JtxCollection.kt b/src/main/java/at/bitfire/ical4android/JtxCollection.kt
new file mode 100644
index 0000000..a10ee42
--- /dev/null
+++ b/src/main/java/at/bitfire/ical4android/JtxCollection.kt
@@ -0,0 +1,265 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import android.accounts.Account
+import android.content.ContentProviderClient
+import android.content.ContentUris
+import android.content.ContentValues
+import android.content.Context
+import android.net.Uri
+import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues
+import at.techbee.jtx.JtxContract
+import at.techbee.jtx.JtxContract.asSyncAdapter
+import java.util.*
+
+open class JtxCollection<out T: JtxICalObject>(val account: Account,
+ val client: ContentProviderClient,
+ private val iCalObjectFactory: JtxICalObjectFactory<JtxICalObject>,
+ val id: Long) {
+
+ companion object {
+
+ fun create(account: Account, client: ContentProviderClient, values: ContentValues): Uri =
+ client.insert(JtxContract.JtxCollection.CONTENT_URI.asSyncAdapter(account), values)?: throw CalendarStorageException("Couldn't create JTX Collection")
+
+ fun<T: JtxCollection<JtxICalObject>> find(account: Account, client: ContentProviderClient, context: Context, factory: JtxCollectionFactory<T>, where: String?, whereArgs: Array<String>?): List<T> {
+ val collections = LinkedList<T>()
+ client.query(JtxContract.JtxCollection.CONTENT_URI.asSyncAdapter(account), null, where, whereArgs, null)?.use { cursor ->
+ while (cursor.moveToNext()) {
+ val values = cursor.toValues()
+ val collection = factory.newInstance(account, client, values.getAsLong(JtxContract.JtxCollection.ID))
+ collection.populate(values, context)
+ collections += collection
+ }
+ }
+ return collections
+ }
+ }
+
+
+ var url: String? = null
+ var displayname: String? = null
+ var syncstate: String? = null
+
+ var context: Context? = null
+
+
+ fun delete() {
+ client.delete(ContentUris.withAppendedId(JtxContract.JtxCollection.CONTENT_URI.asSyncAdapter(account), id), null, null)
+ }
+
+ fun update(values: ContentValues) {
+
+ client.update(ContentUris.withAppendedId(JtxContract.JtxCollection.CONTENT_URI.asSyncAdapter(account), id), values, null, null)
+ }
+
+ protected fun populate(values: ContentValues, context: Context) {
+ url = values.getAsString(JtxContract.JtxCollection.URL)
+ displayname = values.getAsString(JtxContract.JtxCollection.DISPLAYNAME)
+ syncstate = values.getAsString(JtxContract.JtxCollection.SYNC_VERSION)
+
+ this.context = context
+ }
+
+
+ /**
+ * Builds the JtxICalObject content uri with appended parameters for account and syncadapter
+ * @return the Uri for the JtxICalObject Sync in the content provider of jtx Board
+ */
+ fun jtxSyncURI(): Uri =
+ JtxContract.JtxICalObject.CONTENT_URI.buildUpon()
+ .appendQueryParameter(JtxContract.ACCOUNT_NAME, account.name)
+ .appendQueryParameter(JtxContract.ACCOUNT_TYPE, account.type)
+ .appendQueryParameter(JtxContract.CALLER_IS_SYNCADAPTER, "true")
+ .build()
+
+
+ /**
+ * @return a list of content values of the deleted jtxICalObjects
+ */
+ fun queryDeletedICalObjects(): List<ContentValues> {
+ val values = mutableListOf<ContentValues>()
+ client.query(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), null, "${JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID} = ? AND ${JtxContract.JtxICalObject.DELETED} = ?", arrayOf(id.toString(), "1"), null).use { cursor ->
+ Ical4Android.log.fine("findDeleted: found ${cursor?.count} deleted records in ${account.name}")
+ while (cursor?.moveToNext() == true) {
+ values.add(cursor.toValues())
+ }
+ }
+ return values
+ }
+
+
+ /**
+ * @return a list of content values of the dirty jtxICalObjects
+ */
+ fun queryDirtyICalObjects(): List<ContentValues> {
+ val values = mutableListOf<ContentValues>()
+ client.query(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), null, "${JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID} = ? AND ${JtxContract.JtxICalObject.DIRTY} = ?", arrayOf(id.toString(), "1"), null).use { cursor ->
+ Ical4Android.log.fine("findDirty: found ${cursor?.count} dirty records in ${account.name}")
+ while (cursor?.moveToNext() == true) {
+ values.add(cursor.toValues())
+ }
+ }
+ return values
+ }
+
+ /**
+ * @param [filename] of the entry that should be retrieved as content values
+ * @return Content Values of the found item with the given filename or null if the result was empty or more than 1
+ */
+ fun queryByFilename(filename: String): ContentValues? {
+ client.query(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), null, "${JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID} = ? AND ${JtxContract.JtxICalObject.FILENAME} = ?", arrayOf(id.toString(), filename), null).use { cursor ->
+ Ical4Android.log.fine("queryByFilename: found ${cursor?.count} records in ${account.name}")
+ if (cursor?.count != 1)
+ return null
+ cursor.moveToFirst()
+ return cursor.toValues()
+ }
+ }
+
+ /**
+ * updates the flags of all entries in the collection with the given flag
+ * @param [flags] to be set
+ * @return the number of records that were updated
+ */
+ fun updateSetFlags(flags: Int): Int {
+ val values = ContentValues(1)
+ values.put(JtxContract.JtxICalObject.FLAGS, flags)
+ return client.update(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), values, "${JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID} = ? AND ${JtxContract.JtxICalObject.DIRTY} = ?", arrayOf(id.toString(), "0"))
+ }
+
+ /**
+ * deletes all entries with the given flags
+ * @param [flags] of the entries that should be deleted
+ * @return the number of deleted records
+ */
+ fun deleteByFlags(flags: Int) =
+ client.delete(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), "${JtxContract.JtxICalObject.DIRTY} = ? AND ${JtxContract.JtxICalObject.FLAGS} = ? ", arrayOf("0", flags.toString()))
+
+ /**
+ * Updates the eTag value of all entries within a collection to the given eTag
+ * @param [eTag] to be set (or null)
+ */
+ fun updateSetETag(eTag: String?) {
+ val values = ContentValues(1)
+ if(eTag == null)
+ values.putNull(JtxContract.JtxICalObject.ETAG)
+ else
+ values.put(JtxContract.JtxICalObject.ETAG, eTag)
+ client.update(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), values, "${JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID} = ?", arrayOf(id.toString()))
+ }
+
+
+ /**
+ * This function updates the Related-To relations in jtx Board.
+ * STEP 1: find entries to update (all entries with 0 in related-to). When inserting the relation, we only know the parent iCalObjectId and the related UID (but not the related iCalObjectId).
+ * In this step we search for all Related-To relations where the LINKEDICALOBJEC_ID is not set, resolve it through the UID and set it.
+ * STEP 2/3: jtx Board saves the relations in both directions, the Parent has an entry for his Child, the Child has an entry for his Parent. Step 2 and Step 3 make sure, that the Child-Parent pair is
+ * present in both directions.
+ */
+ fun updateRelatedTo() {
+ // STEP 1: first find entries to update (all entries with 0 in related-to)
+ client.query(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxRelatedto.TEXT), "${JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID} = ?", arrayOf("0"), null).use {
+ while(it?.moveToNext() == true) {
+ val uid2upddate = it.getString(0)
+
+ client.query(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxICalObject.ID), "${JtxContract.JtxICalObject.UID} = ?", arrayOf(uid2upddate), null).use { idOfthisUidCursor ->
+ if (idOfthisUidCursor?.moveToFirst() == true) {
+ val idOfthisUid = idOfthisUidCursor.getLong(0)
+
+ val updateContentValues = ContentValues()
+ updateContentValues.put(
+ JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID,
+ idOfthisUid
+ )
+
+ client.update(
+ JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(
+ account
+ ),
+ updateContentValues,
+ "${JtxContract.JtxRelatedto.TEXT} = ? AND ${JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID} = ?",
+ arrayOf(uid2upddate, "0")
+ )
+ }
+ }
+ }
+ }
+
+
+ // STEP 2: query all related to that are linking their PARENTS and check if they also have the opposite relationship entered, if not, then add it
+ client.query(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxRelatedto.ICALOBJECT_ID, JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID, JtxContract.JtxRelatedto.RELTYPE), "${JtxContract.JtxRelatedto.RELTYPE} = ?", arrayOf(JtxContract.JtxRelatedto.Reltype.PARENT.name), null).use {
+ cursorAllLinkedParents ->
+ while (cursorAllLinkedParents?.moveToNext() == true) {
+ val icalObjectId = cursorAllLinkedParents.getString(0)
+ val linkedIcalObjectId = cursorAllLinkedParents.getString(1)
+
+ client.query(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxRelatedto.ICALOBJECT_ID, JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID, JtxContract.JtxRelatedto.RELTYPE), "${JtxContract.JtxRelatedto.ICALOBJECT_ID} = ? AND ${JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID} = ? AND ${JtxContract.JtxRelatedto.RELTYPE} = ?", arrayOf(linkedIcalObjectId.toString(), icalObjectId.toString(), JtxContract.JtxRelatedto.Reltype.CHILD.name), null).use { cursor ->
+ // if the query does not bring any result, then we insert the opposite relationship
+ if (cursor?.moveToFirst() == false) {
+
+ //get the UID of the linked entry
+ client.query(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxICalObject.UID), "${JtxContract.JtxICalObject.ID} = ?", arrayOf(linkedIcalObjectId.toString()), null).use {
+ foundIcalObjectCursor ->
+
+ if (foundIcalObjectCursor?.moveToFirst() == true) {
+ val uid = foundIcalObjectCursor.getString(0)
+
+ val cv = ContentValues().apply {
+ put(JtxContract.JtxRelatedto.ICALOBJECT_ID, linkedIcalObjectId)
+ put(JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID, icalObjectId)
+ put(JtxContract.JtxRelatedto.RELTYPE, JtxContract.JtxRelatedto.Reltype.CHILD.name)
+ put(JtxContract.JtxRelatedto.TEXT, uid)
+ }
+
+ client.insert(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), cv)
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ // STEP 3: query all related to that are linking their CHILD and check if they also have the opposite relationship entered, if not, then add it
+ client.query(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxRelatedto.ICALOBJECT_ID, JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID, JtxContract.JtxRelatedto.RELTYPE), "${JtxContract.JtxRelatedto.RELTYPE} = ?", arrayOf(JtxContract.JtxRelatedto.Reltype.CHILD.name), null).use {
+ cursorAllLinkedParents ->
+ while (cursorAllLinkedParents?.moveToNext() == true) {
+
+ val icalObjectId = cursorAllLinkedParents.getLong(0)
+ val linkedIcalObjectId = cursorAllLinkedParents.getLong(1)
+
+ client.query(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxRelatedto.ICALOBJECT_ID, JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID, JtxContract.JtxRelatedto.RELTYPE), "${JtxContract.JtxRelatedto.ICALOBJECT_ID} = ? AND ${JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID} = ? AND ${JtxContract.JtxRelatedto.RELTYPE} = ?", arrayOf(linkedIcalObjectId.toString(), icalObjectId.toString(), JtxContract.JtxRelatedto.Reltype.PARENT.name), null).use {
+ cursor ->
+
+ // if the query does not bring any result, then we insert the opposite relationship
+ if (cursor?.moveToFirst() == false) {
+
+ //get the UID of the linked entry
+ client.query(JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(account), arrayOf(JtxContract.JtxICalObject.UID), "${JtxContract.JtxICalObject.ID} = ?", arrayOf(linkedIcalObjectId.toString()), null).use {
+ foundIcalObjectCursor ->
+
+ if(foundIcalObjectCursor?.moveToFirst() == true) {
+
+ val uid = foundIcalObjectCursor.getString(0)
+
+ val cv = ContentValues().apply {
+ put(JtxContract.JtxRelatedto.ICALOBJECT_ID, linkedIcalObjectId)
+ put(JtxContract.JtxRelatedto.LINKEDICALOBJECT_ID, icalObjectId)
+ put(JtxContract.JtxRelatedto.RELTYPE, JtxContract.JtxRelatedto.Reltype.PARENT.name)
+ put(JtxContract.JtxRelatedto.TEXT, uid)
+ }
+
+ client.insert(JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(account), cv)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/at/bitfire/ical4android/JtxCollectionFactory.kt b/src/main/java/at/bitfire/ical4android/JtxCollectionFactory.kt
new file mode 100644
index 0000000..177b2d1
--- /dev/null
+++ b/src/main/java/at/bitfire/ical4android/JtxCollectionFactory.kt
@@ -0,0 +1,14 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import android.accounts.Account
+import android.content.ContentProviderClient
+
+interface JtxCollectionFactory<out T: JtxCollection<JtxICalObject>> {
+
+ fun newInstance(account: Account, client: ContentProviderClient, id: Long): T
+
+} \ No newline at end of file
diff --git a/src/main/java/at/bitfire/ical4android/JtxICalObject.kt b/src/main/java/at/bitfire/ical4android/JtxICalObject.kt
new file mode 100644
index 0000000..1b8a5f1
--- /dev/null
+++ b/src/main/java/at/bitfire/ical4android/JtxICalObject.kt
@@ -0,0 +1,1796 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import android.content.ContentUris
+import android.content.ContentValues
+import android.net.ParseException
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import android.util.Base64
+import android.util.Log
+import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues
+import at.techbee.jtx.JtxContract
+import at.techbee.jtx.JtxContract.JtxICalObject.TZ_ALLDAY
+import at.techbee.jtx.JtxContract.asSyncAdapter
+import net.fortuna.ical4j.data.CalendarOutputter
+import net.fortuna.ical4j.data.ParserException
+import net.fortuna.ical4j.model.*
+import net.fortuna.ical4j.model.Calendar
+import net.fortuna.ical4j.model.Date
+import net.fortuna.ical4j.model.component.VAlarm
+import net.fortuna.ical4j.model.component.VJournal
+import net.fortuna.ical4j.model.component.VToDo
+import net.fortuna.ical4j.model.parameter.*
+import net.fortuna.ical4j.model.property.*
+import java.io.*
+import java.net.URI
+import java.net.URISyntaxException
+import java.time.format.DateTimeParseException
+import java.util.*
+import java.util.TimeZone
+import java.util.logging.Level
+
+open class JtxICalObject(
+ val collection: JtxCollection<JtxICalObject>
+) {
+
+ var id: Long = 0L
+ lateinit var component: String
+ var summary: String? = null
+ var description: String? = null
+ var dtstart: Long? = null
+ var dtstartTimezone: String? = null
+ var dtend: Long? = null
+ var dtendTimezone: String? = null
+
+ var classification: String? = null
+ var status: String? = null
+
+ var priority: Int? = null
+
+ var due: Long? = null // VTODO only!
+ var dueTimezone: String? = null //VTODO only!
+ var completed: Long? = null // VTODO only!
+ var completedTimezone: String? = null //VTODO only!
+ var duration: String? = null //VTODO only!
+
+ var percent: Int? = null
+ var url: String? = null
+ var contact: String? = null
+ var geoLat: Double? = null
+ var geoLong: Double? = null
+ var location: String? = null
+ var locationAltrep: String? = null
+
+ var uid: String = "${System.currentTimeMillis()}-${UUID.randomUUID()}@at.techbee.jtx"
+
+ var created: Long = System.currentTimeMillis()
+ var dtstamp: Long = System.currentTimeMillis()
+ var lastModified: Long = System.currentTimeMillis()
+ var sequence: Long = 0
+
+ var color: Int? = null
+
+ var rrule: String? = null //only for recurring events, see https://tools.ietf.org/html/rfc5545#section-3.8.5.3
+ var exdate: String? = null //only for recurring events, see https://tools.ietf.org/html/rfc5545#section-3.8.5.1
+ var rdate: String? = null //only for recurring events, see https://tools.ietf.org/html/rfc5545#section-3.8.5.2
+ var recurid: String? = null //only for recurring events, see https://tools.ietf.org/html/rfc5545#section-3.8.5
+
+ var rstatus: String? = null
+
+ var collectionId: Long = collection.id
+
+ var dirty: Boolean = true
+ var deleted: Boolean = false
+
+ var fileName: String? = null
+ var eTag: String? = null
+ var scheduleTag: String? = null
+ var flags: Int = 0
+
+ var categories: MutableList<Category> = mutableListOf()
+ var attachments: MutableList<Attachment> = mutableListOf()
+ var attendees: MutableList<Attendee> = mutableListOf()
+ var comments: MutableList<Comment> = mutableListOf()
+ var organizer: Organizer? = null
+ var resources: MutableList<Resource> = mutableListOf()
+ var relatedTo: MutableList<RelatedTo> = mutableListOf()
+ var alarms: MutableList<Alarm> = mutableListOf()
+ var unknown: MutableList<Unknown> = mutableListOf()
+
+
+
+
+ data class Category(
+ var categoryId: Long = 0L,
+ var text: String? = null,
+ var language: String? = null,
+ var other: String? = null
+ )
+
+ data class Attachment(
+ var attachmentId: Long = 0L,
+ var uri: String? = null,
+ var binary: String? = null,
+ var fmttype: String? = null,
+ var other: String? = null,
+ var filename: String? = null,
+ var extension: String? = null,
+ var filesize: Long? = null
+ )
+
+ data class Comment(
+ var commentId: Long = 0L,
+ var text: String? = null,
+ var altrep: String? = null,
+ var language: String? = null,
+ var other: String? = null
+ )
+
+ data class RelatedTo(
+ var relatedtoId: Long = 0L,
+ var text: String? = null,
+ var reltype: String? = null,
+ var other: String? = null
+ )
+
+ data class Attendee(
+ var attendeeId: Long = 0L,
+ var caladdress: String? = null,
+ var cutype: String? = JtxContract.JtxAttendee.Cutype.INDIVIDUAL.name,
+ var member: String? = null,
+ var role: String? = JtxContract.JtxAttendee.Role.`REQ-PARTICIPANT`.name,
+ var partstat: String? = null,
+ var rsvp: Boolean? = null,
+ var delegatedto: String? = null,
+ var delegatedfrom: String? = null,
+ var sentby: String? = null,
+ var cn: String? = null,
+ var dir: String? = null,
+ var language: String? = null,
+ var other: String? = null
+ )
+
+ data class Resource(
+ var resourceId: Long = 0L,
+ var text: String? = null,
+ var altrep: String? = null,
+ var language: String? = null,
+ var other: String? = null
+ )
+
+ data class Organizer(
+ var organizerId: Long = 0L,
+ var caladdress: String? = null,
+ var cn: String? = null,
+ var dir: String? = null,
+ var sentby: String? = null,
+ var language: String? = null,
+ var other: String? = null
+ )
+
+ data class Alarm(
+ var alarmId: Long = 0L,
+ var action: String? = null,
+ var description: String? = null,
+ var summary: String? = null,
+ var attendee: String? = null,
+ var duration: String? = null,
+ var repeat: String? = null,
+ var attach: String? = null,
+ var other: String? = null,
+ var triggerTime: Long? = null,
+ var triggerTimezone: String? = null,
+ var triggerRelativeTo: String? = null,
+ var triggerRelativeDuration: String? = null
+ )
+
+ data class Unknown(
+ var unknownId: Long = 0L,
+ var value: String? = null
+ )
+
+
+ companion object {
+
+ const val X_PROP_COMPLETEDTIMEZONE = "X-COMPLETEDTIMEZONE"
+
+ const val MAX_ATTACHMENT_SYNC_SIZE = 102400 // = 100KB
+
+ /**
+ * Parses an iCalendar resource, applies [ICalPreprocessor] to increase compatibility
+ * and extracts the VTODOs and/or VJOURNALS.
+ *
+ * @param reader where the iCalendar is taken from
+ *
+ * @return array of filled [JtxICalObject] data objects (may have size 0)
+ *
+ * @throws ParserException when the iCalendar can't be parsed
+ * @throws IllegalArgumentException when the iCalendar resource contains an invalid value
+ * @throws IOException on I/O errors
+ */
+ @UsesThreadContextClassLoader
+ fun fromReader(
+ reader: Reader,
+ collection: JtxCollection<JtxICalObject>
+ ): List<JtxICalObject> {
+ val ical = ICalendar.fromReader(reader)
+
+ val iCalObjectList = mutableListOf<JtxICalObject>()
+
+ ical.components.forEach { component ->
+
+ val iCalObject = JtxICalObject(collection)
+
+ when(component) {
+ is VToDo -> {
+ iCalObject.component = JtxContract.JtxICalObject.Component.VTODO.name
+ if (component.uid != null)
+ iCalObject.uid = component.uid.value // generated UID is overwritten here (if present)
+ extractProperties(iCalObject, component.properties)
+ extractVAlarms(iCalObject, component.components) // accessing the components needs an explicit type
+ }
+ is VJournal -> {
+ iCalObject.component = JtxContract.JtxICalObject.Component.VJOURNAL.name
+ if (component.uid != null)
+ iCalObject.uid = component.uid.value
+ extractProperties(iCalObject, component.properties)
+ extractVAlarms(iCalObject, component.components) // accessing the components needs an explicit type
+ }
+ }
+ iCalObjectList.add(iCalObject)
+ }
+ return iCalObjectList
+ }
+
+ /**
+ * Extracts VAlarms from the given Component (VJOURNAL or VTODO). The VAlarm is supposed to be a component within the VJOURNAL or VTODO component.
+ * Other components than VAlarms should not occur.
+ * @param [iCalObject] where the VAlarms should be inserted
+ * @param [calComponents] from which the VAlarms should be extracted
+ */
+ private fun extractVAlarms(iCalObject: JtxICalObject, calComponents: ComponentList<*>) {
+
+ calComponents.forEach { component ->
+ if(component is VAlarm) {
+ val jtxAlarm = Alarm().apply {
+ component.action?.value?.let { vAlarmAction -> this.action = vAlarmAction }
+ component.summary?.value?.let { vAlarmSummary -> this.summary = vAlarmSummary }
+ component.description?.value?.let { vAlarmDesc -> this.description = vAlarmDesc }
+ component.duration?.value?.let { vAlarmDur -> this.duration = vAlarmDur }
+ component.attachment?.uri?.let { uri -> this.attach = uri.toString() }
+ component.repeat?.value?.let { vAlarmRep -> this.repeat = vAlarmRep }
+
+ // alarms can have a duration or an absolute dateTime, but not both!
+ if(component.trigger.duration != null) {
+ component.trigger?.duration?.let { duration -> this.triggerRelativeDuration = duration.toString() }
+ component.trigger?.getParameter<Related>(Parameter.RELATED)?.let { related -> this.triggerRelativeTo = related.value }
+ } else if(component.trigger.dateTime != null) {
+ component.trigger?.dateTime?.let { dateTime -> this.triggerTime = dateTime.time }
+ component.trigger?.dateTime?.timeZone?.let { timezone -> this.triggerTimezone = timezone.id }
+ }
+
+ // remove properties to add the rest to other
+ component.properties.remove(component.action)
+ component.properties.remove(component.summary)
+ component.properties.remove(component.description)
+ component.properties.remove(component.duration)
+ component.properties.remove(component.attachment)
+ component.properties.remove(component.repeat)
+ component.properties.remove(component.trigger)
+ component.properties?.let { vAlarmProps -> this.other = JtxContract.getJsonStringFromXProperties(vAlarmProps) }
+ }
+ iCalObject.alarms.add(jtxAlarm)
+ }
+ }
+ }
+
+ /**
+ * Extracts properties from a given Property list and maps it to a JtxICalObject
+ * @param [iCalObject] where the properties should be mapped to
+ * @param [properties] from which the properties can be extracted
+ */
+ private fun extractProperties(iCalObject: JtxICalObject, properties: PropertyList<*>) {
+
+ // sequence must only be null for locally created, not-yet-synchronized events
+ iCalObject.sequence = 0
+
+ for (prop in properties) {
+ when (prop) {
+ is Sequence -> iCalObject.sequence = prop.sequenceNo.toLong()
+ is Created -> iCalObject.created = prop.dateTime.time
+ is LastModified -> iCalObject.lastModified = prop.dateTime.time
+ is Summary -> iCalObject.summary = prop.value
+ is Location -> {
+ iCalObject.location = prop.value
+ if(!prop.parameters.isEmpty && prop.parameters.getParameter<AltRep>(Parameter.ALTREP) != null)
+ iCalObject.locationAltrep = prop.parameters.getParameter<AltRep>(Parameter.ALTREP).value
+ }
+ is Geo -> {
+ iCalObject.geoLat = prop.latitude.toDouble()
+ iCalObject.geoLong = prop.longitude.toDouble()
+ }
+ is Description -> iCalObject.description = prop.value
+ is Color -> iCalObject.color = Css3Color.fromString(prop.value)?.argb
+ is Url -> iCalObject.url = prop.value
+ is Priority -> iCalObject.priority = prop.level
+ is Clazz -> iCalObject.classification = prop.value
+ is Status -> iCalObject.status = prop.value
+ is DtEnd -> Ical4Android.log.warning("The property DtEnd must not be used for VTODO and VJOURNAL, this value is rejected.")
+ is Completed -> {
+ if (iCalObject.component == JtxContract.JtxICalObject.Component.VTODO.name) {
+ iCalObject.completed = prop.date.time
+ } else
+ Ical4Android.log.warning("The property Completed is only supported for VTODO, this value is rejected.")
+ }
+
+ is Due -> {
+ if (iCalObject.component == JtxContract.JtxICalObject.Component.VTODO.name) {
+ iCalObject.due = prop.date.time
+ when {
+ prop.date is DateTime && prop.timeZone != null -> iCalObject.dueTimezone = prop.timeZone.id
+ prop.date is DateTime && prop.isUtc -> iCalObject.dueTimezone = TimeZone.getTimeZone("UTC").id
+ prop.date is DateTime && !prop.isUtc && prop.timeZone == null -> iCalObject.dueTimezone = null // this comparison is kept on purpose as "prop.date is Date" did not work as expected.
+ else -> iCalObject.dueTimezone = TZ_ALLDAY // prop.date is Date (and not DateTime), therefore it must be Allday
+ }
+ } else
+ Ical4Android.log.warning("The property Due is only supported for VTODO, this value is rejected.")
+ }
+
+ is Duration -> iCalObject.duration = prop.value
+
+ is DtStart -> {
+ iCalObject.dtstart = prop.date.time
+ when {
+ prop.date is DateTime && prop.timeZone != null -> iCalObject.dtstartTimezone = prop.timeZone.id
+ prop.date is DateTime && prop.isUtc -> iCalObject.dtstartTimezone = TimeZone.getTimeZone("UTC").id
+ prop.date is DateTime && !prop.isUtc && prop.timeZone == null -> iCalObject.dtstartTimezone = null // this comparison is kept on purpose as "prop.date is Date" did not work as expected.
+ else -> iCalObject.dtstartTimezone = TZ_ALLDAY // prop.date is Date (and not DateTime), therefore it must be Allday
+ }
+ }
+
+ is PercentComplete -> {
+ if (iCalObject.component == JtxContract.JtxICalObject.Component.VTODO.name)
+ iCalObject.percent = prop.percentage
+ else
+ Ical4Android.log.warning("The property PercentComplete is only supported for VTODO, this value is rejected.")
+ }
+
+ is RRule -> iCalObject.rrule = prop.value
+ is RDate -> {
+ val rdateList = if(iCalObject.rdate.isNullOrEmpty())
+ mutableListOf()
+ else
+ JtxContract.getLongListFromString(iCalObject.rdate!!)
+ prop.dates.forEach {
+ rdateList.add(it.time)
+ }
+ iCalObject.rdate = rdateList.toTypedArray().joinToString(separator = ",")
+ }
+ is ExDate -> {
+ val exdateList = if(iCalObject.exdate.isNullOrEmpty())
+ mutableListOf()
+ else
+ JtxContract.getLongListFromString(iCalObject.exdate!!)
+ prop.dates.forEach {
+ exdateList.add(it.time)
+ }
+ iCalObject.exdate = exdateList.toTypedArray().joinToString(separator = ",")
+ }
+ is RecurrenceId -> iCalObject.recurid = prop.value
+
+ //is RequestStatus -> iCalObject.rstatus = prop.value
+
+ is Categories ->
+ for (category in prop.categories)
+ iCalObject.categories.add(Category(text = category))
+
+ is net.fortuna.ical4j.model.property.Comment -> {
+ iCalObject.comments.add(
+ Comment().apply {
+ this.text = prop.value
+ this.language = prop.parameters?.getParameter<Language>(Parameter.LANGUAGE)?.value
+ this.altrep = prop.parameters?.getParameter<AltRep>(Parameter.ALTREP)?.value
+
+ // remove the known parameter
+ prop.parameters?.removeAll(Parameter.LANGUAGE)
+ prop.parameters?.removeAll(Parameter.ALTREP)
+
+ // save unknown parameters in the other field
+ this.other = JtxContract.getJsonStringFromXParameters(prop.parameters)
+ })
+
+ }
+
+ is Resources ->
+ for (resource in prop.resources)
+ iCalObject.resources.add(Resource(text = resource))
+
+ is Attach -> {
+ val attachment = Attachment()
+ prop.uri?.let { attachment.uri = it.toString() }
+ prop.binary?.let {
+ attachment.binary = Base64.encodeToString(it, Base64.DEFAULT)
+ }
+ prop.parameters?.getParameter<FmtType>(Parameter.FMTTYPE)?.let {
+ attachment.fmttype = it.value
+ prop.parameters?.remove(it)
+ }
+
+ attachment.other = JtxContract.getJsonStringFromXParameters(prop.parameters)
+
+ if (attachment.uri?.isNotEmpty() == true || attachment.binary?.isNotEmpty() == true) // either uri or value must be present!
+ iCalObject.attachments.add(attachment)
+ }
+
+ is net.fortuna.ical4j.model.property.RelatedTo -> {
+
+ iCalObject.relatedTo.add(
+ RelatedTo().apply {
+ this.text = prop.value
+ this.reltype = prop.getParameter<RelType>(RelType.RELTYPE)?.value ?: JtxContract.JtxRelatedto.Reltype.PARENT.name
+
+ // remove the known parameter
+ prop.parameters?.removeAll(RelType.RELTYPE)
+
+ // save unknown parameters in the other field
+ this.other = JtxContract.getJsonStringFromXParameters(prop.parameters)
+ })
+ }
+
+ is net.fortuna.ical4j.model.property.Attendee -> {
+ iCalObject.attendees.add(
+ Attendee().apply {
+ this.caladdress = prop.calAddress.toString()
+ this.cn = prop.parameters?.getParameter<Cn>(Parameter.CN)?.value
+ this.delegatedto = prop.parameters?.getParameter<DelegatedTo>(Parameter.DELEGATED_TO)?.value
+ this.delegatedfrom = prop.parameters?.getParameter<DelegatedFrom>(Parameter.DELEGATED_FROM)?.value
+ this.cutype = prop.parameters?.getParameter<CuType>(Parameter.CUTYPE)?.value
+ this.dir = prop.parameters?.getParameter<Dir>(Parameter.DIR)?.value
+ this.language = prop.parameters?.getParameter<Language>(Parameter.LANGUAGE)?.value
+ this.member = prop.parameters?.getParameter<Member>(Parameter.MEMBER)?.value
+ this.partstat = prop.parameters?.getParameter<PartStat>(Parameter.PARTSTAT)?.value
+ this.role = prop.parameters?.getParameter<Role>(Parameter.ROLE)?.value
+ this.rsvp = prop.parameters?.getParameter<Rsvp>(Parameter.RSVP)?.value?.toBoolean()
+ this.sentby = prop.parameters?.getParameter<SentBy>(Parameter.SENT_BY)?.value
+
+ // remove all known parameters so that only unknown parameters remain
+ prop.parameters?.removeAll(Parameter.CN)
+ prop.parameters?.removeAll(Parameter.DELEGATED_TO)
+ prop.parameters?.removeAll(Parameter.DELEGATED_FROM)
+ prop.parameters?.removeAll(Parameter.CUTYPE)
+ prop.parameters?.removeAll(Parameter.DIR)
+ prop.parameters?.removeAll(Parameter.LANGUAGE)
+ prop.parameters?.removeAll(Parameter.MEMBER)
+ prop.parameters?.removeAll(Parameter.PARTSTAT)
+ prop.parameters?.removeAll(Parameter.ROLE)
+ prop.parameters?.removeAll(Parameter.RSVP)
+ prop.parameters?.removeAll(Parameter.SENT_BY)
+
+ // save unknown parameters in the other field
+ this.other = JtxContract.getJsonStringFromXParameters(prop.parameters)
+ }
+ )
+ }
+ is net.fortuna.ical4j.model.property.Organizer -> {
+ iCalObject.organizer = Organizer().apply {
+ this.caladdress = prop.calAddress.toString()
+ this.cn = prop.parameters?.getParameter<Cn>(Parameter.CN)?.value
+ this.dir = prop.parameters?.getParameter<Dir>(Parameter.DIR)?.value
+ this.language = prop.parameters?.getParameter<Language>(Parameter.LANGUAGE)?.value
+ this.sentby = prop.parameters?.getParameter<SentBy>(Parameter.SENT_BY)?.value
+
+ // remove all known parameters so that only unknown parameters remain
+ prop.parameters?.removeAll(Parameter.CN)
+ prop.parameters?.removeAll(Parameter.DIR)
+ prop.parameters?.removeAll(Parameter.LANGUAGE)
+ prop.parameters?.removeAll(Parameter.SENT_BY)
+
+ // save unknown parameters in the other field
+ this.other = JtxContract.getJsonStringFromXParameters(prop.parameters)
+ }
+ }
+
+ is Uid -> iCalObject.uid = prop.value
+ //is Uid,
+ is ProdId, is DtStamp -> {
+ } /* don't save these as unknown properties */
+ else -> {
+ if(prop.name == X_PROP_COMPLETEDTIMEZONE)
+ iCalObject.completedTimezone = prop.value
+ else
+ iCalObject.unknown.add(Unknown(value = UnknownProperty.toJsonString(prop))) // save the whole property for unknown properties
+ }
+ }
+ }
+
+
+ // There seem to be many invalid tasks out there because of some defect clients, do some validation.
+ val dtStartTZ = iCalObject.dtstartTimezone
+ val dueTZ = iCalObject.dueTimezone
+
+ if (dtStartTZ != null && dueTZ != null) {
+ if (dtStartTZ == TZ_ALLDAY && dueTZ != TZ_ALLDAY) {
+ Ical4Android.log.warning("DTSTART is DATE but DUE is DATE-TIME, rewriting DTSTART to DATE-TIME")
+ iCalObject.dtstartTimezone = dueTZ
+ } else if (dtStartTZ != TZ_ALLDAY && dueTZ == TZ_ALLDAY) {
+ Ical4Android.log.warning("DTSTART is DATE-TIME but DUE is DATE, rewriting DUE to DATE-TIME")
+ iCalObject.dueTimezone = dtStartTZ
+ }
+
+ if ( iCalObject.dtstart != null && iCalObject.due != null && iCalObject.due!! <= iCalObject.dtstart!!) {
+ Ical4Android.log.warning("Found invalid DUE <= DTSTART; dropping DUE") // Dtstart must not be dropped as it might be the basis for recurring tasks
+ iCalObject.due = null
+ iCalObject.dueTimezone = null
+ }
+ }
+
+ if (iCalObject.duration != null && iCalObject.dtstart == null) {
+ Ical4Android.log.warning("Found DURATION without DTSTART; ignoring")
+ iCalObject.duration = null
+ }
+ }
+ }
+
+ /**
+ * Takes the current JtxICalObject, transforms it to an iCalendar and writes it in an OutputStream
+ * @param [os] OutputStream where iCalendar should be written to
+ */
+ @UsesThreadContextClassLoader
+ fun write(os: OutputStream) {
+ Ical4Android.checkThreadContextClassLoader()
+
+ val ical = Calendar()
+ ical.properties += Version.VERSION_2_0
+ ical.properties += ICalendar.prodId
+
+ val calComponent = when (component) {
+ JtxContract.JtxICalObject.Component.VTODO.name -> VToDo(true /* generates DTSTAMP */)
+ JtxContract.JtxICalObject.Component.VJOURNAL.name -> VJournal(true /* generates DTSTAMP */)
+ else -> return
+ }
+ ical.components += calComponent
+ addProperties(calComponent.properties)
+
+ alarms.forEach { alarm ->
+
+ val vAlarm = VAlarm()
+ vAlarm.properties.apply {
+ alarm.action?.let {
+ when (it) {
+ JtxContract.JtxAlarm.AlarmAction.DISPLAY.name -> add(Action.DISPLAY)
+ JtxContract.JtxAlarm.AlarmAction.AUDIO.name -> add(Action.AUDIO)
+ JtxContract.JtxAlarm.AlarmAction.EMAIL.name -> add(Action.EMAIL)
+ else -> return@let
+ }
+ }
+ if(alarm.triggerRelativeDuration != null) {
+ add(Trigger().apply {
+ try {
+ val dur = java.time.Duration.parse(alarm.triggerRelativeDuration)
+ this.duration = dur
+
+ // Add the RELATED parameter if present
+ alarm.triggerRelativeTo?.let {
+ if(it == JtxContract.JtxAlarm.AlarmRelativeTo.START.name)
+ this.parameters.add(Related.START)
+ if(it == JtxContract.JtxAlarm.AlarmRelativeTo.END.name)
+ this.parameters.add(Related.END)
+ }
+ } catch (e: DateTimeParseException) {
+ Ical4Android.log.log(Level.WARNING, "Could not parse Trigger duration as Duration.", e)
+ }
+ })
+
+ } else if (alarm.triggerTime != null) {
+ add(Trigger().apply {
+ try {
+ when {
+ alarm.triggerTimezone == TimeZone.getTimeZone("UTC").id -> this.dateTime = DateTime(alarm.triggerTime!!).apply {
+ this.isUtc = true
+ }
+ alarm.triggerTimezone.isNullOrEmpty() -> this.dateTime = DateTime(alarm.triggerTime!!).apply {
+ this.isUtc = true
+ }
+ else -> {
+ val timezone = TimeZoneRegistryFactory.getInstance().createRegistry()
+ .getTimeZone(alarm.triggerTimezone)
+ this.dateTime = DateTime(alarm.triggerTime!!).apply{
+ this.timeZone = timezone
+ }
+ }
+ }
+ } catch (e: ParseException) {
+ Ical4Android.log.log(Level.WARNING, "TriggerTime could not be parsed.", e)
+ }})
+ }
+ alarm.summary?.let { add(Summary(it)) }
+ alarm.repeat?.let { add(Repeat().apply { value = it }) }
+ alarm.duration?.let { add(Duration().apply {
+ try {
+ val dur = java.time.Duration.parse(it)
+ this.duration = dur
+ } catch (e: DateTimeParseException) {
+ Ical4Android.log.log(Level.WARNING, "Could not parse duration as Duration.", e)
+ }
+ }) }
+ alarm.description?.let { add(Description(it)) }
+ alarm.attach?.let { add(Attach().apply { value = it }) }
+ alarm.other?.let { addAll(JtxContract.getXPropertyListFromJson(it)) }
+
+ }
+ calComponent.components.add(vAlarm)
+ }
+
+ ICalendar.softValidate(ical)
+ CalendarOutputter(false).output(ical, os)
+ }
+
+ /**
+ * This function maps the current JtxICalObject to a iCalendar property list
+ * @param [props] The PropertyList where the properties should be added
+ */
+ private fun addProperties(props: PropertyList<Property>) {
+
+ uid.let { props += Uid(it) }
+ sequence.let { props += Sequence(it.toInt()) }
+
+ created.let { props += Created(DateTime(it).apply {
+ this.isUtc = true
+ }) }
+ lastModified.let { props += LastModified(DateTime(it).apply {
+ this.isUtc = true
+ }) }
+
+ summary.let { props += Summary(it) }
+ description?.let { props += Description(it) }
+
+ location?.let { location ->
+ val loc = Location(location)
+ locationAltrep?.let { locationAltrep ->
+ loc.parameters.add(AltRep(locationAltrep))
+ }
+ props += loc
+ }
+ if (geoLat != null && geoLong != null) {
+ props += Geo(geoLat!!.toBigDecimal(), geoLong!!.toBigDecimal())
+ }
+ color?.let { props += Color(null, Css3Color.nearestMatch(it).name) }
+ url?.let {
+ try {
+ props += Url(URI(it))
+ } catch (e: URISyntaxException) {
+ Ical4Android.log.log(Level.WARNING, "Ignoring invalid task URL: $url", e)
+ }
+ }
+ //organizer?.let { props += it }
+
+
+ classification?.let { props += Clazz(it) }
+ status?.let { props += Status(it) }
+
+
+ val categoryTextList = TextList()
+ categories.forEach {
+ categoryTextList.add(it.text)
+ }
+ if (!categoryTextList.isEmpty)
+ props += Categories(categoryTextList)
+
+
+ val resourceTextList = TextList()
+ resources.forEach {
+ resourceTextList.add(it.text)
+ }
+ if (!resourceTextList.isEmpty)
+ props += Resources(resourceTextList)
+
+
+ comments.forEach { comment ->
+ val c = Comment(comment.text).apply {
+ comment.altrep?.let { this.parameters.add(AltRep(it)) }
+ comment.language?.let { this.parameters.add(Language(it)) }
+ comment.other?.let {
+ val xparams = JtxContract.getXParametersFromJson(it)
+ xparams.forEach { xparam ->
+ this.parameters.add(xparam)
+ }
+ }
+ }
+ props += c
+ }
+
+
+ attendees.forEach { attendee ->
+ val attendeeProp = net.fortuna.ical4j.model.property.Attendee().apply {
+ this.calAddress = URI(attendee.caladdress)
+
+ attendee.cn?.let {
+ this.parameters.add(Cn(it))
+ }
+ attendee.cutype?.let {
+ when {
+ it.equals(CuType.INDIVIDUAL.value, ignoreCase = true) -> this.parameters.add(CuType.INDIVIDUAL)
+ it.equals(CuType.GROUP.value, ignoreCase = true) -> this.parameters.add(CuType.GROUP)
+ it.equals(CuType.ROOM.value, ignoreCase = true) -> this.parameters.add(CuType.ROOM)
+ it.equals(CuType.RESOURCE.value, ignoreCase = true) -> this.parameters.add(CuType.RESOURCE)
+ it.equals(CuType.UNKNOWN.value, ignoreCase = true) -> this.parameters.add(CuType.UNKNOWN)
+ else -> this.parameters.add(CuType.UNKNOWN)
+ }
+ }
+ attendee.delegatedfrom?.let {
+ this.parameters.add(DelegatedFrom(it))
+ }
+ attendee.delegatedto?.let {
+ this.parameters.add(DelegatedTo(it))
+ }
+ attendee.dir?.let {
+ this.parameters.add(Dir(it))
+ }
+ attendee.language?.let {
+ this.parameters.add(Language(it))
+ }
+ attendee.member?.let {
+ this.parameters.add(Member(it))
+ }
+ attendee.partstat?.let {
+ this.parameters.add(PartStat(it))
+ }
+ attendee.role?.let {
+ this.parameters.add(Role(it))
+ }
+ attendee.rsvp?.let {
+ this.parameters.add(Rsvp(it))
+ }
+ attendee.sentby?.let {
+ this.parameters.add(SentBy(it))
+ }
+ attendee.other?.let {
+ val params = JtxContract.getXParametersFromJson(it)
+ params.forEach { xparam ->
+ this.parameters.add(xparam)
+ }
+ }
+ }
+ props += attendeeProp
+ }
+
+ organizer?.let { organizer ->
+ val organizerProp = net.fortuna.ical4j.model.property.Organizer().apply {
+ if(organizer.caladdress?.isNotEmpty() == true)
+ this.calAddress = URI(organizer.caladdress)
+
+ organizer.cn?.let {
+ this.parameters.add(Cn(it))
+ }
+ organizer.dir?.let {
+ this.parameters.add(Dir(it))
+ }
+ organizer.language?.let {
+ this.parameters.add(Language(it))
+ }
+ organizer.sentby?.let {
+ this.parameters.add(SentBy(it))
+ }
+ organizer.other?.let {
+ val params = JtxContract.getXParametersFromJson(it)
+ params.forEach { xparam ->
+ this.parameters.add(xparam)
+ }
+ }
+ }
+ props += organizerProp
+ }
+
+ attachments.forEach { attachment ->
+
+ try {
+ if (attachment.uri?.startsWith("content://") == true) {
+
+ val attachmentUri = ContentUris.withAppendedId(JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(collection.account), attachment.attachmentId)
+ val attachmentFile = collection.client.openFile(attachmentUri, "r")
+ val attachmentBytes = ParcelFileDescriptor.AutoCloseInputStream(attachmentFile).readBytes()
+ if(attachmentBytes.size <= MAX_ATTACHMENT_SYNC_SIZE) { // Sync only small attachments that are smaller than 100 KB, larger ones are ignored
+ val att = Attach(attachmentBytes).apply {
+ attachment.fmttype?.let { this.parameters.add(FmtType(it)) }
+ }
+ props += att
+ }
+
+ } else {
+ attachment.uri?.let { uri ->
+ val att = Attach(URI(uri)).apply {
+ attachment.fmttype?.let { this.parameters.add(FmtType(it)) }
+ }
+ props += att
+ }
+ }
+ } catch (e: FileNotFoundException) {
+ Ical4Android.log.log(Level.WARNING, "File not found at the given Uri: ${attachment.uri}", e)
+ } catch (e: NullPointerException) {
+ Ical4Android.log.log(Level.WARNING, "Provided Uri was empty: ${attachment.uri}", e)
+ } catch (e: IllegalArgumentException) {
+ Ical4Android.log.log(Level.WARNING, "Uri could not be parsed: ${attachment.uri}", e)
+ }
+ }
+
+ unknown.forEach {
+ it.value?.let { jsonString ->
+ props.add(UnknownProperty.fromJsonString(jsonString))
+ }
+ }
+
+ relatedTo.forEach {
+ val param: Parameter =
+ when (it.reltype) {
+ RelType.CHILD.value -> RelType.CHILD
+ RelType.SIBLING.value -> RelType.SIBLING
+ RelType.PARENT.value -> RelType.PARENT
+ else -> return@forEach
+ }
+ val parameterList = ParameterList()
+ parameterList.add(param)
+ props += RelatedTo(parameterList, it.text)
+ }
+
+ dtstart?.let {
+ when {
+ dtstartTimezone == TZ_ALLDAY -> props += DtStart(Date(it))
+ dtstartTimezone == TimeZone.getTimeZone("UTC").id -> props += DtStart(DateTime(it).apply {
+ this.isUtc = true
+ })
+ dtstartTimezone.isNullOrEmpty() -> props += DtStart(DateTime(it).apply {
+ this.isUtc = false
+ })
+ else -> {
+ val timezone = TimeZoneRegistryFactory.getInstance().createRegistry()
+ .getTimeZone(dtstartTimezone)
+ val withTimezone = DtStart(DateTime(it))
+ withTimezone.timeZone = timezone
+ props += withTimezone
+ }
+ }
+ }
+
+ rrule?.let { rrule ->
+ props += RRule(rrule)
+ }
+ recurid?.let { recurid ->
+ props += RecurrenceId(recurid)
+ }
+
+ rdate?.let { rdateString ->
+
+ when {
+ dtstartTimezone == TZ_ALLDAY -> {
+ val dateListDate = DateList(Value.DATE)
+ JtxContract.getLongListFromString(rdateString).forEach {
+ dateListDate.add(Date(it))
+ }
+ props += RDate(dateListDate)
+
+ }
+ dtstartTimezone == TimeZone.getTimeZone("UTC").id -> {
+ val dateListDateTime = DateList(Value.DATE_TIME)
+ JtxContract.getLongListFromString(rdateString).forEach {
+ dateListDateTime.add(DateTime(it).apply {
+ this.isUtc = true
+ })
+ }
+ props += RDate(dateListDateTime)
+ }
+ dtstartTimezone.isNullOrEmpty() -> {
+ val dateListDateTime = DateList(Value.DATE_TIME)
+ JtxContract.getLongListFromString(rdateString).forEach {
+ dateListDateTime.add(DateTime(it).apply {
+ this.isUtc = false
+ })
+ }
+ props += RDate(dateListDateTime)
+ }
+ else -> {
+ val dateListDateTime = DateList(Value.DATE_TIME)
+ val timezone = TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone(dtstartTimezone)
+ JtxContract.getLongListFromString(rdateString).forEach {
+ val withTimezone = DateTime(it)
+ withTimezone.timeZone = timezone
+ dateListDateTime.add(DateTime(withTimezone))
+ }
+ props += RDate(dateListDateTime)
+ }
+ }
+ }
+
+ exdate?.let { exdateString ->
+
+ when {
+ dtstartTimezone == TZ_ALLDAY -> {
+ val dateListDate = DateList(Value.DATE)
+ JtxContract.getLongListFromString(exdateString).forEach {
+ dateListDate.add(Date(it))
+ }
+ props += ExDate(dateListDate)
+
+ }
+ dtstartTimezone == TimeZone.getTimeZone("UTC").id -> {
+ val dateListDateTime = DateList(Value.DATE_TIME)
+ JtxContract.getLongListFromString(exdateString).forEach {
+ dateListDateTime.add(DateTime(it).apply {
+ this.isUtc = true
+ })
+ }
+ props += ExDate(dateListDateTime)
+ }
+ dtstartTimezone.isNullOrEmpty() -> {
+ val dateListDateTime = DateList(Value.DATE_TIME)
+ JtxContract.getLongListFromString(exdateString).forEach {
+ dateListDateTime.add(DateTime(it).apply {
+ this.isUtc = false
+ })
+ }
+ props += ExDate(dateListDateTime)
+ }
+ else -> {
+ val dateListDateTime = DateList(Value.DATE_TIME)
+ val timezone = TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone(dtstartTimezone)
+ JtxContract.getLongListFromString(exdateString).forEach {
+ val withTimezone = DateTime(it)
+ withTimezone.timeZone = timezone
+ dateListDateTime.add(DateTime(withTimezone))
+ }
+ props += ExDate(dateListDateTime)
+ }
+ }
+ }
+
+ duration?.let {
+ val dur = Duration()
+ dur.value = it
+ props += dur
+ }
+
+
+ /*
+// remember used time zones
+val usedTimeZones = HashSet<TimeZone>()
+duration?.let(props::add)
+*/
+
+
+ if(component == JtxContract.JtxICalObject.Component.VTODO.name) {
+ completed?.let {
+ //Completed is defines as always DateTime! And is always UTC!?
+
+ props += Completed(DateTime(it))
+ }
+ completedTimezone?.let {
+ props += XProperty(X_PROP_COMPLETEDTIMEZONE, it)
+ }
+ percent?.let {
+ props += PercentComplete(it)
+ }
+
+
+ if (priority != null && priority != Priority.UNDEFINED.level)
+ priority?.let {
+ props += Priority(it)
+ }
+ else {
+ props += Priority(Priority.UNDEFINED.level)
+ }
+
+ due?.let {
+ when {
+ dueTimezone == TZ_ALLDAY -> props += Due(Date(it))
+ dueTimezone == TimeZone.getTimeZone("UTC").id -> props += Due(DateTime(it).apply {
+ this.isUtc = true
+ })
+ dueTimezone.isNullOrEmpty() -> props += Due(DateTime(it).apply {
+ this.isUtc = false
+ })
+ else -> {
+ val timezone = TimeZoneRegistryFactory.getInstance().createRegistry()
+ .getTimeZone(dueTimezone)
+ val withTimezone = Due(DateTime(it))
+ withTimezone.timeZone = timezone
+ props += withTimezone
+ }
+ }
+ }
+ }
+
+ /*
+
+ // determine earliest referenced date
+ val earliest = arrayOf(
+ dtStart?.date,
+ due?.date,
+ completedAt?.date
+ ).filterNotNull().min()
+ // add VTIMEZONE components
+ for (tz in usedTimeZones)
+ ical.components += ICalendar.minifyVTimeZone(tz.vTimeZone, earliest)
+*/
+ }
+
+
+ fun prepareForUpload(): String {
+ return "${this.uid}.ics"
+ }
+
+ /**
+ * Updates the fileName, eTag and scheduleTag of the current JtxICalObject
+ */
+ fun clearDirty(fileName: String?, eTag: String?, scheduleTag: String?) {
+
+ var updateUri = JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(collection.account)
+ updateUri = Uri.withAppendedPath(updateUri, this.id.toString())
+
+ val values = ContentValues()
+ fileName?.let { values.put(JtxContract.JtxICalObject.FILENAME, fileName) }
+ eTag?.let { values.put(JtxContract.JtxICalObject.ETAG, eTag) }
+ scheduleTag?.let { values.put(JtxContract.JtxICalObject.SCHEDULETAG, scheduleTag) }
+ values.put(JtxContract.JtxICalObject.DIRTY, false)
+
+ collection.client.update(updateUri, values, null, null)
+ }
+
+ /**
+ * Updates the flags of the current JtxICalObject
+ * @param [flags] to be set as [Int]
+ */
+ fun updateFlags(flags: Int) {
+
+ var updateUri = JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(collection.account)
+ updateUri = Uri.withAppendedPath(updateUri, this.id.toString())
+
+ val values = ContentValues()
+ values.put(JtxContract.JtxICalObject.FLAGS, flags)
+
+ collection.client.update(updateUri, values, null, null)
+ }
+
+ /**
+ * adds the current JtxICalObject in the jtx DB through the provider
+ * @return the Content [Uri] of the inserted object
+ */
+ fun add(): Uri {
+ val values = this.toContentValues()
+
+ val newUri = collection.client.insert(
+ JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(collection.account),
+ values
+ ) ?: return Uri.EMPTY
+ this.id = newUri.lastPathSegment?.toLong() ?: return Uri.EMPTY
+
+ insertOrUpdateListProperties(false)
+
+ return newUri
+ }
+
+ /**
+ * Updates the current JtxICalObject with the given data
+ * @param [data] The JtxICalObject with the information that should be applied to this object and updated in the provider
+ * @return [Uri] of the updated entry
+ */
+ fun update(data: JtxICalObject): Uri {
+
+ this.applyNewData(data)
+ val values = this.toContentValues()
+
+ var updateUri = JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(collection.account)
+ updateUri = Uri.withAppendedPath(updateUri, this.id.toString())
+ collection.client.update(
+ updateUri,
+ values,
+ "${JtxContract.JtxICalObject.ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ insertOrUpdateListProperties(true)
+
+ return updateUri
+ }
+
+
+ /**
+ * This function takes care of all list properties and inserts them in the DB through the provider
+ * @param isUpdate if true then the list properties are deleted through the provider before they are inserted
+ */
+ private fun insertOrUpdateListProperties(isUpdate: Boolean) {
+
+ // delete the categories, attendees, ... and insert them again after. Only relevant for Update, for an insert there will be no entries
+ if (isUpdate) {
+ collection.client.delete(
+ JtxContract.JtxCategory.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxCategory.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxComment.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxComment.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxResource.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxResource.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxRelatedto.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxAttendee.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxAttendee.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxOrganizer.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxOrganizer.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxAttachment.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxAlarm.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+
+ collection.client.delete(
+ JtxContract.JtxUnknown.CONTENT_URI.asSyncAdapter(collection.account),
+ "${JtxContract.JtxUnknown.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString())
+ )
+ }
+
+ this.categories.forEach { category ->
+ val categoryContentValues = ContentValues().apply {
+ put(JtxContract.JtxCategory.ICALOBJECT_ID, id)
+ put(JtxContract.JtxCategory.TEXT, category.text)
+ put(JtxContract.JtxCategory.ID, category.categoryId)
+ put(JtxContract.JtxCategory.LANGUAGE, category.language)
+ put(JtxContract.JtxCategory.OTHER, category.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxCategory.CONTENT_URI.asSyncAdapter(collection.account),
+ categoryContentValues
+ )
+ }
+
+ this.comments.forEach { comment ->
+ val commentContentValues = ContentValues().apply {
+ put(JtxContract.JtxComment.ICALOBJECT_ID, id)
+ put(JtxContract.JtxComment.ID, comment.commentId)
+ put(JtxContract.JtxComment.TEXT, comment.text)
+ put(JtxContract.JtxComment.LANGUAGE, comment.language)
+ put(JtxContract.JtxComment.OTHER, comment.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxComment.CONTENT_URI.asSyncAdapter(collection.account),
+ commentContentValues
+ )
+ }
+
+
+ this.resources.forEach { resource ->
+ val resourceContentValues = ContentValues().apply {
+ put(JtxContract.JtxResource.ICALOBJECT_ID, id)
+ put(JtxContract.JtxResource.ID, resource.resourceId)
+ put(JtxContract.JtxResource.TEXT, resource.text)
+ put(JtxContract.JtxResource.LANGUAGE, resource.language)
+ put(JtxContract.JtxResource.OTHER, resource.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxResource.CONTENT_URI.asSyncAdapter(collection.account),
+ resourceContentValues
+ )
+ }
+
+
+ this.relatedTo.forEach { related ->
+ val relatedToContentValues = ContentValues().apply {
+ put(JtxContract.JtxRelatedto.ICALOBJECT_ID, id)
+ put(JtxContract.JtxRelatedto.TEXT, related.text)
+ put(JtxContract.JtxRelatedto.RELTYPE, related.reltype)
+ put(JtxContract.JtxRelatedto.OTHER, related.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(collection.account),
+ relatedToContentValues
+ )
+ }
+
+ this.attendees.forEach { attendee ->
+ val attendeeContentValues = ContentValues().apply {
+ put(JtxContract.JtxAttendee.ICALOBJECT_ID, id)
+ put(JtxContract.JtxAttendee.CALADDRESS, attendee.caladdress)
+ put(JtxContract.JtxAttendee.CN, attendee.cn)
+ put(JtxContract.JtxAttendee.CUTYPE, attendee.cutype)
+ put(JtxContract.JtxAttendee.DELEGATEDFROM, attendee.delegatedfrom)
+ put(JtxContract.JtxAttendee.DELEGATEDTO, attendee.delegatedto)
+ put(JtxContract.JtxAttendee.DIR, attendee.dir)
+ put(JtxContract.JtxAttendee.LANGUAGE, attendee.language)
+ put(JtxContract.JtxAttendee.MEMBER, attendee.member)
+ put(JtxContract.JtxAttendee.PARTSTAT, attendee.partstat)
+ put(JtxContract.JtxAttendee.ROLE, attendee.role)
+ put(JtxContract.JtxAttendee.RSVP, attendee.rsvp)
+ put(JtxContract.JtxAttendee.SENTBY, attendee.sentby)
+ put(JtxContract.JtxAttendee.OTHER, attendee.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxAttendee.CONTENT_URI.asSyncAdapter(
+ collection.account
+ ), attendeeContentValues
+ )
+ }
+
+ this.organizer.let { organizer ->
+ val organizerContentValues = ContentValues().apply {
+ put(JtxContract.JtxOrganizer.ICALOBJECT_ID, id)
+ put(JtxContract.JtxOrganizer.CALADDRESS, organizer?.caladdress)
+
+ put(JtxContract.JtxOrganizer.CN, organizer?.cn)
+ put(JtxContract.JtxOrganizer.DIR, organizer?.dir)
+ put(JtxContract.JtxOrganizer.LANGUAGE, organizer?.language)
+ put(JtxContract.JtxOrganizer.SENTBY, organizer?.sentby)
+ put(JtxContract.JtxOrganizer.OTHER, organizer?.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxOrganizer.CONTENT_URI.asSyncAdapter(
+ collection.account
+ ), organizerContentValues
+ )
+ }
+
+ this.attachments.forEach { attachment ->
+ val attachmentContentValues = ContentValues().apply {
+ put(JtxContract.JtxAttachment.ICALOBJECT_ID, id)
+ put(JtxContract.JtxAttachment.URI, attachment.uri)
+ put(JtxContract.JtxAttachment.FMTTYPE, attachment.fmttype)
+ put(JtxContract.JtxAttachment.OTHER, attachment.other)
+ }
+ val newAttachment = collection.client.insert(
+ JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(
+ collection.account
+ ), attachmentContentValues
+ )
+ if(attachment.uri.isNullOrEmpty() && newAttachment != null) {
+ val attachmentPFD = collection.client.openFile(newAttachment, "w")
+ ParcelFileDescriptor.AutoCloseOutputStream(attachmentPFD).write(Base64.decode(attachment.binary, Base64.DEFAULT))
+ }
+ }
+
+ this.alarms.forEach { alarm ->
+ val alarmContentValues = ContentValues().apply {
+ put(JtxContract.JtxAlarm.ICALOBJECT_ID, id)
+ put(JtxContract.JtxAlarm.ACTION, alarm.action)
+ put(JtxContract.JtxAlarm.ATTACH, alarm.attach)
+ //put(JtxContract.JtxAlarm.ATTENDEE, alarm.attendee)
+ put(JtxContract.JtxAlarm.DESCRIPTION, alarm.description)
+ put(JtxContract.JtxAlarm.DURATION, alarm.duration)
+ put(JtxContract.JtxAlarm.REPEAT, alarm.repeat)
+ put(JtxContract.JtxAlarm.SUMMARY, alarm.summary)
+ put(JtxContract.JtxAlarm.TRIGGER_RELATIVE_TO, alarm.triggerRelativeTo)
+ put(JtxContract.JtxAlarm.TRIGGER_RELATIVE_DURATION, alarm.triggerRelativeDuration)
+ put(JtxContract.JtxAlarm.TRIGGER_TIME, alarm.triggerTime)
+ put(JtxContract.JtxAlarm.TRIGGER_TIMEZONE, alarm.triggerTimezone)
+ put(JtxContract.JtxAlarm.OTHER, alarm.other)
+ }
+ collection.client.insert(
+ JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(collection.account),
+ alarmContentValues
+ )
+ }
+
+ this.unknown.forEach { unknown ->
+ val unknownContentValues = ContentValues().apply {
+ put(JtxContract.JtxUnknown.ICALOBJECT_ID, id)
+ put(JtxContract.JtxUnknown.UNKNOWN_VALUE, unknown.value)
+ }
+ collection.client.insert(
+ JtxContract.JtxUnknown.CONTENT_URI.asSyncAdapter(collection.account),
+ unknownContentValues
+ )
+ }
+ }
+
+ /**
+ * Deletes the current JtxICalObject
+ * @return The number of deleted records (should always be 1)
+ */
+ fun delete(): Int {
+ val uri = Uri.withAppendedPath(
+ JtxContract.JtxICalObject.CONTENT_URI.asSyncAdapter(collection.account),
+ id.toString()
+ )
+ return collection.client.delete(uri, null, null)
+ }
+
+
+ /**
+ * This function is used for empty JtxICalObjects that need new data applied, usually a LocalJtxICalObject.
+ * @param [newData], the (local) JtxICalObject that should be mapped onto the given JtxICalObject
+ */
+ fun applyNewData(newData: JtxICalObject) {
+
+ this.component = newData.component
+ this.sequence = newData.sequence
+ this.created = newData.created
+ this.lastModified = newData.lastModified
+ this.summary = newData.summary
+ this.description = newData.description
+ this.uid = newData.uid
+
+ this.location = newData.location
+ this.locationAltrep = newData.locationAltrep
+ this.geoLat = newData.geoLat
+ this.geoLong = newData.geoLong
+ this.percent = newData.percent
+ this.classification = newData.classification
+ this.status = newData.status
+ this.priority = newData.priority
+
+ this.dtstart = newData.dtstart
+ this.dtstartTimezone = newData.dtstartTimezone
+ this.dtend = newData.dtend
+ this.dtendTimezone = newData.dtendTimezone
+ this.completed = newData.completed
+ this.completedTimezone = newData.completedTimezone
+ this.due = newData.due
+ this.dueTimezone = newData.dueTimezone
+ this.duration = newData.duration
+
+ this.rrule = newData.rrule
+ this.rdate = newData.rdate
+ this.exdate = newData.exdate
+ this.recurid = newData.recurid
+
+
+ this.categories = newData.categories
+ this.comments = newData.comments
+ this.resources = newData.resources
+ this.relatedTo = newData.relatedTo
+ this.attendees = newData.attendees
+ this.organizer = newData.organizer
+ this.attachments = newData.attachments
+ this.alarms = newData.alarms
+ this.unknown = newData.unknown
+ }
+
+ /**
+ * Takes Content Values, applies them on the current JtxICalObject and retrieves all further list properties from the content provier and adds them.
+ * @param [values] The Content Values with the information about the JtxICalObject
+ */
+ fun populateFromContentValues(values: ContentValues) {
+
+ values.getAsLong(JtxContract.JtxICalObject.ID)?.let { id -> this.id = id }
+
+ values.getAsString(JtxContract.JtxICalObject.COMPONENT)?.let { component -> this.component = component }
+ values.getAsString(JtxContract.JtxICalObject.SUMMARY)?.let { summary -> this.summary = summary }
+ values.getAsString(JtxContract.JtxICalObject.DESCRIPTION)?.let { description -> this.description = description }
+ values.getAsLong(JtxContract.JtxICalObject.DTSTART)?.let { dtstart -> this.dtstart = dtstart }
+ values.getAsString(JtxContract.JtxICalObject.DTSTART_TIMEZONE)?.let { dtstartTimezone -> this.dtstartTimezone = dtstartTimezone }
+ values.getAsLong(JtxContract.JtxICalObject.DTEND)?.let { dtend -> this.dtend = dtend }
+ values.getAsString(JtxContract.JtxICalObject.DTEND_TIMEZONE)?.let { dtendTimezone -> this.dtendTimezone = dtendTimezone }
+ values.getAsString(JtxContract.JtxICalObject.STATUS)?.let { status -> this.status = status }
+ values.getAsString(JtxContract.JtxICalObject.CLASSIFICATION)?.let { classification -> this.classification = classification }
+ values.getAsString(JtxContract.JtxICalObject.URL)?.let { url -> this.url = url }
+ values.getAsDouble(JtxContract.JtxICalObject.GEO_LAT)?.let { geoLat -> this.geoLat = geoLat }
+ values.getAsDouble(JtxContract.JtxICalObject.GEO_LONG)?.let { geoLong -> this.geoLong = geoLong }
+ values.getAsString(JtxContract.JtxICalObject.LOCATION)?.let { location -> this.location = location }
+ values.getAsString(JtxContract.JtxICalObject.LOCATION_ALTREP)?.let { locationAltrep -> this.locationAltrep = locationAltrep }
+ values.getAsInteger(JtxContract.JtxICalObject.PERCENT)?.let { percent -> this.percent = percent }
+ values.getAsInteger(JtxContract.JtxICalObject.PRIORITY)?.let { priority -> this.priority = priority }
+ values.getAsLong(JtxContract.JtxICalObject.DUE)?.let { due -> this.due = due }
+ values.getAsString(JtxContract.JtxICalObject.DUE_TIMEZONE)?.let { dueTimezone -> this.dueTimezone = dueTimezone }
+ values.getAsLong(JtxContract.JtxICalObject.COMPLETED)?.let { completed -> this.completed = completed }
+ values.getAsString(JtxContract.JtxICalObject.COMPLETED_TIMEZONE)?.let { completedTimezone -> this.completedTimezone = completedTimezone }
+ values.getAsString(JtxContract.JtxICalObject.DURATION)?.let { duration -> this.duration = duration }
+ values.getAsString(JtxContract.JtxICalObject.UID)?.let { uid -> this.uid = uid }
+ values.getAsLong(JtxContract.JtxICalObject.CREATED)?.let { created -> this.created = created }
+ values.getAsLong(JtxContract.JtxICalObject.DTSTAMP)?.let { dtstamp -> this.dtstamp = dtstamp }
+ values.getAsLong(JtxContract.JtxICalObject.LAST_MODIFIED)?.let { lastModified -> this.lastModified = lastModified }
+ values.getAsLong(JtxContract.JtxICalObject.SEQUENCE)?.let { sequence -> this.sequence = sequence }
+ values.getAsInteger(JtxContract.JtxICalObject.COLOR)?.let { color -> this.color = color }
+
+ values.getAsString(JtxContract.JtxICalObject.RRULE)?.let { rrule -> this.rrule = rrule }
+ values.getAsString(JtxContract.JtxICalObject.EXDATE)?.let { exdate -> this.exdate = exdate }
+ values.getAsString(JtxContract.JtxICalObject.RDATE)?.let { rdate -> this.rdate = rdate }
+ values.getAsString(JtxContract.JtxICalObject.RECURID)?.let { recurid -> this.recurid = recurid }
+
+ this.collectionId = collection.id
+ values.getAsBoolean(JtxContract.JtxICalObject.DIRTY)?.let { dirty -> this.dirty = dirty }
+ values.getAsBoolean(JtxContract.JtxICalObject.DELETED)?.let { deleted -> this.deleted = deleted }
+
+ values.getAsString(JtxContract.JtxICalObject.FILENAME)?.let { fileName -> this.fileName = fileName }
+ values.getAsString(JtxContract.JtxICalObject.ETAG)?.let { eTag -> this.eTag = eTag }
+ values.getAsString(JtxContract.JtxICalObject.SCHEDULETAG)?.let { scheduleTag -> this.scheduleTag = scheduleTag }
+ values.getAsInteger(JtxContract.JtxICalObject.FLAGS)?.let { flags -> this.flags = flags }
+
+
+ // Take care of categories
+ val categoriesContentValues = getCategoryContentValues()
+ categoriesContentValues.forEach { catValues ->
+ val category = Category().apply {
+ catValues.getAsLong(JtxContract.JtxCategory.ID)?.let { id -> this.categoryId = id }
+ catValues.getAsString(JtxContract.JtxCategory.TEXT)?.let { text -> this.text = text }
+ catValues.getAsString(JtxContract.JtxCategory.LANGUAGE)?.let { language -> this.language = language }
+ catValues.getAsString(JtxContract.JtxCategory.OTHER)?.let { other -> this.other = other }
+ }
+ categories.add(category)
+ }
+
+ // Take care of comments
+ val commentsContentValues = getCommentContentValues()
+ commentsContentValues.forEach { commentValues ->
+ val comment = Comment().apply {
+ commentValues.getAsLong(JtxContract.JtxComment.ID)?.let { id -> this.commentId = id }
+ commentValues.getAsString(JtxContract.JtxComment.TEXT)?.let { text -> this.text = text }
+ commentValues.getAsString(JtxContract.JtxComment.LANGUAGE)?.let { language -> this.language = language }
+ commentValues.getAsString(JtxContract.JtxComment.OTHER)?.let { other -> this.other = other }
+ }
+ comments.add(comment)
+ }
+
+ // Take care of resources
+ val resourceContentValues = getResourceContentValues()
+ resourceContentValues.forEach { resourceValues ->
+ val resource = Resource().apply {
+ resourceValues.getAsLong(JtxContract.JtxResource.ID)?.let { id -> this.resourceId = id }
+ resourceValues.getAsString(JtxContract.JtxResource.TEXT)?.let { text -> this.text = text }
+ resourceValues.getAsString(JtxContract.JtxResource.LANGUAGE)?.let { language -> this.language = language }
+ resourceValues.getAsString(JtxContract.JtxResource.OTHER)?.let { other -> this.other = other }
+ }
+ resources.add(resource)
+ }
+
+
+ // Take care of related-to
+ val relatedToContentValues = getRelatedToContentValues()
+ relatedToContentValues.forEach { relatedToValues ->
+ val relTo = RelatedTo().apply {
+ relatedToValues.getAsLong(JtxContract.JtxRelatedto.ID)?.let { id -> this.relatedtoId = id }
+ relatedToValues.getAsString(JtxContract.JtxRelatedto.TEXT)?.let { text -> this.text = text }
+ relatedToValues.getAsString(JtxContract.JtxRelatedto.RELTYPE)?.let { reltype -> this.reltype = reltype }
+ relatedToValues.getAsString(JtxContract.JtxRelatedto.OTHER)?.let { other -> this.other = other }
+
+ }
+ relatedTo.add(relTo)
+ }
+
+
+ // Take care of attendees
+ val attendeeContentValues = getAttendeesContentValues()
+ attendeeContentValues.forEach { attendeeValues ->
+ val attendee = Attendee().apply {
+ attendeeValues.getAsLong(JtxContract.JtxAttendee.ID)?.let { id -> this.attendeeId = id }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.CALADDRESS)?.let { caladdress -> this.caladdress = caladdress }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.CUTYPE)?.let { cutype -> this.cutype = cutype }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.MEMBER)?.let { member -> this.member = member }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.ROLE)?.let { role -> this.role = role }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.PARTSTAT)?.let { partstat -> this.partstat = partstat }
+ attendeeValues.getAsBoolean(JtxContract.JtxAttendee.RSVP)?.let { rsvp -> this.rsvp = rsvp }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.DELEGATEDTO)?.let { delto -> this.delegatedto = delto }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.DELEGATEDFROM)?.let { delfrom -> this.delegatedfrom = delfrom }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.SENTBY)?.let { sentby -> this.sentby = sentby }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.CN)?.let { cn -> this.cn = cn }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.DIR)?.let { dir -> this.dir = dir }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.LANGUAGE)?.let { lang -> this.language = lang }
+ attendeeValues.getAsString(JtxContract.JtxAttendee.OTHER)?.let { other -> this.other = other }
+
+ }
+ attendees.add(attendee)
+ }
+
+ // Take care of organizer
+ val organizerContentValues = getOrganizerContentValues()
+ val orgnzr = Organizer().apply {
+ organizerId = organizerContentValues?.getAsLong(JtxContract.JtxOrganizer.ID) ?: 0L
+ caladdress = organizerContentValues?.getAsString(JtxContract.JtxOrganizer.CALADDRESS)
+ sentby = organizerContentValues?.getAsString(JtxContract.JtxOrganizer.SENTBY)
+ cn = organizerContentValues?.getAsString(JtxContract.JtxOrganizer.CN)
+ dir = organizerContentValues?.getAsString(JtxContract.JtxOrganizer.DIR)
+ language = organizerContentValues?.getAsString(JtxContract.JtxOrganizer.LANGUAGE)
+ other = organizerContentValues?.getAsString(JtxContract.JtxOrganizer.OTHER)
+ }
+ organizer = orgnzr
+
+ // Take care of attachments
+ val attachmentContentValues = getAttachmentsContentValues()
+ attachmentContentValues.forEach { attachmentValues ->
+ val attachment = Attachment().apply {
+ attachmentValues.getAsLong(JtxContract.JtxAttachment.ID)?.let { id -> this.attachmentId = id }
+ attachmentValues.getAsString(JtxContract.JtxAttachment.URI)?.let { uri -> this.uri = uri }
+ attachmentValues.getAsString(JtxContract.JtxAttachment.BINARY)?.let { value -> this.binary = value }
+ attachmentValues.getAsString(JtxContract.JtxAttachment.FMTTYPE)?.let { fmttype -> this.fmttype = fmttype }
+ attachmentValues.getAsString(JtxContract.JtxAttachment.OTHER)?.let { other -> this.other = other }
+
+ }
+ attachments.add(attachment)
+ }
+
+ // Take care of alarms
+ val alarmContentValues = getAlarmsContentValues()
+ alarmContentValues.forEach { alarmValues ->
+ val alarm = Alarm().apply {
+ alarmValues.getAsLong(JtxContract.JtxAlarm.ID)?.let { id -> this.alarmId = id }
+ alarmValues.getAsString(JtxContract.JtxAlarm.ACTION)?.let { action -> this.action = action }
+ alarmValues.getAsString(JtxContract.JtxAlarm.DESCRIPTION)?.let { desc -> this.description = desc }
+ alarmValues.getAsLong(JtxContract.JtxAlarm.TRIGGER_TIME)?.let { time -> this.triggerTime = time }
+ alarmValues.getAsString(JtxContract.JtxAlarm.TRIGGER_TIMEZONE)?.let { tz -> this.triggerTimezone = tz }
+ alarmValues.getAsString(JtxContract.JtxAlarm.TRIGGER_RELATIVE_TO)?.let { relative -> this.triggerRelativeTo = relative }
+ alarmValues.getAsString(JtxContract.JtxAlarm.TRIGGER_RELATIVE_DURATION)?.let { duration -> this.triggerRelativeDuration = duration }
+ alarmValues.getAsString(JtxContract.JtxAlarm.SUMMARY)?.let { summary -> this.summary = summary }
+ alarmValues.getAsString(JtxContract.JtxAlarm.DURATION)?.let { dur -> this.duration = dur }
+ alarmValues.getAsString(JtxContract.JtxAlarm.REPEAT)?.let { repeat -> this.repeat = repeat }
+ alarmValues.getAsString(JtxContract.JtxAlarm.ATTACH)?.let { attach -> this.attach = attach }
+ alarmValues.getAsString(JtxContract.JtxAlarm.OTHER)?.let { other -> this.other = other }
+ }
+ alarms.add(alarm)
+ }
+
+ // Take care of uknown properties
+ val unknownContentValues = getUnknownContentValues()
+ unknownContentValues.forEach { unknownValues ->
+ val unknwn = Unknown().apply {
+ unknownValues.getAsLong(JtxContract.JtxUnknown.ID)?.let { id -> this.unknownId = id }
+ unknownValues.getAsString(JtxContract.JtxUnknown.UNKNOWN_VALUE)?.let { value -> this.value = value }
+ }
+ unknown.add(unknwn)
+ }
+ }
+
+ /**
+ * Puts the current JtxICalObjects attributes into Content Values
+ * @return The JtxICalObject attributes as [ContentValues] (exluding list properties)
+ */
+ private fun toContentValues(): ContentValues {
+
+ val values = ContentValues()
+ values.put(JtxContract.JtxICalObject.ID, id)
+ summary.let { values.put(JtxContract.JtxICalObject.SUMMARY, it) }
+ description.let { values.put(JtxContract.JtxICalObject.DESCRIPTION, it) }
+ values.put(JtxContract.JtxICalObject.COMPONENT, component)
+ status.let { values.put(JtxContract.JtxICalObject.STATUS, it) }
+ classification.let { values.put(JtxContract.JtxICalObject.CLASSIFICATION, it) }
+ priority.let { values.put(JtxContract.JtxICalObject.PRIORITY, it) }
+ values.put(JtxContract.JtxICalObject.ICALOBJECT_COLLECTIONID, collectionId)
+ values.put(JtxContract.JtxICalObject.UID, uid)
+ geoLat.let { values.put(JtxContract.JtxICalObject.GEO_LAT, it) }
+ geoLong.let { values.put(JtxContract.JtxICalObject.GEO_LONG, it) }
+ location.let { values.put(JtxContract.JtxICalObject.LOCATION, it) }
+ locationAltrep.let { values.put(JtxContract.JtxICalObject.LOCATION_ALTREP, it) }
+ percent.let { values.put(JtxContract.JtxICalObject.PERCENT, it) }
+ values.put(JtxContract.JtxICalObject.DTSTAMP, dtstamp)
+ dtstart.let { values.put(JtxContract.JtxICalObject.DTSTART, it) }
+ dtstartTimezone.let { values.put(JtxContract.JtxICalObject.DTSTART_TIMEZONE, it) }
+ dtend.let { values.put(JtxContract.JtxICalObject.DTEND, it) }
+ dtendTimezone.let { values.put(JtxContract.JtxICalObject.DTEND_TIMEZONE, it) }
+ completed.let { values.put(JtxContract.JtxICalObject.COMPLETED, it) }
+ completedTimezone.let { values.put(JtxContract.JtxICalObject.COMPLETED_TIMEZONE, it) }
+ due.let { values.put(JtxContract.JtxICalObject.DUE, it) }
+ dueTimezone.let { values.put(JtxContract.JtxICalObject.DUE_TIMEZONE, it) }
+ duration.let { values.put(JtxContract.JtxICalObject.DURATION, it) }
+
+ rrule.let { values.put(JtxContract.JtxICalObject.RRULE, it) }
+ rdate.let { values.put(JtxContract.JtxICalObject.RDATE, it) }
+ exdate.let { values.put(JtxContract.JtxICalObject.EXDATE, it) }
+ recurid.let { values.put(JtxContract.JtxICalObject.RECURID, it) }
+
+ fileName.let { values.put(JtxContract.JtxICalObject.FILENAME, it) }
+ eTag.let { values.put(JtxContract.JtxICalObject.ETAG, it) }
+ scheduleTag.let { values.put(JtxContract.JtxICalObject.SCHEDULETAG, it) }
+ values.put(JtxContract.JtxICalObject.FLAGS, flags)
+
+ return values
+ }
+
+
+ /**
+ * @return The categories of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getCategoryContentValues(): List<ContentValues> {
+
+ val categoryUrl = JtxContract.JtxCategory.CONTENT_URI.asSyncAdapter(collection.account)
+ val categoryValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ categoryUrl,
+ null,
+ "${JtxContract.JtxCategory.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ categoryValues.add(cursor.toValues())
+ }
+ }
+ return categoryValues
+ }
+
+
+ /**
+ * @return The comments of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getCommentContentValues(): List<ContentValues> {
+
+ val commentUrl = JtxContract.JtxComment.CONTENT_URI.asSyncAdapter(collection.account)
+ val commentValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ commentUrl,
+ null,
+ "${JtxContract.JtxComment.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ commentValues.add(cursor.toValues())
+ }
+ }
+ return commentValues
+ }
+
+ /**
+ * @return The resources of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getResourceContentValues(): List<ContentValues> {
+
+ val resourceUrl = JtxContract.JtxResource.CONTENT_URI.asSyncAdapter(collection.account)
+ val resourceValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ resourceUrl,
+ null,
+ "${JtxContract.JtxResource.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ resourceValues.add(cursor.toValues())
+ }
+ }
+ return resourceValues
+ }
+
+ /**
+ * @return The RelatedTo of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getRelatedToContentValues(): List<ContentValues> {
+
+ val relatedToUrl = JtxContract.JtxRelatedto.CONTENT_URI.asSyncAdapter(collection.account)
+ val relatedToValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ relatedToUrl,
+ null,
+ "${JtxContract.JtxRelatedto.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ relatedToValues.add(cursor.toValues())
+ }
+ }
+ return relatedToValues
+ }
+
+ /**
+ * @return The attendees of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getAttendeesContentValues(): List<ContentValues> {
+
+ val attendeesUrl = JtxContract.JtxAttendee.CONTENT_URI.asSyncAdapter(collection.account)
+ val attendeesValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ attendeesUrl,
+ null,
+ "${JtxContract.JtxAttendee.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ attendeesValues.add(cursor.toValues())
+ }
+ }
+ return attendeesValues
+ }
+
+ /**
+ * @return The organizer of the given JtxICalObject as ContentValues
+ */
+ private fun getOrganizerContentValues(): ContentValues? {
+
+ val organizerUrl = JtxContract.JtxOrganizer.CONTENT_URI.asSyncAdapter(collection.account)
+ collection.client.query(
+ organizerUrl,
+ null,
+ "${JtxContract.JtxOrganizer.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ if(cursor.moveToFirst()) {
+ return cursor.toValues()
+ }
+ }
+ return null
+ }
+
+ /**
+ * @return The attachments of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getAttachmentsContentValues(): List<ContentValues> {
+
+ val attachmentsUrl =
+ JtxContract.JtxAttachment.CONTENT_URI.asSyncAdapter(collection.account)
+ val attachmentsValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ attachmentsUrl,
+ null,
+ "${JtxContract.JtxAttachment.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ attachmentsValues.add(cursor.toValues())
+ }
+ }
+ return attachmentsValues
+ }
+
+ /**
+ * @return The alarms of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getAlarmsContentValues(): List<ContentValues> {
+
+ val alarmsUrl =
+ JtxContract.JtxAlarm.CONTENT_URI.asSyncAdapter(collection.account)
+ val alarmValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ alarmsUrl,
+ null,
+ "${JtxContract.JtxAlarm.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ alarmValues.add(cursor.toValues())
+ }
+ }
+ return alarmValues
+ }
+
+ /**
+ * @return The unknown properties of the given JtxICalObject as a list of ContentValues
+ */
+ private fun getUnknownContentValues(): List<ContentValues> {
+
+ val unknownUrl =
+ JtxContract.JtxUnknown.CONTENT_URI.asSyncAdapter(collection.account)
+ val unknownValues: MutableList<ContentValues> = mutableListOf()
+ collection.client.query(
+ unknownUrl,
+ null,
+ "${JtxContract.JtxUnknown.ICALOBJECT_ID} = ?",
+ arrayOf(this.id.toString()),
+ null
+ )?.use { cursor ->
+ while (cursor.moveToNext()) {
+ unknownValues.add(cursor.toValues())
+ }
+ }
+ return unknownValues
+ }
+}
diff --git a/src/main/java/at/bitfire/ical4android/JtxICalObjectFactory.kt b/src/main/java/at/bitfire/ical4android/JtxICalObjectFactory.kt
new file mode 100644
index 0000000..8f6648c
--- /dev/null
+++ b/src/main/java/at/bitfire/ical4android/JtxICalObjectFactory.kt
@@ -0,0 +1,13 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.ical4android
+
+import android.content.ContentValues
+
+interface JtxICalObjectFactory<out T: JtxICalObject> {
+
+ fun fromProvider(collection: JtxCollection<JtxICalObject>, values: ContentValues): T
+
+} \ No newline at end of file
diff --git a/src/main/java/at/bitfire/ical4android/MiscUtils.kt b/src/main/java/at/bitfire/ical4android/MiscUtils.kt
index a9a91ec..2b0e57d 100644
--- a/src/main/java/at/bitfire/ical4android/MiscUtils.kt
+++ b/src/main/java/at/bitfire/ical4android/MiscUtils.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/Task.kt b/src/main/java/at/bitfire/ical4android/Task.kt
index 6e84a0f..0e70ca0 100644
--- a/src/main/java/at/bitfire/ical4android/Task.kt
+++ b/src/main/java/at/bitfire/ical4android/Task.kt
@@ -1,12 +1,6 @@
-/*
- * Copyright © Ricki Hirner and contributors (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- *
- * Contributors: Alex Baker
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/main/java/at/bitfire/ical4android/TaskProvider.kt b/src/main/java/at/bitfire/ical4android/TaskProvider.kt
index da8dd38..108875f 100644
--- a/src/main/java/at/bitfire/ical4android/TaskProvider.kt
+++ b/src/main/java/at/bitfire/ical4android/TaskProvider.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
@@ -35,7 +31,8 @@ class TaskProvider private constructor(
private val writePermission: String
) {
OpenTasks("org.dmfs.tasks", "org.dmfs.tasks", 103, "1.1.8.2", PERMISSION_OPENTASKS_READ, PERMISSION_OPENTASKS_WRITE),
- TasksOrg("org.tasks.opentasks", "org.tasks", 100000, "10.0", PERMISSION_TASKS_ORG_READ, PERMISSION_TASKS_ORG_WRITE);
+ TasksOrg("org.tasks.opentasks", "org.tasks", 100000, "10.0", PERMISSION_TASKS_ORG_READ, PERMISSION_TASKS_ORG_WRITE),
+ JtxBoard("at.techbee.jtx.provider", "at.techbee.jtx", 1, "0.1", PERMISSION_JTX_READ, PERMISSION_JTX_WRITE);
companion object {
fun fromAuthority(authority: String): ProviderName {
@@ -54,7 +51,8 @@ class TaskProvider private constructor(
val TASK_PROVIDERS = listOf(
ProviderName.OpenTasks,
- ProviderName.TasksOrg
+ ProviderName.TasksOrg,
+ ProviderName.JtxBoard
)
const val PERMISSION_OPENTASKS_READ = "org.dmfs.permission.READ_TASKS"
@@ -65,6 +63,10 @@ class TaskProvider private constructor(
const val PERMISSION_TASKS_ORG_WRITE = "org.tasks.permission.WRITE_TASKS"
val PERMISSIONS_TASKS_ORG = arrayOf(PERMISSION_TASKS_ORG_READ, PERMISSION_TASKS_ORG_WRITE)
+ const val PERMISSION_JTX_READ = "at.techbee.jtx.permission.READ"
+ const val PERMISSION_JTX_WRITE = "at.techbee.jtx.permission.WRITE"
+ val PERMISSIONS_JTX = arrayOf(PERMISSION_JTX_READ, PERMISSION_JTX_WRITE)
+
/**
* Acquires a content provider for a given task provider. The content provider will
* be released when the TaskProvider is closed with [close].
diff --git a/src/main/java/at/bitfire/ical4android/UnknownProperty.kt b/src/main/java/at/bitfire/ical4android/UnknownProperty.kt
index 2ffad4a..f1fba04 100644
--- a/src/main/java/at/bitfire/ical4android/UnknownProperty.kt
+++ b/src/main/java/at/bitfire/ical4android/UnknownProperty.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import android.content.ContentResolver
diff --git a/src/main/java/at/bitfire/ical4android/UsesThreadContextClassLoader.kt b/src/main/java/at/bitfire/ical4android/UsesThreadContextClassLoader.kt
index 68f1093..8bc96ba 100644
--- a/src/main/java/at/bitfire/ical4android/UsesThreadContextClassLoader.kt
+++ b/src/main/java/at/bitfire/ical4android/UsesThreadContextClassLoader.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
@Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
diff --git a/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt b/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt
index 1a9c1b2..e0c8c11 100644
--- a/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt
+++ b/src/main/java/at/bitfire/ical4android/util/AndroidTimeUtils.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
@file:Suppress("DEPRECATION")
package at.bitfire.ical4android.util
diff --git a/src/main/java/at/bitfire/ical4android/util/TimeApiExtensions.kt b/src/main/java/at/bitfire/ical4android/util/TimeApiExtensions.kt
index af9e407..f9b1ac1 100644
--- a/src/main/java/at/bitfire/ical4android/util/TimeApiExtensions.kt
+++ b/src/main/java/at/bitfire/ical4android/util/TimeApiExtensions.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android.util
import at.bitfire.ical4android.DateUtils
diff --git a/src/main/java/at/techbee/jtx/JtxContract.kt b/src/main/java/at/techbee/jtx/JtxContract.kt
new file mode 100644
index 0000000..ce1f42e
--- /dev/null
+++ b/src/main/java/at/techbee/jtx/JtxContract.kt
@@ -0,0 +1,1409 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.techbee.jtx
+
+import android.accounts.Account
+import android.net.Uri
+import android.provider.BaseColumns
+import android.util.Log
+import at.bitfire.ical4android.Ical4Android
+import net.fortuna.ical4j.model.ParameterList
+import net.fortuna.ical4j.model.Property
+import net.fortuna.ical4j.model.PropertyList
+import net.fortuna.ical4j.model.parameter.XParameter
+import net.fortuna.ical4j.model.property.XProperty
+import org.json.JSONObject
+import java.util.logging.Level
+
+@Suppress("unused")
+object JtxContract {
+
+ /**
+ * URI parameter to signal that the caller is a sync adapter.
+ */
+ const val CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"
+
+ /**
+ * URI parameter to submit the account name of the account we operate on.
+ */
+ const val ACCOUNT_NAME = "account_name"
+
+ /**
+ * URI parameter to submit the account type of the account we operate on.
+ */
+ const val ACCOUNT_TYPE = "account_type"
+
+ /** The authority under which the content provider can be accessed */
+ const val AUTHORITY = "at.techbee.jtx.provider"
+
+ /** The version of this SyncContentProviderContract */
+ const val VERSION = 1
+
+ /** Constructs an Uri for the Jtx Sync Adapter with the given Account
+ * @param [account] The account that should be appended to the Base Uri
+ * @return [Uri] with the appended Account
+ */
+ fun Uri.asSyncAdapter(account: Account): Uri =
+ buildUpon()
+ .appendQueryParameter(CALLER_IS_SYNCADAPTER, "true")
+ .appendQueryParameter(ACCOUNT_NAME, account.name)
+ .appendQueryParameter(ACCOUNT_TYPE, account.type)
+ .build()
+
+ /**
+ * This function takes a string and tries to parse it to a list of XParameters.
+ * This is the counterpart of getJsonStringFromXParameters(...)
+ * @param [string] that should be parsed
+ * @return The list of XParameter parsed from the string
+ */
+ fun getXParametersFromJson(string: String): List<XParameter> {
+ val jsonObject = JSONObject(string)
+ val xparamList = mutableListOf<XParameter>()
+ for (i in 0 until jsonObject.length()) {
+ val names = jsonObject.names() ?: break
+ val xparamName = names[i]?.toString() ?: break
+ val xparamValue = jsonObject.getString(xparamName).toString()
+ if (xparamName.isNotBlank() && xparamValue.isNotBlank()) {
+ val xparam = XParameter(xparamName, xparamValue)
+ xparamList.add(xparam)
+ }
+ }
+ return xparamList
+ }
+
+ /**
+ * This function takes a string and tries to parse it to a list of XProperty.
+ * This is the counterpart of getJsonStringFromXProperties(...)
+ * @param [string] that should be parsed
+ * @return The list of XProperty parsed from the string
+ */
+ fun getXPropertyListFromJson(string: String): PropertyList<Property> {
+ val propertyList = PropertyList<Property>()
+
+ if (string.isBlank())
+ return propertyList
+
+ try {
+ val jsonObject = JSONObject(string)
+ for (i in 0 until jsonObject.length()) {
+ val names = jsonObject.names() ?: break
+ val propertyName = names[i]?.toString() ?: break
+ val propertyValue = jsonObject.getString(propertyName).toString()
+ if (propertyName.isNotBlank() && propertyValue.isNotBlank()) {
+ val prop = XProperty(propertyName, propertyValue)
+ propertyList.add(prop)
+ }
+ }
+ } catch (e: NullPointerException) {
+ Ical4Android.log.log(Level.WARNING, "Error parsing x-property-list $string", e)
+ }
+ return propertyList
+ }
+
+
+ /**
+ * Takes a Parameter List and returns a Json String to be saved in a DB field.
+ * This is the counterpart to getXParameterFromJson(...)
+ * @param [parameters] The ParameterList that should be transformed into a Json String
+ * @return The generated Json object as a [String]
+ */
+ fun getJsonStringFromXParameters(parameters: ParameterList?): String? {
+ if (parameters == null)
+ return null
+
+ val jsonObject = JSONObject()
+ parameters.forEach { parameter ->
+ jsonObject.put(parameter.name, parameter.value)
+ }
+ return if (jsonObject.length() == 0)
+ null
+ else
+ jsonObject.toString()
+ }
+
+ /**
+ * Takes a Property List and returns a Json String to be saved in a DB field.
+ * This is the counterpart to getXPropertyListFromJson(...)
+ * @param [propertyList] The PropertyList that should be transformed into a Json String
+ * @return The generated Json object as a [String]
+ */
+ fun getJsonStringFromXProperties(propertyList: PropertyList<*>?): String? {
+ if (propertyList == null)
+ return null
+
+ val jsonObject = JSONObject()
+ propertyList.forEach { property ->
+ jsonObject.put(property.name, property.value)
+ }
+ return if (jsonObject.length() == 0)
+ null
+ else
+ jsonObject.toString()
+ }
+
+
+ /**
+ * Some date fields in jtx Board are saved as a list of Long values separated by commas.
+ * This applies for example to the exdate for recurring events.
+ * This function takes a string and tries to parse it to a list of long values (timestamps)
+ * @param [string] that should be parsed
+ * @return a [MutableList<Long>] with the timestamps parsed from the string
+ *
+ */
+ fun getLongListFromString(string: String): MutableList<Long> {
+ val stringList = string.split(",")
+ val longList = mutableListOf<Long>()
+
+ stringList.forEach {
+ try {
+ longList.add(it.toLong())
+ } catch (e: NumberFormatException) {
+ Ical4Android.log.log(Level.WARNING, "String could not be cast to Long ($it)")
+ return@forEach
+ }
+ }
+ return longList
+ }
+
+
+ @Suppress("unused")
+ object JtxICalObject {
+
+ /** The name of the the content URI for IcalObjects.
+ * This is a general purpose table containing general columns
+ * for Journals, Notes and Todos */
+ private const val CONTENT_URI_PATH = "icalobject"
+
+ /** The content uri of the ICalObject table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+ /** Constant to define all day values (for dtstart, due, completed timezone fields */
+ const val TZ_ALLDAY = "ALLDAY"
+
+ /** The name of the ID column.
+ * This is the unique identifier of an ICalObject.
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The column for the module.
+ * This is an internal differentiation for JOURNAL, NOTE and TODO as provided in the enum [Module].
+ * The Module does not need to be set. On import it will be derived from the component from the [Component] (Todo or Journal/Note) and if a
+ * dtstart is present or not (Journal or Note). If the module was set, it might be overwritten on import. In this sense also
+ * unknown values are overwritten.
+ * Use e.g. Module.JOURNAL.name for a correct String value in this field.
+ * Type: [String]
+ */
+ const val MODULE = "module"
+
+ /***** The names of all the other columns *****/
+ /** The column for the component based on the values provided in the enum [Component].
+ * Use e.g. Component.VTODO.name for a correct String value in this field.
+ * If no Component is provided on Insert, an IllegalArgumentException is thrown
+ * Type: [String]
+ */
+ const val COMPONENT = "component"
+
+ /**
+ * Purpose: This column/property defines a short summary or subject for the calendar component.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.12]
+ * Type: [String]
+ */
+ const val SUMMARY = "summary"
+
+ /**
+ * Purpose: This column/property provides a more complete description of the calendar component than that provided by the "SUMMARY" property.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.5]
+ * Type: [String]
+ */
+ const val DESCRIPTION = "description"
+
+ /**
+ * Purpose: This column/property specifies when the calendar component begins.
+ * This value is stored as UNIX timestamp (milliseconds).
+ * The corresponding timezone is stored in [DTSTART_TIMEZONE].
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.4]
+ * Type: [Long]
+ */
+ const val DTSTART = "dtstart"
+
+ /**
+ * Purpose: This column/property specifies the timezone of when the calendar component begins.
+ * The corresponding datetime is stored in [DTSTART].
+ * The value of a timezone can be:
+ * 1. the id of a Java timezone to represent the given timezone.
+ * 2. null to represent floating time.
+ * 3. the value of [TZ_ALLDAY] to represent an all-day entry.
+ * If an invalid value is passed, the Timezone is ignored and interpreted as UTC.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.4]
+ * Type: [String]
+ */
+ const val DTSTART_TIMEZONE = "dtstarttimezone"
+
+ /**
+ * Purpose: This column/property specifies when the calendar component ends.
+ * This value is stored as UNIX timestamp (milliseconds).
+ * The corresponding timezone is stored in [DTEND_TIMEZONE].
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.4]
+ * Type: [Long]
+ */
+ const val DTEND = "dtend"
+
+ /**
+ * Purpose: This column/property specifies the timezone of when the calendar component ends.
+ * The corresponding datetime is stored in [DTEND].
+ * See [DTSTART_TIMEZONE] for information about the timezone handling
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.2]
+ * Type: [String]
+ */
+ const val DTEND_TIMEZONE = "dtendtimezone"
+
+ /**
+ * Purpose: This property defines the overall status or confirmation for the calendar component.
+ * The possible values of a status are defined in [StatusTodo] for To-Dos and in [StatusJournal] for Notes and Journals
+ * If no valid Status is used, the value is taken and will be shown as-is.
+ * Also null-value is allowed.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.11]
+ * Type: [String]
+ */
+ const val STATUS = "status"
+
+ /**
+ * Purpose: This property defines the access classification for a calendar component.
+ * The possible values of a status are defined in the enum [Classification].
+ * Use e.g. Classification.PUBLIC.name to put a correct String value in this field.
+ * If no valid Classification is used, the value is taken and will be shown as-is.
+ * Also null-value is allowed.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.11]
+ * Type: [String]
+ */
+ const val CLASSIFICATION = "classification"
+
+ /**
+ * Purpose: This property defines a Uniform Resource Locator (URL) associated with the iCalendar object.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.4.6]
+ * Type: [String]
+ */
+ const val URL = "url"
+
+ /**
+ * Purpose: This property is used to represent contact information or alternately a reference
+ * to contact information associated with the calendar component.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.4.2]
+ * Type: [String]
+ */
+ const val CONTACT = "contact"
+
+ /**
+ * Purpose: This property specifies information related to the global position for the activity specified by a calendar component.
+ * This property is split in the fields [GEO_LAT] for the latitude
+ * and [GEO_LONG] for the longitude coordinates using the WGS84 ellipsoid.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.6]
+ * Type: [Float]
+ */
+ const val GEO_LAT = "geolat"
+
+ /**
+ * Purpose: This property specifies information related to the global position for the activity specified by a calendar component.
+ * This property is split in the fields [GEO_LAT] for the latitude
+ * and [GEO_LONG] for the longitude coordinates using the WGS84 ellipsoid.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.6]
+ * Type: [Double]
+ */
+ const val GEO_LONG = "geolong"
+
+ /**
+ * Purpose: This property defines the intended venue for the activity defined by a calendar component.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.7]
+ * Type: [Double]
+ */
+ const val LOCATION = "location"
+
+ /**
+ * Purpose: This property defines the alternative representation of the intended venue for the activity defined by a calendar component.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.7]
+ * Type: [String]
+ */
+ const val LOCATION_ALTREP = "locationaltrep"
+
+ /**
+ * Purpose: This property is used by an assignee or delegatee of a to-do to convey the percent completion of a to-do to the "Organizer".
+ * The value must either be null or between 0 and 100. The value 0 is interpreted as null.
+ * If a value outside of the boundaries (0-100) is passed, the value is reset to null.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.8]
+ * Type: [Int]
+ */
+ const val PERCENT = "percent"
+
+ /**
+ * Purpose: This property defines the relative priority for a calendar component.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.1.9]. The priority can be null or between 0 and 9.
+ * Values outside of these boundaries are accepted but internally not mapped to a string resource.
+ * Type: [Int]
+ */
+ const val PRIORITY = "priority"
+
+ /**
+ * Purpose: This property defines the date and time that a to-do is expected to be completed.
+ * This value is stored as UNIX timestamp (milliseconds).
+ * The corresponding timezone is stored in [DUE_TIMEZONE].
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.3]
+ * Type: [Long]
+ */
+ const val DUE = "due"
+
+ /**
+ * Purpose: This column/property specifies the timezone of when a to-do is expected to be completed.
+ * The corresponding datetime is stored in [DUE].
+ * See [DTSTART_TIMEZONE] for information about the timezone handling
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.2]
+ * Type: [String]
+ */
+ const val DUE_TIMEZONE = "duetimezone"
+
+ /**
+ * Purpose: This property defines the date and time that a to-do was actually completed.
+ * This value is stored as UNIX timestamp (milliseconds).
+ * The corresponding timezone is stored in [COMPLETED_TIMEZONE].
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.1]
+ * Type: [Long]
+ */
+ const val COMPLETED = "completed"
+
+ /**
+ * Purpose: This column/property specifies the timezone of when a to-do was actually completed.
+ * The corresponding datetime is stored in [COMPLETED].
+ * See [DTSTART_TIMEZONE] for information about the timezone handling
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.1]
+ * Type: [String]
+ */
+ const val COMPLETED_TIMEZONE = "completedtimezone"
+
+ /**
+ * Purpose: This property specifies a positive duration of time.
+ * See [DTSTART_TIMEZONE] for information about the timezone handling
+ * The string representation follows the notation as given in RFC-5545
+ * for the value type duration: [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.6]
+ * Exampe: "P15DT5H0M20S". This field is currently not in use. If present, the user would
+ * see a notification that a duration cannot be processed and will be overwritten if the entry is changed.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.5]
+ * Type: [String]
+ */
+ const val DURATION = "duration"
+
+
+ /**
+ * Purpose: This property defines a rule or repeating pattern for recurring events,
+ * to-dos, journal entries, or time zone definitions.
+ * The representation of the RRULE follows the value type RECUR of RFC-5545 as given in
+ * [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10]
+ * For example: "FREQ=DAILY;COUNT=10"
+ * See also [https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.5.3].
+ * Type: [String]
+ */
+ const val RRULE = "rrule"
+
+ /**
+ * Purpose: This property defines the list of DATE-TIME values for
+ * recurring events, to-dos, journal entries, or time zone definitions.
+ * Type: [String], contains a list of comma-separated date values as
+ * UNIX timestamps (milliseconds) e.g. "1636751475000,1636837875000".
+ * Invalid values that cannot be transformed to [Long] are ignored.
+ */
+ const val RDATE = "rdate"
+
+ /**
+ * Purpose: This property defines the list of DATE-TIME exceptions for
+ * recurring events, to-dos, journal entries, or time zone definitions.
+ * Type: [String], contains a list of comma-separated date values as
+ * UNIX timestamps (milliseconds) e.g. "1636751475000,1636837875000".
+ * Invalid values that cannot be transformed to [Long] are ignored.
+ */
+ const val EXDATE = "exdate"
+
+ /**
+ * Purpose: This property is used in conjunction with the "UID" and
+ * "SEQUENCE" properties to identify a specific instance of a
+ * recurring "VEVENT", "VTODO", or "VJOURNAL" calendar component.
+ * The property value is the original value of the "DTSTART" property
+ * of the recurrence instance, ie. a DATE or DATETIME value e.g. "20211101T160000".
+ * Must be null for non-recurring and original events from which recurring events are derived.
+ * Type: [String]
+ */
+ const val RECURID = "recurid"
+
+ /**
+ * Stores the reference to the original event from which the recurring event was derived.
+ * This value is NULL for the orignal event or if the event is not recurring
+ * Type: [Long], References [JtxICalObject.ID]
+ */
+ const val RECUR_ORIGINALICALOBJECTID = "original_id"
+
+ /**
+ * Purpose: This property defines the persistent, globally unique identifier for the calendar component.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.4.7]
+ * Type: [String]
+ */
+ const val UID = "uid"
+
+ /**
+ * Purpose: This property specifies the date and time that the calendar information
+ * was created by the calendar user agent in the calendar store and is stored as UNIX timestamp (milliseconds).
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.7.1]
+ * Type: [Long]
+ */
+ const val CREATED = "created"
+
+ /**
+ * Purpose: In the case of an iCalendar object that specifies a
+ * "METHOD" property, this property specifies the date and time that
+ * the instance of the iCalendar object was created. In the case of
+ * an iCalendar object that doesn't specify a "METHOD" property, this
+ * property specifies the date and time that the information
+ * associated with the calendar component was last revised in the
+ * calendar store. It is saved as UNIX timestamp (milliseconds).
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.7.2]
+ * Type: [Long]
+ */
+ const val DTSTAMP = "dtstamp"
+
+ /**
+ * Purpose: This property specifies the date and time that the information associated
+ * with the calendar component was last revised in the calendar store.
+ * It is saved as UNIX timestamp (milliseconds).
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.7.3]
+ * Type: [Long]
+ */
+ const val LAST_MODIFIED = "lastmodified"
+
+ /**
+ * Purpose: This property defines the revision sequence number of the calendar component within a sequence of revisions.
+ * If no sequence is present, it is automatically initialized with 0
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.7.4]
+ * Type: [Int]
+ */
+ const val SEQUENCE = "sequence"
+
+ /**
+ * Purpose: This property specifies a color used for displaying the calendar, event, todo, or journal data.
+ * See [https://tools.ietf.org/html/rfc7986#section-5.9].
+ * The color is represented as Int-value as described in [https://developer.android.com/reference/android/graphics/Color#color-ints]
+ * Type: [Int]
+ */
+ const val COLOR = "color"
+
+ /**
+ * Purpose: This column is the foreign key to the [JtxCollection].
+ * Type: [Long], references [JtxCollection.ID]
+ */
+ const val ICALOBJECT_COLLECTIONID = "collectionId"
+
+ /**
+ * Purpose: This column defines if the local ICalObject was changed and that it is supposed to be synchronised.
+ * Type: [Boolean]
+ */
+ const val DIRTY = "dirty"
+
+ /**
+ * Purpose: This column defines if a collection that is supposed to be synchonized was locally marked as deleted.
+ * Type: [Boolean]
+ */
+ const val DELETED = "deleted"
+
+ /**
+ * Purpose: The remote file name of the synchronized entry (*.ics), only relevant for synchronized
+ * entries through the sync-adapter
+ * Type: [String]
+ */
+ const val FILENAME = "filename"
+
+ /**
+ * Purpose: eTag for SyncAdapter, only relevant for synched entries through sync-adapter
+ * Type: [String]
+ */
+ const val ETAG = "etag"
+
+ /**
+ * Purpose: scheduleTag for SyncAdapter, only relevant for synched entries through sync-adapter
+ * Type: [String]
+ */
+ const val SCHEDULETAG = "scheduletag"
+
+ /**
+ * Purpose: flags for SyncAdapter, only relevant for synched entries through sync-adapter
+ * Type: [Int]
+ */
+ const val FLAGS = "flags"
+
+ /**
+ * Purpose: To specify other properties for the ICalObject.
+ * This is especially used for additional properties that cannot be put into another field, especially relevant for the synchronization
+ * The Properties are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXProperties(PropertyList<*> that returns a Json String from the property list
+ * to be stored in this other field. The counterpart to this function is
+ * getXPropertyListFromJson(String) that returns a PropertyList from a Json that was created with getJsonStringFromXProperties()
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+
+ /**
+ * This enum class defines the possible values for the attribute status of an [JtxICalObject] for Journals/Notes
+ * Use its name when the string representation is required, e.g. StatusJournal.DRAFT.name.
+ */
+ enum class StatusJournal {
+ DRAFT, FINAL, CANCELLED
+ }
+
+ /**
+ * This enum class defines the possible values for the attribute status of an [JtxICalObject] for Todos
+ * Use its name when the string representation is required, e.g. StatusTodo.`NEEDS-ACTION`.name.
+ */
+ enum class StatusTodo {
+ `NEEDS-ACTION`, COMPLETED, `IN-PROCESS`, CANCELLED
+ }
+
+ /**
+ * This enum class defines the possible values for the attribute classification of an [JtxICalObject]
+ * Use its name when the string representation is required, e.g. Classification.PUBLIC.name.
+ */
+ enum class Classification {
+ PUBLIC, PRIVATE, CONFIDENTIAL
+ }
+
+ /**
+ * This enum class defines the possible values for the attribute component of an [JtxICalObject]
+ * Use its name when the string representation is required, e.g. Component.VJOURNAL.name.
+ */
+ enum class Component {
+ VJOURNAL, VTODO
+ }
+
+ /**
+ * This enum class defines the possible values for the attribute module of an [JtxICalObject]
+ * Use its name when the string representation is required, e.g. Module.JOURNAL.name.
+ */
+ enum class Module {
+ JOURNAL, NOTE, TODO
+ }
+ }
+
+
+ @Suppress("unused")
+ object JtxAttendee {
+
+ /** The name of the the table for Attendees that are linked to an ICalObject.
+ * [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] */
+ private const val CONTENT_URI_PATH = "attendee"
+
+ /** The content uri of the Attendee table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column.
+ * This is the unique identifier of an Attendee
+ * Type: [Long] */
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This value type is used to identify properties that contain a calendar user address (in this case of the attendee).
+ * This is usually an e-mail address as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.3].
+ * The value should be passed as String containing the URI with "mailto:", for example: "mailto:jane_doe@example.com"
+ * See also [https://tools.ietf.org/html/rfc5545#section-3.8.4.1].
+ * Type: [String]
+ */
+ const val CALADDRESS = "caladdress"
+
+ /**
+ * Purpose: To identify the type of calendar user specified by the property in this case for the attendee.
+ * The possible values are defined in the enum [Cutype].
+ * Use e.g. Cutype.INDIVIDUAL.name to put a correct String value in this field.
+ * If no value is passed for the Cutype, the Cutype will be interpreted as INDIVIDUAL as according to the RFC.
+ * Other values are accepted and treated as UNKNOWN.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.3]
+ * Type: [String]
+ */
+ const val CUTYPE = "cutype"
+
+ /**
+ * Purpose: To specify the group or list membership of the calendar user specified by the property in this case for the attendee.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.11]
+ * Type: [String]
+ */
+ const val MEMBER = "member"
+
+ /**
+ * Purpose: To specify the participation role for the calendar user specified by the property in this case for the attendee.
+ * The possible values are defined in the enum [Role]
+ * Use e.g. Role.CHAIR.name to put a correct String value in this field.
+ * If no value (null) is passed for the Role, it will be interpreted as REQ-PARTICIPANT as according to the RFC.
+ * Other values are accepted and treated as REQ-PARTICIPANTs.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.16]
+ * Type: [String]
+ */
+ const val ROLE = "role"
+
+ /**
+ * Purpose: To specify the participation status for the calendar user specified by the property in this case for the attendee.
+ * The possible values are defined in the enum [PartstatJournal] and [PartstatTodo]
+ * Use e.g. PartstatJournal.ACCEPTED.name to put a correct String value in this field.
+ * If no value (null) is passed for the Partstat, it will be interpreted as NEEDS-ACTION as according to the RFC.
+ * Other values are accepted and treated as NEEDS-ACTION.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.12]
+ * Type: [String]
+ */
+ const val PARTSTAT = "partstat"
+
+ /**
+ * Purpose: To specify whether there is an expectation of a favor of a reply from the calendar user specified by the property value
+ * in this case for the attendee.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.17]
+ * Type: [Boolean]
+ */
+ const val RSVP = "rsvp"
+
+ /**
+ * Purpose: To specify the calendar users to whom the calendar user specified by the property
+ * has delegated participation in this case for the attendee.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.5]
+ * Type: [String]
+ */
+ const val DELEGATEDTO = "delegatedto"
+
+ /**
+ * Purpose: To specify the calendar users that have delegated their participation to the calendar user specified by the property
+ * in this case for the attendee.
+ * This is usually an e-mail address as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.3].
+ * The value should be passed as String containing the URI with "mailto:", for example: "mailto:jane_doe@example.com"
+ * See also [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.4]
+ * Type: [String]
+ */
+ const val DELEGATEDFROM = "delegatedfrom"
+
+ /**
+ * Purpose: To specify the calendar user that is acting on behalf of the calendar user specified by the property in this case for the attendee.
+ * This is usually an e-mail address as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.3].
+ * The value should be passed as String containing the URI with "mailto:", for example: "mailto:jane_doe@example.com"
+ * See also [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.18]
+ * Type: [String]
+ */
+ const val SENTBY = "sentby"
+
+ /**
+ * Purpose: To specify the common name to be associated with the calendar user specified by the property in this case for the attendee.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.2]
+ * Type: [String]
+ */
+ const val CN = "cn"
+
+ /**
+ * Purpose: To specify reference to a directory entry associated with the calendar user specified by the property in this case for the attendee.
+ * Expected is an URI as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.6].
+ * The value should be passed as String, e.g. "ldap://example.com:6666/o=ABC%20Industries,c=US???(cn=Jim%20Dolittle)"
+ * Type: [String]
+ */
+ const val DIR = "dir"
+
+ /**
+ * Purpose: To specify the language for text values in a property or property parameter, in this case of the attendee.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1] and [https://tools.ietf.org/html/rfc5545#section-3.2.10]
+ * Language-Tag as defined in RFC5646, e.g. "en:Germany"
+ * Type: [String]
+ */
+ const val LANGUAGE = "language"
+
+ /**
+ * Purpose: To specify other parameters for the attendee.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.1]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+
+ /**
+ * This enum class defines the possible values for the attribute Cutype of an [JtxAttendee]
+ * Use its name when the string representation is required, e.g. Cutype.INDIVIDUAL.name.
+ */
+ enum class Cutype {
+ INDIVIDUAL, GROUP, RESOURCE, ROOM, UNKNOWN
+ }
+
+ /**
+ * This enum class defines the possible values for the attribute Role of an [JtxAttendee]
+ * Use its name when the string representation is required, e.g. Role.`REQ-PARTICIPANT`.name.
+ */
+ enum class Role {
+ CHAIR, `REQ-PARTICIPANT`, `OPT-PARTICIPANT`, `NON-PARTICIPANT`
+ }
+
+ /** This enum class defines the possible values for the attribute [JtxAttendee] for the Component VJOURNAL */
+ @Suppress("unused")
+ enum class PartstatJournal {
+ `NEEDS-ACTION`, ACCEPTED, DECLINED
+ }
+
+ /** This enum class defines the possible values for the attribute [JtxAttendee] for the Component VTODO */
+ @Suppress("unused")
+ enum class PartstatTodo {
+ `NEEDS-ACTION`, ACCEPTED, DECLINED, TENTATIVE, DELEGATED, COMPLETED, `IN-PROCESS`
+ }
+ }
+
+ @Suppress("unused")
+ object JtxCategory {
+
+ /** The name of the the table for Categories that are linked to an ICalObject.
+ * [https://tools.ietf.org/html/rfc5545#section-3.8.1.2]*/
+ private const val CONTENT_URI_PATH = "category"
+
+ /** The content uri of the Category table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for categories.
+ * This is the unique identifier of a Category
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This property defines the name of the category for a calendar component.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.2]
+ * Type: [String]
+ */
+ const val TEXT = "text"
+
+ /**
+ * Purpose: To specify the language for text values in a property or property parameter, in this case of the category.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.2] and [https://tools.ietf.org/html/rfc5545#section-3.2.10]
+ * Language-Tag as defined in RFC5646, e.g. "en:Germany"
+ * Type: [String]
+ */
+ const val LANGUAGE = "language"
+
+ /**
+ * Purpose: To specify other properties for the category.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.2]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+ }
+
+ @Suppress("unused")
+ object JtxComment {
+
+ /** The name of the the table for Comments that are linked to an ICalObject.
+ * [https://tools.ietf.org/html/rfc5545#section-3.8.1.4]*/
+ private const val CONTENT_URI_PATH = "comment"
+
+ /** The content uri of the Comment table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for comments.
+ * This is the unique identifier of a Comment
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This property specifies non-processing information intended to provide a comment to the calendar user.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.4]
+ * Type: [String]
+ */
+ const val TEXT = "text"
+
+ /**
+ * Purpose: To specify the language for text values in a property or property parameter, in this case of the comment.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.4]
+ * Language-Tag as defined in RFC5646, e.g. "en:Germany"
+ * Type: [String]
+ */
+ const val ALTREP = "altrep"
+
+ /**
+ * Purpose: To specify an alternate text representation for the property value, in this case of the comment.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.4]
+ * Type: [String]
+ */
+ const val LANGUAGE = "language"
+
+ /**
+ * Purpose: To specify other properties for the comment.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.4]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+ }
+
+
+ @Suppress("unused")
+ object JtxOrganizer {
+ /** The name of the the table for Organizer that are linked to an ICalObject.
+ * [https://tools.ietf.org/html/rfc5545#section-3.8.4.3]
+ */
+ private const val CONTENT_URI_PATH = "organizer"
+
+ /** The content uri of the Organizer table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for the organizer.
+ * This is the unique identifier of a Organizer
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This value type is used to identify properties that contain a calendar user address (in this case of the organizer).
+ * This is usually an e-mail address as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.3].
+ * The value should be passed as String containing the URI with "mailto:", for example: "mailto:jane_doe@example.com"
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.3].
+ * Type: [String]
+ */
+ const val CALADDRESS = "caladdress"
+
+ /**
+ * Purpose: To specify the common name to be associated with the calendar user specified by the property in this case for the organizer.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.3] and [https://tools.ietf.org/html/rfc5545#section-3.2.18]
+ * Type: [String]
+ */
+ const val CN = "cnparam"
+
+ /**
+ * Purpose: To specify reference to a directory entry associated with the calendar user specified by the property in this case for the organizer.
+ * Expected is an URI as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.6].
+ * The value should be passed as String, e.g. "ldap://example.com:6666/o=ABC%20Industries,c=US???(cn=Jim%20Dolittle)"
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.3] and [https://tools.ietf.org/html/rfc5545#section-3.2.2]
+ * Type: [String]
+ */
+ const val DIR = "dirparam"
+
+ /**
+ * Purpose: To specify the calendar user that is acting on behalf of the calendar user specified by the property in this case for the organizer.
+ * This is usually an e-mail address as defined in [https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.3].
+ * The value should be passed as String containing the URI with "mailto:", for example: "mailto:jane_doe@example.com"
+ * See also [https://tools.ietf.org/html/rfc5545#section-3.8.4.3] and [https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.18]
+ * Type: [String]
+ */
+ const val SENTBY = "sentbyparam"
+
+ /**
+ * Purpose: To specify the language for text values in a property or property parameter, in this case of the organizer.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.3] and [https://tools.ietf.org/html/rfc5545#section-3.2.10]
+ * Language-Tag as defined in RFC5646, e.g. "en:Germany"
+ * Type: [String]
+ */
+ const val LANGUAGE = "language"
+
+ /**
+ * Purpose: To specify other properties for the organizer.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.3]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+
+ }
+
+ @Suppress("unused")
+ object JtxRelatedto {
+
+ /** The name of the the table for Relationships (related-to) that are linked to an ICalObject.
+ * [https://tools.ietf.org/html/rfc5545#section-3.8.4.5]
+ */
+ private const val CONTENT_URI_PATH = "relatedto"
+
+ /** The content uri of the relatedto table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for the related-to.
+ * This is the unique identifier of a Related-to
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+ /** The name of the second Foreign Key Column of the related IcalObject
+ * Type: [Long]
+ */
+ const val LINKEDICALOBJECT_ID = "linkedICalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This property is used to represent a relationship or reference between one calendar component and another.
+ * The text gives the UID of the related calendar entry.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.5]
+ * Type: [String]
+ */
+ const val TEXT = "text"
+
+ /**
+ * Purpose: To specify the type of hierarchical relationship associated
+ * with the calendar component specified by the property.
+ * The possible relationship types are defined in the enum [Reltype].
+ * Use e.g. Reltype.PARENT.name to put a correct String value in this field.
+ * Other values and null-values are allowed, but will not be processed by the app.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.5] and [https://tools.ietf.org/html/rfc5545#section-3.2.15]
+ * Type: [String]
+ */
+ const val RELTYPE = "reltype"
+
+ /**
+ * Purpose: To specify other properties for the related-to.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.5]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+
+ /**
+ * This enum class defines the possible values for the attribute Reltype of an [JtxRelatedto].
+ * Use its name when the string representation is required, e.g. Reltype.PARENT.name.
+ */
+ enum class Reltype {
+ PARENT, CHILD, SIBLING
+ }
+
+ }
+
+ @Suppress("unused")
+ object JtxResource {
+ /** The name of the the table for Resources that are linked to an ICalObject.
+ * [https://tools.ietf.org/html/rfc5545#section-3.8.1.10]*/
+ private const val CONTENT_URI_PATH = "resource"
+
+ /** The content uri of the resources table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for resources.
+ * This is the unique identifier of a Resource
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This property defines the name of the resource for a calendar component.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.10]
+ * Type: [String]
+ */
+ const val TEXT = "text"
+
+ /**
+ * Purpose: To specify an alternate text representation for the property value, in this case of the resource.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.4]
+ * Language-Tag as defined in RFC5646, e.g. "en:Germany"
+ * Type: [String]
+ */
+ const val LANGUAGE = "language"
+
+ /**
+ * Purpose: To specify other properties for the resource.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.10]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+ }
+
+ @Suppress("unused")
+ object JtxCollection {
+
+ /** The name of the the table for Collections
+ * ICalObjects MUST be linked to a collection! */
+ private const val CONTENT_URI_PATH = "collection"
+
+ /** The content uri of the collections table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+ /**
+ * Account type used for testing. This account type must be used for integrated testing.
+ * Otherwise the application would check if an account exists in the Android accounts
+ * and delete the test account/collections immediately. Using this test account prevents
+ * this behaviour for Debug builds.
+ */
+ const val TEST_ACCOUNT_TYPE = "TEST"
+
+
+ /** The name of the the table for Collections.
+ * ICalObjects MUST be linked to a collection! */
+ const val TABLE_NAME_COLLECTION = "collection"
+
+ /** The name of the ID column for collections.
+ * This is the unique identifier of a Collection
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This column/property defines the url of the collection.
+ * Type: [String]
+ */
+ const val URL = "url"
+
+ /**
+ * Purpose: This column/property defines the display name of the collection.
+ * Type: [String]
+ */
+ const val DISPLAYNAME = "displayname"
+
+ /**
+ * Purpose: This column/property defines a description of the collection.
+ * Type: [String]
+ */
+ const val DESCRIPTION = "description"
+
+ /**
+ * Purpose: This column/property defines the owner of the collection.
+ * Type: [String]
+ */
+ const val OWNER = "owner"
+
+ /**
+ * Purpose: This column/property defines the color of the collection items.
+ * This color can also be overwritten by the color in an ICalObject.
+ * The color is represented as Int-value as described in [https://developer.android.com/reference/android/graphics/Color#color-ints]
+ * Type: [Int]
+ */
+ const val COLOR = "color"
+
+ /**
+ * Purpose: This column/property defines the if the collection supports VEVENTs.
+ * Type: [Boolean]
+ */
+ const val SUPPORTSVEVENT = "supportsVEVENT"
+
+ /**
+ * Purpose: This column/property defines the if the collection supports VTODOs.
+ * Type: [Boolean]
+ */
+ const val SUPPORTSVTODO = "supportsVTODO"
+
+ /**
+ * Purpose: This column/property defines the if the collection supports VJOURNALs.
+ * Type: [Boolean]
+ */
+ const val SUPPORTSVJOURNAL = "supportsVJOURNAL"
+
+ /**
+ * Purpose: This column/property defines the if the account name under which the collection resides.
+ * Type: [String]
+ */
+ const val ACCOUNT_NAME = "accountname"
+
+ /**
+ * Purpose: This column/property defines the if the account type under which the collection resides.
+ * Type: [String]
+ */
+ const val ACCOUNT_TYPE = "accounttype"
+
+ /**
+ * Purpose: This column/property defines a field for the Sync Version for the Sync Adapter
+ * Type: [String]
+ */
+ const val SYNC_VERSION = "syncversion"
+
+ /**
+ * Purpose: This column/property defines if a collection is marked as read-only by the Sync Adapter
+ * Type: [Boolean]
+ */
+ const val READONLY = "readonly"
+
+ }
+
+
+ @Suppress("unused")
+ object JtxAttachment {
+
+ /** The name of the the table for Attachments that are linked to an ICalObject.*/
+ private const val CONTENT_URI_PATH = "attachment"
+
+ /** The content uri of the resources table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for attachments.
+ * This is the unique identifier of an Attachment
+ * Type: [Long]*/
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This property specifies the uri of an attachment.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.1]
+ * Type: [String]
+ */
+ const val URI = "uri"
+
+ /**
+ * Purpose: To specify the value of the attachment (binary).
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.1]
+ * Type: [String]
+ */
+ const val BINARY = "binary"
+
+ /**
+ * Purpose: To specify the fmttype of the attachment.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.1]
+ * Type: [String]
+ */
+ const val FMTTYPE = "fmttype"
+
+ /**
+ * Purpose: To specify other properties for the attachment.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.1.1]
+ * The Parameters are stored as JSON. There are two helper functions provided:
+ * getJsonStringFromXParameters(ParameterList?) that returns a Json String from the parameter list
+ * to be stored in this other field. The counterpart to this function is
+ * getXParameterFromJson(String) that returns a list of XParameters from a Json that was created with getJsonStringFromXParameters(...)
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+ }
+
+
+ @Suppress("unused")
+ object JtxAlarm {
+
+ /** The name of the the table for Alarms that are linked to an ICalObject.*/
+ private const val CONTENT_URI_PATH = "alarm"
+
+ /** The content uri of the resources table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for attachments.
+ * This is the unique identifier of an Attachment
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+ /**
+ * Each "VALARM" calendar component has a particular type
+ * of action with which it is associated. This property specifies
+ * the type of action. Applications MUST ignore alarms with x-name
+ * and iana-token values they don't recognize.
+ * Currently only "DISPLAY" is supported, all other values can be stored but are ignored
+ * Type: [String]
+ */
+ const val ACTION = "action"
+
+ /**
+ * This property provides a more complete description of the
+ * calendar component than that provided by the "SUMMARY" property.
+ * Type: [String]
+ */
+ const val DESCRIPTION = "description"
+
+ /**
+ * This property defines a short summary or subject for the
+ * calendar component.
+ * Type: [String]
+ */
+ const val SUMMARY = "summary"
+
+ /**
+ * This property contains a CSV-list of attendees as Uris
+ * e.g. "mailto:contact@techbee.at,mailto:jtx@techbee.at"
+ */
+ const val ATTENDEE = "attendee"
+
+ /**
+ * The alarm can be defined such that it triggers repeatedly. A
+ * definition of an alarm with a repeating trigger MUST include both
+ * the "DURATION" and "REPEAT" properties. The "DURATION" property
+ * specifies the delay period, after which the alarm will repeat.
+ * Type: [String]
+ */
+ const val DURATION = "duration"
+
+ /**
+ * The "REPEAT" property specifies the number of additional
+ * repetitions that the alarm will be triggered. This repetition
+ * count is in addition to the initial triggering of the alarm. Both
+ * of these properties MUST be present in order to specify a
+ * repeating alarm. If one of these two properties is absent, then
+ * the alarm will not repeat beyond the initial trigger.
+ * Type: [String]
+ */
+ const val REPEAT = "repeat"
+
+ /**
+ * Contains the uri of an attachment
+ * Type: [String]
+ */
+ const val ATTACH = "attach"
+
+ /**
+ * Purpose: To specify other properties for the alarm.
+ * see [https://tools.ietf.org/html/rfc5545#section-3.8.4.3]
+ * Type: [String]
+ */
+ const val OTHER = "other"
+
+ /**
+ * Stores a timestamp with the absolute time when the alarm should be triggered.
+ * This value is stored as UNIX timestamp (milliseconds).
+ * Either a Alarm Trigger Time OR a Alarm Relative Duration must be provided, but not both!
+ * Type: [Long]
+ */
+ const val TRIGGER_TIME = "triggerTime"
+
+ /**
+ * Purpose: This column/property specifies the timezone of the absolute trigger time.
+ * The corresponding datetime is stored in [TRIGGER_TIME].
+ * The value of a timezone can be:
+ * 1. the id of a Java timezone to represent the given timezone.
+ * 2. null to represent floating time.
+ * If an invalid value is passed, the Timezone is ignored and interpreted as UTC.
+ * See [https://tools.ietf.org/html/rfc5545#section-3.8.2.4]
+ * Type: [String]
+ */
+ const val TRIGGER_TIMEZONE = "triggerTimezone"
+
+ /**
+ * Purpose: This property defines the field to which the duration is relatiive to.
+ * The possible values of a status are defined in the enum [AlarmRelativeTo].
+ * Use e.g. AlarmRelativeTo.START.name to put a correct String value in this field.
+ * AlarmRelativeTo.START would make the duration relative to DTSTART.
+ * AlarmRelativeTo.END would make the duration relative to DUE (only VTODO is supported!).
+ * If no valid AlarmRelativeTo is provided, the default value is AlarmRelativeTo.START.
+ * Type: [String]
+ */
+ const val TRIGGER_RELATIVE_TO = "triggerRelativeTo"
+
+ /**
+ * Purpose: Specifying a relative time for the
+ * trigger of the alarm. The default duration is relative to the
+ * start of an event or to-do with which the alarm is associated.
+ * The duration can be explicitly set to trigger from either the end
+ * or the start of the associated event or to-do with the [TRIGGER_RELATIVE_TO]
+ * parameter. A value of START will set the alarm to trigger off the
+ * start of the associated event or to-do. A value of END will set
+ * the alarm to trigger off the end of the associated event or to-do.
+ * Either a positive or negative duration may be specified for the
+ * "TRIGGER" property. An alarm with a positive duration is
+ * triggered after the associated start or end of the event or to-do.
+ * An alarm with a negative duration is triggered before the
+ * associated start or end of the event or to-do.
+ * Type: [String]
+ */
+ const val TRIGGER_RELATIVE_DURATION = "triggerRelativeDuration"
+
+ /** This enum class defines the possible values for the attribute [TRIGGER_RELATIVE_TO] for the Component VALARM */
+ @Suppress("unused")
+ enum class AlarmRelativeTo {
+ START, END
+ }
+
+ /** This enum class defines the possible values for the attribute [ACTION] for the Component VALARM */
+ @Suppress("unused")
+ enum class AlarmAction {
+ AUDIO, DISPLAY, EMAIL
+ }
+ }
+
+ @Suppress("unused")
+ object JtxUnknown {
+
+ /** The name of the the table for Unknown properties that are linked to an ICalObject.*/
+ private const val CONTENT_URI_PATH = "unknown"
+
+ /** The content uri of the resources table */
+ val CONTENT_URI: Uri by lazy { Uri.parse("content://$AUTHORITY/$CONTENT_URI_PATH") }
+
+
+ /** The name of the ID column for attachments.
+ * This is the unique identifier of an Attachment
+ * Type: [Long], references [JtxICalObject.ID]
+ */
+ const val ID = BaseColumns._ID
+
+ /** The name of the Foreign Key Column for IcalObjects.
+ * Type: [Long] */
+ const val ICALOBJECT_ID = "icalObjectId"
+
+
+ /***** The names of all the other columns *****/
+ /**
+ * Purpose: This property stores the unknown value as json
+ * Type: [String]
+ */
+ const val UNKNOWN_VALUE = "value"
+ }
+
+} \ No newline at end of file
diff --git a/src/test/java/at/bitfire/ical4android/AndroidTimeUtilsTest.kt b/src/test/java/at/bitfire/ical4android/AndroidTimeUtilsTest.kt
index 0d8e176..fd8bca0 100644
--- a/src/test/java/at/bitfire/ical4android/AndroidTimeUtilsTest.kt
+++ b/src/test/java/at/bitfire/ical4android/AndroidTimeUtilsTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import at.bitfire.ical4android.util.AndroidTimeUtils
diff --git a/src/test/java/at/bitfire/ical4android/Css3ColorTest.kt b/src/test/java/at/bitfire/ical4android/Css3ColorTest.kt
index a4bd6c0..9828752 100644
--- a/src/test/java/at/bitfire/ical4android/Css3ColorTest.kt
+++ b/src/test/java/at/bitfire/ical4android/Css3ColorTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/test/java/at/bitfire/ical4android/DateUtilsTest.kt b/src/test/java/at/bitfire/ical4android/DateUtilsTest.kt
index 6f03906..395b8da 100644
--- a/src/test/java/at/bitfire/ical4android/DateUtilsTest.kt
+++ b/src/test/java/at/bitfire/ical4android/DateUtilsTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/test/java/at/bitfire/ical4android/EventTest.kt b/src/test/java/at/bitfire/ical4android/EventTest.kt
index a2ce504..e4d25c8 100644
--- a/src/test/java/at/bitfire/ical4android/EventTest.kt
+++ b/src/test/java/at/bitfire/ical4android/EventTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
import net.fortuna.ical4j.model.Date
diff --git a/src/test/java/at/bitfire/ical4android/ICalPreprocessorTest.kt b/src/test/java/at/bitfire/ical4android/ICalPreprocessorTest.kt
index ca7433e..894452e 100644
--- a/src/test/java/at/bitfire/ical4android/ICalPreprocessorTest.kt
+++ b/src/test/java/at/bitfire/ical4android/ICalPreprocessorTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarBuilder
diff --git a/src/test/java/at/bitfire/ical4android/ICalendarTest.kt b/src/test/java/at/bitfire/ical4android/ICalendarTest.kt
index 329fd1b..7e1d0e7 100644
--- a/src/test/java/at/bitfire/ical4android/ICalendarTest.kt
+++ b/src/test/java/at/bitfire/ical4android/ICalendarTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/test/java/at/bitfire/ical4android/Ical4jTest.kt b/src/test/java/at/bitfire/ical4android/Ical4jTest.kt
index cec2c29..ad8d11b 100644
--- a/src/test/java/at/bitfire/ical4android/Ical4jTest.kt
+++ b/src/test/java/at/bitfire/ical4android/Ical4jTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarBuilder
diff --git a/src/test/java/at/bitfire/ical4android/MiscUtilsTest.kt b/src/test/java/at/bitfire/ical4android/MiscUtilsTest.kt
index 3ff78d9..e32ba0a 100644
--- a/src/test/java/at/bitfire/ical4android/MiscUtilsTest.kt
+++ b/src/test/java/at/bitfire/ical4android/MiscUtilsTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import org.junit.Assert.assertTrue
diff --git a/src/test/java/at/bitfire/ical4android/TaskTest.kt b/src/test/java/at/bitfire/ical4android/TaskTest.kt
index 850b8e2..cbe0bcc 100644
--- a/src/test/java/at/bitfire/ical4android/TaskTest.kt
+++ b/src/test/java/at/bitfire/ical4android/TaskTest.kt
@@ -1,10 +1,6 @@
-/*
- * Copyright © Ricki Hirner (bitfire web engineering).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the GNU Public License v3.0
- * which accompanies this distribution, and is available at
- * http://www.gnu.org/licenses/gpl.html
- */
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
package at.bitfire.ical4android
diff --git a/src/test/java/at/bitfire/ical4android/TimeApiExtensionsTest.kt b/src/test/java/at/bitfire/ical4android/TimeApiExtensionsTest.kt
index 3f632ca..44a02a0 100644
--- a/src/test/java/at/bitfire/ical4android/TimeApiExtensionsTest.kt
+++ b/src/test/java/at/bitfire/ical4android/TimeApiExtensionsTest.kt
@@ -1,3 +1,7 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
package at.bitfire.ical4android
import at.bitfire.ical4android.util.TimeApiExtensions.requireTimeZone