66
77package at.bitfire.ical4android
88
9+ import android.accounts.Account
10+ import android.content.ContentProviderClient
911import android.content.ContentResolver
1012import android.content.ContentUris
1113import android.content.ContentValues
1214import android.content.EntityIterator
1315import android.net.Uri
1416import android.os.RemoteException
17+ import android.provider.CalendarContract
1518import android.provider.CalendarContract.Attendees
1619import android.provider.CalendarContract.Colors
1720import android.provider.CalendarContract.Events
@@ -20,7 +23,9 @@ import android.provider.CalendarContract.ExtendedProperties
2023import android.provider.CalendarContract.Reminders
2124import android.util.Patterns
2225import androidx.annotation.CallSuper
26+ import androidx.core.content.contentValuesOf
2327import at.bitfire.ical4android.AndroidEvent.Companion.CATEGORIES_SEPARATOR
28+ import at.bitfire.ical4android.AndroidEvent.Companion.numInstances
2429import at.bitfire.ical4android.util.AndroidTimeUtils
2530import at.bitfire.ical4android.util.DateUtils
2631import 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 */
8792open 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}
0 commit comments