Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e246a55
Refactor AndroidEvent to use Entity for event data
rfc2822 Jul 9, 2025
34f63f4
Keep legacy AndroidEvent
rfc2822 Jul 9, 2025
38b4227
Keep legacy for InitCalendarProviderRule
rfc2822 Jul 9, 2025
2d719f2
Rename legacy calendar event builder/processing classes
rfc2822 Jul 9, 2025
97d72f0
Use low-level AndroidEvent in InitCalendarProviderRule
rfc2822 Jul 9, 2025
24c8a9d
Builder template
rfc2822 Jul 9, 2025
9f552f1
Merge branch 'main' into androidevent-entity
rfc2822 Jul 10, 2025
ead97a8
[WIP] Ideas AssociatedRows + collection
rfc2822 Jul 10, 2025
df873d2
AndroidEvent: move add() to LegacyAndroidCalendar
rfc2822 Jul 10, 2025
40f7df7
LegacyAndroidCalendar: add `getEvent()` for event retrieval
rfc2822 Jul 10, 2025
5679c67
LegacyAndroidCalendar: add `getEvent()` for event retrieval
rfc2822 Jul 10, 2025
b58a0f6
Move constants to AndroidEvent2
rfc2822 Jul 10, 2025
524a3f5
Move getLegacyEvent to LegacyAndroidCalendar
rfc2822 Jul 10, 2025
7fb2ed4
Add package structure to README
rfc2822 Jul 11, 2025
34d6c62
AndroidCalendarTest
rfc2822 Jul 11, 2025
7d6774c
Adapt numDirectInstances
rfc2822 Jul 11, 2025
5f7ff56
Fix updateEvent
rfc2822 Jul 11, 2025
706c725
[WIP] Fix tests
rfc2822 Jul 11, 2025
017b112
Simplify numDirectInstances / numInstances to only numInstances
rfc2822 Jul 11, 2025
2724e33
Imports; AndroidEvent2: add attendees/extended properties
rfc2822 Jul 11, 2025
8da9161
Fix InitCalendarProviderRule
rfc2822 Jul 11, 2025
3eab4eb
Adapt tests
rfc2822 Jul 12, 2025
5012782
Add method to find first event row
rfc2822 Jul 12, 2025
b9f83dd
Update findEventRow and iterateEventRows to accept nullable parameters
rfc2822 Jul 12, 2025
5b4a4ca
Update KDoc and AndroidEvent.toString
rfc2822 Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ We're happy about contributions! In case of bigger changes, please let us know i
Then make the changes in your own repository and send a pull request.


# Packages

