Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 640fc411 authored by Ricki Hirner's avatar Ricki Hirner
Browse files

Events/tasks: better handling of VALARM

* handle alarms with REL=END even when it's not supported by the storage backend (like in case of events)
* handle alarms with VALUE=DATE-TIME instead of VALUE=DURATION
* round seconds to minutes
parent be6d515d
Loading
Loading
Loading
Loading
+2 −1
Original line number Original line Diff line number Diff line
@@ -664,7 +664,8 @@ abstract class AndroidEvent(
            else               -> Reminders.METHOD_DEFAULT
            else               -> Reminders.METHOD_DEFAULT
        }
        }


        val minutes = ICalendar.alarmMinBefore(alarm)
        val (_, minutes) = ICalendar.vAlarmToMin(alarm, event!!, false) ?: return

        builder .withValue(Reminders.METHOD, method)
        builder .withValue(Reminders.METHOD, method)
                .withValue(Reminders.MINUTES, minutes)
                .withValue(Reminders.MINUTES, minutes)


+6 −4
Original line number Original line Diff line number Diff line
@@ -312,8 +312,10 @@ abstract class AndroidTask(
    }
    }


    protected open fun insertAlarms(batch: BatchOperation) {
    protected open fun insertAlarms(batch: BatchOperation) {
        for (alarm in requireNotNull(task).alarms) {
        val task = requireNotNull(task)
            val alarmRef = when (alarm.trigger.getParameter(Parameter.RELATED)) {
        for (alarm in task.alarms) {
            val (alarmRef, minutes) = ICalendar.vAlarmToMin(alarm, task, true) ?: continue
            val ref = when (alarmRef) {
                Related.END ->
                Related.END ->
                    Alarm.ALARM_REFERENCE_DUE_DATE
                    Alarm.ALARM_REFERENCE_DUE_DATE
                else /* Related.START is the default value */ ->
                else /* Related.START is the default value */ ->
@@ -334,8 +336,8 @@ abstract class AndroidTask(
            val builder = ContentProviderOperation.newInsert(taskList.tasksPropertiesSyncUri())
            val builder = ContentProviderOperation.newInsert(taskList.tasksPropertiesSyncUri())
                    .withValue(Alarm.TASK_ID, id)
                    .withValue(Alarm.TASK_ID, id)
                    .withValue(Alarm.MIMETYPE, Alarm.CONTENT_ITEM_TYPE)
                    .withValue(Alarm.MIMETYPE, Alarm.CONTENT_ITEM_TYPE)
                    .withValue(Alarm.MINUTES_BEFORE, ICalendar.alarmMinBefore(alarm))
                    .withValue(Alarm.MINUTES_BEFORE, minutes)
                    .withValue(Alarm.REFERENCE, alarmRef)
                    .withValue(Alarm.REFERENCE, ref)
                    .withValue(Alarm.MESSAGE, alarm.description?.value ?: alarm.summary)
                    .withValue(Alarm.MESSAGE, alarm.description?.value ?: alarm.summary)
                    .withValue(Alarm.ALARM_TYPE, alarmType)
                    .withValue(Alarm.ALARM_TYPE, alarmType)


+83 −8
Original line number Original line Diff line number Diff line
@@ -12,9 +12,9 @@ import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.component.*
import net.fortuna.ical4j.model.component.*
import net.fortuna.ical4j.model.property.DateProperty
import net.fortuna.ical4j.model.parameter.Related
import net.fortuna.ical4j.model.property.ProdId
import net.fortuna.ical4j.model.property.ProdId
import net.fortuna.ical4j.model.property.TzUrl
import net.fortuna.ical4j.model.property.TzUrl
import net.fortuna.ical4j.validate.ValidationException
import net.fortuna.ical4j.validate.ValidationException
@@ -23,6 +23,9 @@ import java.io.StringReader
import java.util.*
import java.util.*
import java.util.logging.Level
import java.util.logging.Level
import java.util.logging.Logger
import java.util.logging.Logger
import kotlin.math.round
import kotlin.math.roundToInt
import kotlin.math.roundToLong


open class ICalendar {
open class ICalendar {


@@ -195,15 +198,87 @@ open class ICalendar {


        // misc. iCalendar helpers
        // misc. iCalendar helpers


        internal fun alarmMinBefore(alarm: VAlarm): Int {
        /**
            var minutes = 0
         * Calculates the minutes before/after an event/task a certain alarm occurs.
            alarm.trigger?.duration?.let { duration ->
         *
         * @param alarm the alarm to calculate the minutes from
         * @param eventEndRef reference [VEvent] or [VToDo] to take start/end time from (required for calculations)
         * @param allowRelEnd *true*: caller accepts minutes related to the end;
         * *false*: caller only accepts minutes related to the start
         *
         * @return Pair of values:
         *
         * 1. whether the minutes are related to the start or end (always [Related.START] if [allowRelEnd] is *false*)
         * 2. number of minutes before start/end (negative value means number of minutes *after* start/end)
         *
         * May be *null* if the minutes can't be calculated.
         */
        fun vAlarmToMin(alarm: VAlarm, reference: ICalendar, allowRelEnd: Boolean): Pair<Related, Int>? {
            val trigger = alarm.trigger ?: return null

            var minutes = 0       // minutes before/after the event
            var related = trigger.getParameter(Parameter.RELATED) as? Related ?: Related.START

            val alarmDur = trigger.duration
            val alarmTime = trigger.dateTime

            if (alarmDur != null) {
                // TRIGGER value is a DURATION

                // negative value in TRIGGER means positive value in Reminders.MINUTES and vice versa
                // negative value in TRIGGER means positive value in Reminders.MINUTES and vice versa
                minutes = -(((duration.weeks * 7 + duration.days) * 24 + duration.hours) * 60 + duration.minutes + duration.seconds/60)
                minutes = -(((alarmDur.weeks * 7 + alarmDur.days) * 24 + alarmDur.hours) * 60 + alarmDur.minutes + (alarmDur.seconds/60.0).roundToInt())
                if (duration.isNegative)
                // duration.weeks etc. always contain positive values → evaluate duration.isNegative
                if (alarmDur.isNegative)
                    minutes *= -1
                    minutes *= -1

                // DURATION triggers may have RELATED=END (default: RELATED=START), which may not be useful for caller
                if (related == Related.END && !allowRelEnd) {
                    // Related.END is not accepted by caller (for instance because the calendar storage doesn't support it)

                    val start = when (reference) {
                        is Event -> reference.dtStart?.date?.time
                        is Task -> reference.dtStart?.date?.time
                        else -> null
                    }
                    if (start == null) {
                        Constants.log.warning("iCalendar with RELATED=END VALARM doesn't have start time (required for calculation), ignoring")
                        return null
                    }
                    }
            return minutes

                    val end = when (reference) {
                        is Event -> reference.dtEnd?.date?.time
                        is Task -> reference.due?.date?.time
                        else -> null
                    }
                    if (end == null) {
                        Constants.log.warning("iCalendar with RELATED=END VALARM doesn't have end time, ignoring")
                        return null
                    }
                    val durMin = ((end - start)/60000.0).roundToInt()      // ms → min

                    // move alarm towards end
                    related = Related.START
                    minutes -= durMin
                }

            } else if (alarmTime != null) {
                // TRIGGER value is a DATE-TIME, calculate minutes from start time
                val start = if (reference is Event)
                            reference.dtStart?.date?.time
                        else if (reference is Task)
                            reference.dtStart?.date?.time
                        else
                            null
                if (start == null) {
                    Constants.log.warning("iCalendar with DATE-TIME VALARM doesn't have start time (required for calculation), ignoring")
                    return null
                }

                related = Related.START
                minutes = ((start - alarmTime.time)/60000.0).roundToInt()   // ms → min
            }

            return Pair(related, minutes)
        }
        }


    }
    }
+76 −1
Original line number Original line Diff line number Diff line
@@ -8,6 +8,13 @@


package at.bitfire.ical4android
package at.bitfire.ical4android


import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.Dur
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.parameter.Related
import net.fortuna.ical4j.model.property.DtEnd
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.Due
import org.junit.Assert.assertEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.Test
@@ -50,4 +57,72 @@ class ICalendarTest {
				"END:VCALENDAR"))
				"END:VCALENDAR"))
    }
    }


	@Test
	fun testVAlarmToMin() {
		run {
			// TRIGGER;REL=START:-PT1D1H1M29S (round down)
			val (ref, min) = ICalendar.vAlarmToMin(
					VAlarm(Dur(1, 1, 1, 29).negate()),
					ICalendar(), false)!!
			assertEquals(Related.START, ref)
			assertEquals(60*24 + 60 + 1, min)
		}

		run {
			// TRIGGER;REL=START:PT1D1H1M30S (round up; alarm *after* start)
			val (ref, min) = ICalendar.vAlarmToMin(
					VAlarm(Dur(1, 1, 1, 30)),
					ICalendar(), false)!!
			assertEquals(Related.START, ref)
			assertEquals(-(60 * 24 + 60 + 1 + 1), min)
		}

		run {
			// TRIGGER;REL=END:-PT1D1H1M30S (caller accepts Related.END)
			val alarm = VAlarm(Dur(1, 1, 1, 30).negate())
			alarm.trigger.parameters.add(Related.END)
			val (ref, min) = ICalendar.vAlarmToMin(alarm, ICalendar(), true)!!
			assertEquals(Related.END, ref)
			assertEquals(60 * 24 + 60 + 1 + 1, min)
		}

		run {
			// event with TRIGGER;REL=END:-PT1D1H1M30S (caller doesn't accept Related.END)
			val alarm = VAlarm(Dur(1, 1, 1, 30).negate())
			alarm.trigger.parameters.add(Related.END)
			val event = Event()
			val currentTime = java.util.Date().time
			event.dtStart = DtStart(DateTime(currentTime))
			event.dtEnd = DtEnd(DateTime(currentTime + 90*1000))	// 90 sec (should be rounded up to 2 min) later
			val (ref, min) = ICalendar.vAlarmToMin(alarm, event, false)!!
			assertEquals(Related.START, ref)
			assertEquals(60 * 24 + 60 + 1 + 1 /* duration of event: */ - 2, min)
		}

		run {
			// task with TRIGGER;REL=END:-PT1D1H1M30S (caller doesn't accept Related.END; alarm *after* end)
			val alarm = VAlarm(Dur(1, 1, 1, 30))
			alarm.trigger.parameters.add(Related.END)
			val task = Task()
			val currentTime = java.util.Date().time
			task.dtStart = DtStart(DateTime(currentTime))
			task.due = Due(DateTime(currentTime + 90*1000))	// 90 sec (should be rounded up to 2 min) later
			val (ref, min) = ICalendar.vAlarmToMin(alarm, task, false)!!
			assertEquals(Related.START, ref)
			assertEquals(-(60 * 24 + 60 + 1 + 1) /* duration of event: */ - 2, min)
		}

		run {
			// TRIGGER;VALUE=DATE-TIME:<xxxx>
			val event = Event()
			val currentTime = java.util.Date().time
			event.dtStart = DtStart(DateTime(currentTime))
			val alarm = VAlarm(DateTime(currentTime - 89*1000))	// 89 sec (should be rounded down to 1 min) before event
			alarm.trigger.parameters.add(Related.END)	// not useful for DATE-TIME values, should be ignored
			val (ref, min) = ICalendar.vAlarmToMin(alarm, event, false)!!
			assertEquals(Related.START, ref)
			assertEquals(1, min)
		}
	}

}
}
 No newline at end of file