@@ -295,7 +295,7 @@ class AndroidCalendar(
295295 * Updates a specific event's main row with the given values. Doesn't influence data rows.
296296 *
297297 * This method always uses the update method of the content provider and does not
298- * re-create rows, as it is required for some operations (see [updateEvent] and [eventUpdateNeedsRebuild ]
298+ * re-create rows, as it is required for some operations (see [updateEvent] and [getStatusUpdateWorkaround ]
299299 * for more information).
300300 *
301301 * @param id event ID
@@ -311,40 +311,81 @@ class AndroidCalendar(
311311 }
312312 }
313313
314+ /* *
315+ * Updates an event and applies the eventStatus=null workaround, if necessary.
316+ *
317+ * While the event row can be updated, sub-values (data rows) are always deleted and created from scratch.
318+ *
319+ * @param id ID of the event to update
320+ * @param entity new values of the event
321+ *
322+ * @return ID of the updated event (not necessarily the same as the original event)
323+ */
314324 fun updateEvent (id : Long , entity : Entity ): Long {
315325 try {
316- val rebuild = eventUpdateNeedsRebuild(id, entity.entityValues) ? : true
317- if (rebuild) {
318- deleteEvent(id)
319- return addEvent(entity)
320- }
321-
322- // remove existing data rows which are created by us (don't touch 3rd-party calendar apps rows)
323326 val batch = CalendarBatchOperation (client)
324- updateEvent(id, entity, batch)
327+ val newEventIdIdx = updateEvent(id, entity, batch)
325328 batch.commit()
326329
327- return id
330+ if (newEventIdIdx == null )
331+ // event was updated
332+ return id
333+ else {
334+ // event was re-built
335+ val result = batch.getResult(newEventIdIdx)
336+ val newEventUri = result?.uri ? : throw LocalStorageException (" Content provider returned null on insert" )
337+ return ContentUris .parseId(newEventUri)
338+ }
328339 } catch (e: RemoteException ) {
329340 throw LocalStorageException (" Couldn't update event $id " , e)
330341 }
331342 }
332343
333- internal fun updateEvent (id : Long , entity : Entity , batch : CalendarBatchOperation ) {
344+ /* *
345+ * Enqueues an update of an event and applies the eventStatus=null workaround, if necessary.
346+ *
347+ * While the event row can be updated, sub-values (data rows) are always deleted and created from scratch.
348+ *
349+ * @param id ID of the event to update
350+ * @param batch batch operation in which the update is enqueued
351+ * @param entity new values of the event
352+ *
353+ * @return `null` if an event update was enqueued so that its ID won't change;
354+ * otherwise (if re-build is needed) the result index of the new event ID.
355+ */
356+ internal fun updateEvent (id : Long , entity : Entity , batch : CalendarBatchOperation ): Int? {
357+ val workaround = getStatusUpdateWorkaround(id, entity.entityValues)
358+ if (workaround == StatusUpdateWorkaround .REBUILD_EVENT ) {
359+ deleteEvent(id, batch)
360+
361+ val idx = batch.nextBackrefIdx()
362+ addEvent(entity, batch)
363+ return idx
364+ }
365+
366+ // remove existing data rows which are created by us (don't touch 3rd-party calendar apps rows)
334367 deleteDataRows(id, batch)
335368
336369 // update main row
370+ val newValues = ContentValues (entity.entityValues).apply {
371+ // don't update event ID
372+ remove(Events ._ID )
373+
374+ // don't update status if that is our required workaround
375+ if (workaround == StatusUpdateWorkaround .DONT_UPDATE_STATUS )
376+ remove(Events .STATUS )
377+ }
337378 batch + = CpoBuilder .newUpdate(eventUri(id))
338- .withValues(ContentValues (entity.entityValues).apply {
339- remove(Events ._ID ) // don't update ID
340- })
379+ .withValues(newValues)
341380
342381 // insert data rows (with reference to main row ID)
343382 for (row in entity.subValues)
344383 batch + = CpoBuilder .newInsert(row.uri.asSyncAdapter(account))
345384 .withValues(ContentValues (row.values).apply {
346385 put(AndroidEvent2 .DATA_ROW_EVENT_ID , id) // always keep reference to main row ID
347386 })
387+
388+ return null
348389 }
349390
350391 /* *
@@ -375,7 +416,7 @@ class AndroidCalendar(
375416
376417 /* *
377418 * There is a bug in the calendar provider that prevent events from being updated from a non-null STATUS value
378- * to STATUS=null (see AndroidCalendarProviderBehaviorTest.testUpdateEventStatusToNull ).
419+ * to STATUS=null (see ` AndroidCalendarProviderBehaviorTest` test class ).
379420 *
380421 * In that case we can't update the event, so we completely re-create it.
381422 *
@@ -384,9 +425,20 @@ class AndroidCalendar(
384425 *
385426 * @return whether the event can't be updated/needs to be re-created; or `null` if existing values couldn't be determined
386427 */
387- internal fun eventUpdateNeedsRebuild (id : Long , newValues : ContentValues ): Boolean? {
388- val existingValues = getEventRow(id, arrayOf(Events .STATUS )) ? : return null
389- return existingValues.getAsInteger(Events .STATUS ) != null && newValues.getAsInteger(Events .STATUS ) == null
428+ internal fun getStatusUpdateWorkaround (id : Long , newValues : ContentValues ): StatusUpdateWorkaround {
429+ // No workaround needed if STATUS is a) not updated at all, or b) updated to a non-null value.
430+ if (! newValues.containsKey(Events .STATUS ) || newValues.getAsInteger(Events .STATUS ) != null )
431+ return StatusUpdateWorkaround .NO_WORKAROUND
432+ // We're now sure that STATUS shall be updated to null.
433+
434+ // If STATUS is null before the update, just don't include the STATUS in the update.
435+ // In case that the old values can't be determined, rebuild the row to be on the safe side.
436+ val existingValues = getEventRow(id, arrayOf(Events .STATUS )) ? : return StatusUpdateWorkaround .REBUILD_EVENT
437+ if (existingValues.getAsInteger(Events .STATUS ) == null )
438+ return StatusUpdateWorkaround .DONT_UPDATE_STATUS
439+
440+ // Update from non-null to null → rebuild (delete/insert) event instead of updating it.
441+ return StatusUpdateWorkaround .REBUILD_EVENT
390442 }
391443
392444 /* *
@@ -422,6 +474,10 @@ class AndroidCalendar(
422474 }
423475 }
424476
477+ internal fun deleteEvent (id : Long , batch : CalendarBatchOperation ) {
478+ batch + = CpoBuilder .newDelete(eventUri(id))
479+ }
480+
425481
426482
427483 // event instances (these methods operate directly with event IDs and without the events
@@ -516,6 +572,15 @@ class AndroidCalendar(
516572
517573 // helpers
518574
575+ enum class StatusUpdateWorkaround {
576+ /* * no workaround needed */
577+ NO_WORKAROUND ,
578+ /* * don't update eventStatus (no need to change value) */
579+ DONT_UPDATE_STATUS ,
580+ /* * rebuild event (delete+insert instead of update) */
581+ REBUILD_EVENT
582+ }
583+
519584 val account
520585 get() = provider.account
521586
0 commit comments