- `at.bitfire.synctools`: new package where everything shall be refactored into
- `.icalendar`: high-level operations on iCalendar objects
- `.mapping`: mappers between low-level (database rows) and high-level (iCalendar/vCard) objects
- `.storage`: low-level operations on content-provider storage (`ContentValues` / `Entity` to store data)
- `at.bitfire.ical4android`: legacy [ical4android](https://github.com/bitfireAT/ical4android)
- `at.bitfire.vcard4android`: legacy [vcard4android](https://github.com/bitfireAT/vcard4android)



# How to use

Add the [jitpack.io](https://jitpack.io) repository to your project's level `build.gradle`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import at.bitfire.ical4android.impl.TestCalendar
import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.icalendar.Css3Color
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidEvent2
import at.bitfire.synctools.test.InitCalendarProviderRule
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.component.VAlarm
Expand Down Expand Up @@ -44,15 +45,17 @@ class AndroidEventTest {

private val testAccount = Account(javaClass.name, ACCOUNT_TYPE_LOCAL)

private lateinit var calendar: AndroidCalendar
lateinit var client: ContentProviderClient
private lateinit var calendar: AndroidCalendar
private lateinit var legacyCalendar: LegacyAndroidCalendar

@Before
fun prepare() {
val context = getInstrumentation().targetContext
client = context.contentResolver.acquireContentProviderClient(AUTHORITY)!!

calendar = TestCalendar.findOrCreate(testAccount, client, withColors = true)
legacyCalendar = LegacyAndroidCalendar(calendar)
}

@After
Expand All @@ -68,9 +71,9 @@ class AndroidEventTest {
calendar, contentValuesOf(
Events._ID to 123,
Events._SYNC_ID to "some-ical.ics",
AndroidEvent.COLUMN_ETAG to "some-etag",
AndroidEvent.COLUMN_SCHEDULE_TAG to "some-schedule-tag",
AndroidEvent.COLUMN_FLAGS to 45
AndroidEvent2.COLUMN_ETAG to "some-etag",
AndroidEvent2.COLUMN_SCHEDULE_TAG to "some-schedule-tag",
AndroidEvent2.COLUMN_FLAGS to 45
)
)
assertEquals(123L, e.id)
Expand All @@ -90,11 +93,11 @@ class AndroidEventTest {
event.dtStart = DtStart("20150502T120000Z")
event.dtEnd = DtEnd("20150502T130000Z")
event.organizer = Organizer(URI("mailto:organizer@example.com"))
val uri = AndroidEvent(calendar, event, "update-event").add()
val uri = legacyCalendar.add(event)

// update test event in calendar
val testEvent = calendar.getEvent(ContentUris.parseId(uri))!!
val event2 = testEvent.event!!
val testEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri))!!
val event2 = legacyCalendar.getEvent(testEvent.id)!!
event2.summary = "Updated event"
// add data rows
event2.alarms += VAlarm(Duration.parse("-P1DT2H3M4S"))
Expand All @@ -105,9 +108,9 @@ class AndroidEventTest {
assertEquals(ContentUris.parseId(uri), ContentUris.parseId(uri2))

// read again and verify result
val updatedEvent = calendar.getEvent(ContentUris.parseId(uri2))!!
val updatedEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri2))!!
try {
val event3 = updatedEvent.event!!
val event3 = legacyCalendar.getEvent(updatedEvent.id)!!
assertEquals(event2.summary, event3.summary)
assertEquals(1, event3.alarms.size)
assertEquals(1, event3.attendees.size)
Expand All @@ -124,20 +127,20 @@ class AndroidEventTest {
dtStart = DtStart(DateTime())
color = Css3Color.silver
}
val uri = AndroidEvent(calendar, event, "reset-color").add()
val uri = legacyCalendar.add(event)
val id = ContentUris.parseId(uri)

// verify that it has color
val beforeUpdate = calendar.getEvent(id)!!
assertNotNull(beforeUpdate.event?.color)
val beforeUpdate = legacyCalendar.getAndroidEvent(calendar, id)!!
assertNotNull(legacyCalendar.getEvent(beforeUpdate.id)!!.color)

// update: reset color
event.color = null
beforeUpdate.update(event)

// verify that it doesn't have color anymore
val afterUpdate = calendar.getEvent(id)!!
assertNull(afterUpdate.event!!.color)
val afterUpdate = legacyCalendar.getAndroidEvent(calendar, id)!!
assertNull(legacyCalendar.getEvent(afterUpdate.id)!!.color)
}

@Test
Expand All @@ -147,11 +150,11 @@ class AndroidEventTest {
event.summary = "Sample event with STATUS"
event.dtStart = DtStart("20150502T120000Z")
event.dtEnd = DtEnd("20150502T130000Z")
val uri = AndroidEvent(calendar, event, "update-status-from-null").add()
val uri = legacyCalendar.add(event)

// update test event in calendar
val testEvent = calendar.getEvent(ContentUris.parseId(uri))!!
val event2 = testEvent.event!!
val testEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri))!!
val event2 = legacyCalendar.getEvent(testEvent.id)!!
event2.summary = "Sample event without STATUS"
event2.status = Status.VEVENT_CONFIRMED
val uri2 = testEvent.update(event2)
Expand All @@ -160,9 +163,9 @@ class AndroidEventTest {
assertEquals(ContentUris.parseId(uri), ContentUris.parseId(uri2))

// read again and verify result
val updatedEvent = calendar.getEvent(ContentUris.parseId(uri2))!!
val updatedEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri2))!!
try {
val event3 = updatedEvent.event!!
val event3 = legacyCalendar.getEvent(updatedEvent.id)!!
assertEquals(Status.VEVENT_CONFIRMED, event3.status)
} finally {
updatedEvent.delete()
Expand All @@ -177,11 +180,11 @@ class AndroidEventTest {
event.dtStart = DtStart("20150502T120000Z")
event.dtEnd = DtEnd("20150502T130000Z")
event.status = Status.VEVENT_CONFIRMED
val uri = AndroidEvent(calendar, event, "update-status-to-null").add()
val uri = legacyCalendar.add(event)

// update test event in calendar
val testEvent = calendar.getEvent(ContentUris.parseId(uri))!!
val event2 = testEvent.event!!
val testEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri))!!
val event2 = legacyCalendar.getEvent(testEvent.id)!!
event2.summary = "Sample event without STATUS"
event2.status = null
val uri2 = testEvent.update(event2)
Expand All @@ -190,9 +193,9 @@ class AndroidEventTest {
assertNotEquals(ContentUris.parseId(uri), ContentUris.parseId(uri2))

// read again and verify result
val updatedEvent = calendar.getEvent(ContentUris.parseId(uri2))!!
val updatedEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri2))!!
try {
val event3 = updatedEvent.event!!
val event3 = legacyCalendar.getEvent(updatedEvent.id)!!
assertNull(event3.status)
} finally {
updatedEvent.delete()
Expand All @@ -209,11 +212,11 @@ class AndroidEventTest {
event.dtEnd = DtEnd("20150502T130000Z")
for (i in 0 until 20)
event.attendees += Attendee(URI("mailto:att$i@example.com"))
val uri = AndroidEvent(calendar, event, "transaction").add()
val uri = legacyCalendar.add(event)

val testEvent = calendar.getEvent(ContentUris.parseId(uri))!!
val testEvent = legacyCalendar.getAndroidEvent(calendar, ContentUris.parseId(uri))!!
try {
assertEquals(20, testEvent.event!!.attendees.size)
assertEquals(20, legacyCalendar.getEvent(testEvent.id)!!.attendees.size)
} finally {
testEvent.delete()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package at.bitfire.ical4android.impl
import android.accounts.Account
import android.content.ContentProviderClient
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Reminders
import androidx.core.content.contentValuesOf
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider
Expand All @@ -30,7 +29,7 @@ object TestCalendar {
?: provider.createAndGetCalendar(contentValuesOf(
Calendars.NAME to UUID.randomUUID().toString(),
Calendars.CALENDAR_DISPLAY_NAME to "ical4android Test Calendar",
Calendars.ALLOWED_REMINDERS to Reminders.METHOD_DEFAULT)
Calendars.CALENDAR_ACCESS_LEVEL to Calendars.CAL_ACCESS_ROOT)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import android.provider.CalendarContract.Events
import android.provider.CalendarContract.ExtendedProperties
import android.provider.CalendarContract.Reminders
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import at.bitfire.ical4android.AndroidEvent
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.LegacyAndroidCalendar
import at.bitfire.ical4android.UnknownProperty
import at.bitfire.ical4android.impl.TestCalendar
import at.bitfire.ical4android.util.AndroidTimeUtils
Expand Down Expand Up @@ -69,7 +69,7 @@ import java.net.URI
import java.time.Period
import java.util.UUID

class AndroidEventBuilderTest {
class LegacyAndroidEventBuilderTest {

@get:Rule
val initCalendarProviderRule = InitCalendarProviderRule.initialize()
Expand Down Expand Up @@ -134,7 +134,7 @@ class AndroidEventBuilderTest {
eventBuilder()
}
// write event with random file name/sync_id
val uri = AndroidEvent(calendar, event, syncId = UUID.randomUUID().toString()).add()
val uri = LegacyAndroidCalendar(calendar).add(event, syncId = UUID.randomUUID().toString())
client.query(uri, null, null, null, null)!!.use { cursor ->
cursor.moveToNext()
return cursor.toContentValues()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,17 @@ import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentUris
import android.content.ContentValues
import android.net.Uri
import android.provider.CalendarContract.ACCOUNT_TYPE_LOCAL
import android.provider.CalendarContract.AUTHORITY
import android.provider.CalendarContract.Attendees
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Events
import android.provider.CalendarContract.ExtendedProperties
import android.provider.CalendarContract.Reminders
import androidx.core.content.contentValuesOf
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import at.bitfire.ical4android.AndroidEvent
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.LegacyAndroidCalendar
import at.bitfire.ical4android.UnknownProperty
import at.bitfire.ical4android.impl.TestCalendar
import at.bitfire.ical4android.util.AndroidTimeUtils
Expand All @@ -30,6 +29,7 @@ import at.bitfire.ical4android.util.MiscUtils.closeCompat
import at.bitfire.synctools.icalendar.Css3Color
import at.bitfire.synctools.storage.calendar.AndroidCalendar
import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider
import at.bitfire.synctools.storage.calendar.AndroidEvent2
import at.bitfire.synctools.test.InitCalendarProviderRule
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
Expand Down Expand Up @@ -65,7 +65,7 @@ import org.junit.rules.TestRule
import java.net.URI
import java.time.Duration

class AndroidEventProcessorTest {
class LegacyAndroidEventProcessorTest {

@get:Rule
val initCalendarProviderRule: TestRule = InitCalendarProviderRule.initialize()
Expand All @@ -75,8 +75,8 @@ class AndroidEventProcessorTest {
private val tzVienna = tzRegistry.getTimeZone("Europe/Vienna")!!
private val tzShanghai = tzRegistry.getTimeZone("Asia/Shanghai")!!

private lateinit var calendarUri: Uri
private lateinit var calendar: AndroidCalendar
lateinit var legacyCalendar: LegacyAndroidCalendar
lateinit var client: ContentProviderClient

@Before
Expand All @@ -85,8 +85,7 @@ class AndroidEventProcessorTest {
client = context.contentResolver.acquireContentProviderClient(AUTHORITY)!!

calendar = TestCalendar.findOrCreate(testAccount, client)
assertNotNull(calendar)
calendarUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendar.id)
legacyCalendar = LegacyAndroidCalendar(calendar)
}

@After
Expand Down Expand Up @@ -134,7 +133,7 @@ class AndroidEventProcessorTest {
client.insert(ExtendedProperties.CONTENT_URI.asSyncAdapter(testAccount), extendedValues)
}

return destinationCalendar.getEvent(id)!!
return legacyCalendar.getAndroidEvent(destinationCalendar, id)!!
}

private fun populateEvent(
Expand All @@ -145,22 +144,23 @@ class AndroidEventProcessorTest {
extendedProperties: Map<String, String> = emptyMap(),
valuesBuilder: ContentValues.() -> Unit = {}
): Event {
return populateAndroidEvent(
val androidEvent = populateAndroidEvent(
automaticDates,
destinationCalendar,
asSyncAdapter,
insertCallback,
extendedProperties,
valuesBuilder
).event!!
)
return LegacyAndroidCalendar(destinationCalendar).getEvent(androidEvent.id)!!
}

@Test
fun testPopulateEvent_Uid_iCalUid() {
populateEvent(
true,
extendedProperties = mapOf(
AndroidEvent.EXTNAME_ICAL_UID to "event1@example.com"
AndroidEvent2.EXTNAME_ICAL_UID to "event1@example.com"
)
).let { result ->
assertEquals("event1@example.com", result.uid)
Expand All @@ -181,7 +181,7 @@ class AndroidEventProcessorTest {
populateEvent(
true,
extendedProperties = mapOf(
AndroidEvent.EXTNAME_ICAL_UID to "event1@example.com"
AndroidEvent2.EXTNAME_ICAL_UID to "event1@example.com"
)
) {
put(Events.UID_2445, "event2@example.com")
Expand All @@ -194,7 +194,7 @@ class AndroidEventProcessorTest {
@Test
fun testPopulateEvent_Sequence_Int() {
populateEvent(true, asSyncAdapter = true) {
put(AndroidEvent.COLUMN_SEQUENCE, 5)
put(AndroidEvent2.COLUMN_SEQUENCE, 5)
}.let { result ->
assertEquals(5, result.sequence)
}
Expand All @@ -203,7 +203,7 @@ class AndroidEventProcessorTest {
@Test
fun testPopulateEvent_Sequence_Null() {
populateEvent(true, asSyncAdapter = true) {
putNull(AndroidEvent.COLUMN_SEQUENCE)
putNull(AndroidEvent2.COLUMN_SEQUENCE)
}.let { result ->
assertNull(result.sequence)
}
Expand Down Expand Up @@ -408,7 +408,7 @@ class AndroidEventProcessorTest {
@Test
fun testPopulateEvent_Url() {
populateEvent(true,
extendedProperties = mapOf(AndroidEvent.EXTNAME_URL to "https://example.com")
extendedProperties = mapOf(AndroidEvent2.EXTNAME_URL to "https://example.com")
).let { result ->
assertEquals(URI("https://example.com"), result.url)
}
Expand Down
Loading