Skip to content

Commit 5e96a08

Browse files
committed
- Move features from LocalEvent to AndroidEvent
- Remove AndroidEventFactory because it's not meant to be subclassed anymore
1 parent 25404dc commit 5e96a08

File tree

8 files changed

+147
-68
lines changed

8 files changed

+147
-68
lines changed

lib/src/androidTest/kotlin/at/bitfire/ical4android/AndroidCalendarTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import android.provider.CalendarContract.Colors
1717
import androidx.test.platform.app.InstrumentationRegistry
1818
import androidx.test.rule.GrantPermissionRule
1919
import at.bitfire.ical4android.impl.TestCalendar
20-
import at.bitfire.ical4android.impl.TestEvent
2120
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
2221
import at.bitfire.ical4android.util.MiscUtils.closeCompat
2322
import net.fortuna.ical4j.model.property.DtEnd
@@ -109,12 +108,12 @@ class AndroidCalendarTest {
109108
val cal = TestCalendar.findOrCreate(testAccount, provider)
110109
try {
111110
// add event with color
112-
TestEvent(cal, Event().apply {
111+
AndroidEvent(cal, Event().apply {
113112
dtStart = DtStart("20210314T204200Z")
114113
dtEnd = DtEnd("20210314T204230Z")
115114
color = Css3Color.limegreen
116115
summary = "Test event with color"
117-
}).add()
116+
}, "remove-colors").add()
118117

119118
AndroidCalendar.removeColors(provider, testAccount)
120119
assertEquals(0, countColors(testAccount))

lib/src/androidTest/kotlin/at/bitfire/ical4android/AndroidEventTest.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import androidx.core.content.contentValuesOf
2323
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
2424
import androidx.test.rule.GrantPermissionRule
2525
import at.bitfire.ical4android.impl.TestCalendar
26-
import at.bitfire.ical4android.impl.TestEvent
2726
import at.bitfire.ical4android.util.AndroidTimeUtils
2827
import at.bitfire.ical4android.util.DateUtils
2928
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
@@ -62,6 +61,7 @@ import org.junit.Test
6261
import java.net.URI
6362
import java.time.Duration
6463
import java.time.Period
64+
import java.util.UUID
6565
import java.util.logging.Logger
6666
import kotlin.collections.plusAssign
6767

@@ -171,7 +171,8 @@ class AndroidEventTest {
171171
dtStart = DtStart(DateTime())
172172
eventBuilder()
173173
}
174-
val uri = TestEvent(calendar, event).add()
174+
// write event with random file name/sync_id
175+
val uri = AndroidEvent(calendar, event, syncId = UUID.randomUUID().toString()).add()
175176
provider.query(uri, null, null, null, null)!!.use { cursor ->
176177
cursor.moveToNext()
177178
val values = ContentValues(cursor.columnCount)
@@ -2408,7 +2409,7 @@ class AndroidEventTest {
24082409
event.dtStart = DtStart("20150502T120000Z")
24092410
event.dtEnd = DtEnd("20150502T130000Z")
24102411
event.organizer = Organizer(URI("mailto:organizer@example.com"))
2411-
val uri = TestEvent(calendar, event).add()
2412+
val uri = AndroidEvent(calendar, event, "update-event").add()
24122413

24132414
// update test event in calendar
24142415
val testEvent = calendar.findById(ContentUris.parseId(uri))
@@ -2442,7 +2443,7 @@ class AndroidEventTest {
24422443
dtStart = DtStart(DateTime())
24432444
color = Css3Color.silver
24442445
}
2445-
val uri = TestEvent(calendar, event).add()
2446+
val uri = AndroidEvent(calendar, event, "reset-color").add()
24462447
val id = ContentUris.parseId(uri)
24472448

24482449
// verify that it has color
@@ -2465,7 +2466,7 @@ class AndroidEventTest {
24652466
event.summary = "Sample event with STATUS"
24662467
event.dtStart = DtStart("20150502T120000Z")
24672468
event.dtEnd = DtEnd("20150502T130000Z")
2468-
val uri = TestEvent(calendar, event).add()
2469+
val uri = AndroidEvent(calendar, event, "update-status-from-null").add()
24692470

24702471
// update test event in calendar
24712472
val testEvent = calendar.findById(ContentUris.parseId(uri))
@@ -2495,7 +2496,7 @@ class AndroidEventTest {
24952496
event.dtStart = DtStart("20150502T120000Z")
24962497
event.dtEnd = DtEnd("20150502T130000Z")
24972498
event.status = Status.VEVENT_CONFIRMED
2498-
val uri = TestEvent(calendar, event).add()
2499+
val uri = AndroidEvent(calendar, event, "update-status-to-null").add()
24992500

25002501
// update test event in calendar
25012502
val testEvent = calendar.findById(ContentUris.parseId(uri))
@@ -2528,7 +2529,7 @@ class AndroidEventTest {
25282529
event.dtEnd = DtEnd("20150502T130000Z")
25292530
for (i in 0 until 20)
25302531
event.attendees += Attendee(URI("mailto:att$i@example.com"))
2531-
val uri = TestEvent(calendar, event).add()
2532+
val uri = AndroidEvent(calendar, event, "transaction").add()
25322533

25332534
val testEvent = calendar.findById(ContentUris.parseId(uri))
25342535
try {

lib/src/androidTest/kotlin/at/bitfire/ical4android/impl/TestCalendar.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class TestCalendar(
1818
account: Account,
1919
providerClient: ContentProviderClient,
2020
id: Long
21-
): AndroidCalendar<TestEvent>(account, providerClient, TestEvent.Factory, id) {
21+
) : AndroidCalendar(account, providerClient, id) {
2222

2323
companion object {
2424
fun findOrCreate(account: Account, provider: ContentProviderClient): TestCalendar {

lib/src/androidTest/kotlin/at/bitfire/ical4android/impl/TestEvent.kt

Lines changed: 0 additions & 30 deletions
This file was deleted.

lib/src/main/kotlin/at/bitfire/ical4android/AndroidCalendar.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@ import java.util.logging.Logger
3131
* Communicates with the Android Contacts Provider which uses an SQLite
3232
* database to store the events.
3333
*/
34-
open class AndroidCalendar<out T : AndroidEvent>(
34+
open class AndroidCalendar(
3535
val account: Account,
3636
val provider: ContentProviderClient,
37-
val eventFactory: AndroidEventFactory<T>,
3837

3938
/** the calendar ID ([Calendars._ID]) **/
4039
val id: Long
@@ -100,14 +99,14 @@ open class AndroidCalendar<out T : AndroidEvent>(
10099
* @param _whereArgs arguments for selection
101100
* @return events from this calendar which match the selection
102101
*/
103-
fun queryEvents(_where: String? = null, _whereArgs: Array<String>? = null): List<T> {
102+
fun queryEvents(_where: String? = null, _whereArgs: Array<String>? = null): List<AndroidEvent> {
104103
val where = "(${_where ?: "1"}) AND " + Events.CALENDAR_ID + "=?"
105104
val whereArgs = (_whereArgs ?: arrayOf()) + id.toString()
106105

107-
val events = LinkedList<T>()
106+
val events = LinkedList<AndroidEvent>()
108107
provider.query(Events.CONTENT_URI.asSyncAdapter(account), null, where, whereArgs, null)?.use { cursor ->
109108
while (cursor.moveToNext())
110-
events += eventFactory.fromProvider(this, cursor.toValues())
109+
events += AndroidEvent(this, cursor.toValues())
111110
}
112111
return events
113112
}
@@ -216,7 +215,12 @@ open class AndroidCalendar<out T : AndroidEvent>(
216215
provider.delete(Colors.CONTENT_URI.asSyncAdapter(account), null, null)
217216
}
218217

219-
fun<T: AndroidCalendar<AndroidEvent>> findByID(account: Account, provider: ContentProviderClient, factory: AndroidCalendarFactory<T>, id: Long): T {
218+
fun <T : AndroidCalendar> findByID(
219+
account: Account,
220+
provider: ContentProviderClient,
221+
factory: AndroidCalendarFactory<T>,
222+
id: Long
223+
): T {
220224
val iterCalendars = CalendarEntity.newEntityIterator(
221225
provider.query(ContentUris.withAppendedId(CalendarEntity.CONTENT_URI, id).asSyncAdapter(account), null, null, null, null)
222226
)
@@ -233,7 +237,13 @@ open class AndroidCalendar<out T : AndroidEvent>(
233237
throw FileNotFoundException()
234238
}
235239

236-
fun<T: AndroidCalendar<AndroidEvent>> find(account: Account, provider: ContentProviderClient, factory: AndroidCalendarFactory<T>, where: String?, whereArgs: Array<String>?): List<T> {
240+
fun <T : AndroidCalendar> find(
241+
account: Account,
242+
provider: ContentProviderClient,
243+
factory: AndroidCalendarFactory<T>,
244+
where: String?,
245+
whereArgs: Array<String>?
246+
): List<T> {
237247
val iterCalendars = CalendarEntity.newEntityIterator(
238248
provider.query(CalendarEntity.CONTENT_URI.asSyncAdapter(account), null, where, whereArgs, null)
239249
)

lib/src/main/kotlin/at/bitfire/ical4android/AndroidCalendarFactory.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package at.bitfire.ical4android
99
import android.accounts.Account
1010
import android.content.ContentProviderClient
1111

12-
interface AndroidCalendarFactory<out T: AndroidCalendar<AndroidEvent>> {
12+
interface AndroidCalendarFactory<out T : AndroidCalendar> {
1313

1414
fun newInstance(account: Account, provider: ContentProviderClient, id: Long): T
1515

lib/src/main/kotlin/at/bitfire/ical4android/AndroidEvent.kt

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66

77
package at.bitfire.ical4android
88

9+
import android.accounts.Account
10+
import android.content.ContentProviderClient
911
import android.content.ContentResolver
1012
import android.content.ContentUris
1113
import android.content.ContentValues
1214
import android.content.EntityIterator
1315
import android.net.Uri
1416
import android.os.RemoteException
17+
import android.provider.CalendarContract
1518
import android.provider.CalendarContract.Attendees
1619
import android.provider.CalendarContract.Colors
1720
import android.provider.CalendarContract.Events
@@ -20,7 +23,9 @@ import android.provider.CalendarContract.ExtendedProperties
2023
import android.provider.CalendarContract.Reminders
2124
import android.util.Patterns
2225
import androidx.annotation.CallSuper
26+
import androidx.core.content.contentValuesOf
2327
import at.bitfire.ical4android.AndroidEvent.Companion.CATEGORIES_SEPARATOR
28+
import at.bitfire.ical4android.AndroidEvent.Companion.numInstances
2429
import at.bitfire.ical4android.util.AndroidTimeUtils
2530
import at.bitfire.ical4android.util.DateUtils
2631
import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter
@@ -85,7 +90,7 @@ import java.util.logging.Logger
8590
* in populateEvent() / buildEvent. Setting _ID and ORIGINAL_ID is not sufficient.
8691
*/
8792
open class AndroidEvent(
88-
val calendar: AndroidCalendar<AndroidEvent>
93+
val calendar: AndroidCalendar
8994
) {
9095

9196
protected val logger: Logger by lazy { Logger.getLogger(AndroidEvent::class.java.name) }
@@ -104,7 +109,7 @@ open class AndroidEvent(
104109
*
105110
* @param values database row with all columns, as returned by the calendar provider
106111
*/
107-
constructor(calendar: AndroidCalendar<*>, values: ContentValues) : this(calendar) {
112+
constructor(calendar: AndroidCalendar, values: ContentValues) : this(calendar) {
108113
this.id = values.getAsLong(Events._ID)
109114
this.syncId = values.getAsString(Events._SYNC_ID)
110115
this.eTag = values.getAsString(COLUMN_ETAG)
@@ -114,9 +119,17 @@ open class AndroidEvent(
114119

115120
/**
116121
* Creates a new object from an event which doesn't exist in the calendar storage yet.
122+
*
117123
* @param event event that can be saved into the calendar storage
118124
*/
119-
constructor(calendar: AndroidCalendar<*>, event: Event, syncId: String?, eTag: String?, scheduleTag: String?, flags: Int) : this(calendar) {
125+
constructor(
126+
calendar: AndroidCalendar,
127+
event: Event,
128+
syncId: String?,
129+
eTag: String? = null,
130+
scheduleTag: String? = null,
131+
flags: Int = 0
132+
) : this(calendar) {
120133
this.event = event
121134
this.syncId = syncId
122135
this.eTag = eTag
@@ -501,7 +514,7 @@ open class AndroidEvent(
501514
while (c.moveToNext()) {
502515
val values = c.toValues(true)
503516
try {
504-
val exception = calendar.eventFactory.fromProvider(calendar, values)
517+
val exception = AndroidEvent(calendar, values)
505518
val exceptionEvent = exception.event!!
506519
val recurrenceId = exceptionEvent.recurrenceId!!
507520

@@ -1180,6 +1193,107 @@ open class AndroidEvent(
11801193
*/
11811194
const val EXTNAME_URL = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.ical4android.url"
11821195

1196+
1197+
// helpers
1198+
1199+
/**
1200+
* Marks the event as deleted
1201+
* @param eventID
1202+
*/
1203+
fun markAsDeleted(provider: ContentProviderClient, account: Account, eventID: Long) {
1204+
provider.update(
1205+
ContentUris.withAppendedId(
1206+
Events.CONTENT_URI,
1207+
eventID
1208+
).asSyncAdapter(account),
1209+
contentValuesOf(Events.DELETED to 1),
1210+
null, null
1211+
)
1212+
}
1213+
1214+
/**
1215+
* Finds the amount of direct instances this event has (without exceptions); used by [numInstances]
1216+
* to find the number of instances of exceptions.
1217+
*
1218+
* The number of returned instances may vary with the Android version.
1219+
*
1220+
* @return number of direct event instances (not counting instances of exceptions); *null* if
1221+
* the number can't be determined or if the event has no last date (recurring event without last instance)
1222+
*/
1223+
fun numDirectInstances(provider: ContentProviderClient, account: Account, eventID: Long): Int? {
1224+
// query event to get first and last instance
1225+
var first: Long? = null
1226+
var last: Long? = null
1227+
provider.query(
1228+
ContentUris.withAppendedId(
1229+
Events.CONTENT_URI,
1230+
eventID
1231+
),
1232+
arrayOf(Events.DTSTART, Events.LAST_DATE), null, null, null
1233+
)?.use { cursor ->
1234+
cursor.moveToNext()
1235+
if (!cursor.isNull(0))
1236+
first = cursor.getLong(0)
1237+
if (!cursor.isNull(1))
1238+
last = cursor.getLong(1)
1239+
}
1240+
// if this event doesn't have a last occurence, it's endless and always has instances
1241+
if (first == null || last == null)
1242+
return null
1243+
1244+
/* We can't use Long.MIN_VALUE and Long.MAX_VALUE because Android generates the instances
1245+
on the fly and it doesn't accept those values. So we use the first/last actual occurence
1246+
of the event (calculated by Android). */
1247+
val instancesUri = CalendarContract.Instances.CONTENT_URI.asSyncAdapter(account)
1248+
.buildUpon()
1249+
.appendPath(first.toString()) // begin timestamp
1250+
.appendPath(last.toString()) // end timestamp
1251+
.build()
1252+
1253+
var numInstances = 0
1254+
provider.query(
1255+
instancesUri, null,
1256+
"${CalendarContract.Instances.EVENT_ID}=?", arrayOf(eventID.toString()),
1257+
null
1258+
)?.use { cursor ->
1259+
numInstances += cursor.count
1260+
}
1261+
return numInstances
1262+
}
1263+
1264+
/**
1265+
* Finds the total number of instances this event has (including instances of exceptions)
1266+
*
1267+
* The number of returned instances may vary with the Android version.
1268+
*
1269+
* @return number of direct event instances (not counting instances of exceptions); *null* if
1270+
* the number can't be determined or if the event has no last date (recurring event without last instance)
1271+
*/
1272+
fun numInstances(provider: ContentProviderClient, account: Account, eventID: Long): Int? {
1273+
// num instances of the main event
1274+
var numInstances = numDirectInstances(provider, account, eventID) ?: return null
1275+
1276+
// add the number of instances of every main event's exception
1277+
provider.query(
1278+
Events.CONTENT_URI,
1279+
arrayOf(Events._ID),
1280+
"${Events.ORIGINAL_ID}=?", // get exception events of the main event
1281+
arrayOf("$eventID"), null
1282+
)?.use { exceptionsEventCursor ->
1283+
while (exceptionsEventCursor.moveToNext()) {
1284+
val exceptionEventID = exceptionsEventCursor.getLong(0)
1285+
val exceptionInstances = numDirectInstances(provider, account, exceptionEventID)
1286+
1287+
if (exceptionInstances == null)
1288+
// number of instances of exception can't be determined; so the total number of instances is also unclear
1289+
return null
1290+
1291+
numInstances += exceptionInstances
1292+
}
1293+
}
1294+
return numInstances
1295+
}
1296+
11831297
}
11841298

11851299
}

lib/src/main/kotlin/at/bitfire/ical4android/AndroidEventFactory.kt

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)