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

Commit 1e5c9e7e authored by Sumit Pundir's avatar Sumit Pundir
Browse files

Merge remote-tracking branch 'bitfireAT/master' into sprint

parents fe80a2bc be6d515d
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line

buildscript {
    ext.versions = [
        kotlin: '1.3.60',
        kotlin: '1.3.61',
        dokka: '0.10.0',
        ical4j: '2.2.6'
    ]
@@ -12,7 +12,7 @@ buildscript {
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
        classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
    }
+0 −8
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@ import foundation.e.ical4android.MiscUtils.TextListHelper.toList
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.TextList
import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.component.VTimeZone
import net.fortuna.ical4j.model.property.DtStart
@@ -114,13 +113,6 @@ class MiscUtilsTest {
        assertEquals("row1_val2", values.getAsString("col2"))
    }

    @Test
    @SmallTest
    fun testTextListToList() {
        assertEquals(listOf("str1", "str2"), TextList(arrayOf("str1", "str2")).toList())
        assertEquals(emptyList<String>(), TextList(arrayOf()).toList())
    }


    @Suppress("unused")
    private class TestClass {
+35 −4
Original line number Diff line number Diff line
@@ -57,6 +57,18 @@ abstract class AndroidEvent(
        @Deprecated("New content item MIME type", ReplaceWith("UnknownProperty.CONTENT_ITEM_TYPE"))
        const val EXT_UNKNOWN_PROPERTY2 = "unknown-property.v2"

        /**
         * VEVENT CATEGORIES will be stored as an extended property with this [ExtendedProperties.NAME].
         *
         * The [ExtendedProperties.VALUE] format is the same as used by the AOSP Exchange ActiveSync adapter:
         * the category values are stored as list, separated by [EXT_CATEGORIES_SEPARATOR]. (If a category
         * value contains [EXT_CATEGORIES_SEPARATOR], [EXT_CATEGORIES_SEPARATOR] will be dropped.)
         *
         * Example: `Cat1\Cat2`
         */
        const val EXT_CATEGORIES = "categories"
        const val EXT_CATEGORIES_SEPARATOR = '\\'

        /**
         * EMAIL parameter name (as used for ORGANIZER). Not declared in ical4j Parameters class yet.
         */
@@ -242,9 +254,7 @@ abstract class AndroidEvent(

        // exceptions from recurring events
        row.getAsLong(Events.ORIGINAL_INSTANCE_TIME)?.let { originalInstanceTime ->
            var originalAllDay = false
            row.getAsInteger(Events.ORIGINAL_ALL_DAY)?.let { originalAllDay = it != 0 }

            val originalAllDay = (row.getAsInteger(Events.ORIGINAL_ALL_DAY) ?: 0) != 0
            val originalDate = if (originalAllDay)
                    Date(originalInstanceTime) else
                    DateTime(originalInstanceTime)
@@ -340,11 +350,17 @@ abstract class AndroidEvent(
    }

    protected open fun populateExtended(row: ContentValues) {
        Constants.log.log(Level.FINE, "Read extended property from calender provider", row.getAsString(ExtendedProperties.NAME))
        val name = row.getAsString(ExtendedProperties.NAME)
        Constants.log.log(Level.FINE, "Read extended property from calender provider (name=$name)")
        val event = requireNotNull(event)

        try {
            when (row.getAsString(ExtendedProperties.NAME)) {
                EXT_CATEGORIES -> {
                    val rawCategories = row.getAsString(ExtendedProperties.VALUE)
                    event.categories += rawCategories.split(EXT_CATEGORIES_SEPARATOR)
                }

                EXT_UNKNOWN_PROPERTY -> {
                    // deserialize unknown property (deprecated format)
                    val stream = ByteArrayInputStream(Base64.decode(row.getAsString(ExtendedProperties.VALUE), Base64.NO_WRAP))
@@ -426,6 +442,8 @@ abstract class AndroidEvent(

        // add unknown properties
        retainClassification()
        if (event.categories.isNotEmpty())
            insertCategories(batch, idxEvent)
        event.unknownProperties.forEach { insertUnknownProperty(batch, idxEvent, it) }

        // add exceptions
@@ -711,6 +729,18 @@ abstract class AndroidEvent(
        batch.enqueue(BatchOperation.Operation(builder, Attendees.EVENT_ID, idxEvent))
    }

    protected open fun insertCategories(batch: BatchOperation, idxEvent: Int) {
        val rawCategories = event!!.categories
                .map { it.filter { it != EXT_CATEGORIES_SEPARATOR } }   // drop backslashes
                .joinToString(EXT_CATEGORIES_SEPARATOR.toString())      // concatenate, separate by backslash
        val builder = ContentProviderOperation.newInsert(calendar.syncAdapterURI(ExtendedProperties.CONTENT_URI))
                .withValue(ExtendedProperties.NAME, EXT_CATEGORIES)
                .withValue(ExtendedProperties.VALUE, rawCategories)

        Constants.log.log(Level.FINE, "Built categories", builder.build())
        batch.enqueue(BatchOperation.Operation(builder, ExtendedProperties.EVENT_ID, idxEvent))
    }

    protected open fun insertUnknownProperty(batch: BatchOperation, idxEvent: Int, property: Property) {
        if (property.value.length > UnknownProperty.MAX_UNKNOWN_PROPERTY_SIZE) {
            Constants.log.warning("Ignoring unknown property with ${property.value.length} octets (too long)")
@@ -721,6 +751,7 @@ abstract class AndroidEvent(
                .withValue(ExtendedProperties.NAME, UnknownProperty.CONTENT_ITEM_TYPE)
                .withValue(ExtendedProperties.VALUE, UnknownProperty.toJsonString(property))

        Constants.log.log(Level.FINE, "Built unknown property: ${property.name}")
        batch.enqueue(BatchOperation.Operation(builder, ExtendedProperties.EVENT_ID, idxEvent))
    }

+9 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.component.VTimeZone
import net.fortuna.ical4j.model.parameter.Value
import net.fortuna.ical4j.model.property.DateListProperty
import net.fortuna.ical4j.model.property.DateProperty
import net.fortuna.ical4j.model.property.ExDate
import net.fortuna.ical4j.model.property.RDate
import java.io.StringReader
@@ -74,6 +75,14 @@ object DateUtils {
        return deviceTZ
    }

    /**
     * Determines whether a given date represents a DATE-TIME value.
     * @param date date property to check
     * @return *true* if the date is a DATE-TIME value; *false* otherwise (for instance, when the
     * date is a DATE value or null)
     */
    fun isDateTime(date: DateProperty?) = date != null && date.date is DateTime

    /**
     * Parses a VTIMEZONE definition to a VTimeZone object.
     * @param timezoneDef VTIMEZONE definition
+43 −17
Original line number Diff line number Diff line
@@ -8,11 +8,13 @@

package foundation.e.ical4android

import foundation.e.ical4android.DateUtils.isDateTime
import foundation.e.ical4android.ICalendar.Companion.CALENDAR_NAME
import net.fortuna.ical4j.data.CalendarOutputter
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.*
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.TimeZone
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.component.VEvent
@@ -55,6 +57,7 @@ class Event: ICalendar() {

    var lastModified: LastModified? = null

    val categories = LinkedList<String>()
    val unknownProperties = LinkedList<Property>()

    companion object {
@@ -150,6 +153,9 @@ class Event: ICalendar() {
                    is Summary -> e.summary = prop.value
                    is Location -> e.location = prop.value
                    is Description -> e.description = prop.value
                    is Categories ->
                        for (category in prop.categories)
                            e.categories += category
                    is Color -> e.color = Css3Color.fromString(prop.value)
                    is DtStart -> e.dtStart = prop
                    is DtEnd -> e.dtEnd = prop
@@ -202,17 +208,31 @@ class Event: ICalendar() {

        // recurrence exceptions
        for (exception in exceptions) {
            // make sure that
            //     - exceptions have the same UID as the main event and
            //     - RECURRENCE-IDs have the same timezone as the main event's DTSTART
            // exceptions must always have the same UID as the main event
            exception.uid = uid
            exception.recurrenceId?.let { recurrenceId ->
                if (recurrenceId.timeZone != dtStart.timeZone) {

            val recurrenceId = exception.recurrenceId
            if (recurrenceId == null) {
                Constants.log.warning("Ignoring exception without recurrenceId")
                continue
            }

            /* Exceptions must always have the same value type as DTSTART [RFC 5545 3.8.4.4].
               If this is not the case, we don't add the exception to the event because we're
               strict in what we send (and servers may reject such a case).
             */
            if (isDateTime(recurrenceId) != isDateTime(dtStart)) {
                Constants.log.warning("Ignoring exception $recurrenceId with other date type than dtStart: $dtStart")
                continue
            }

            // for simplicity and compatibility, rewrite date-time exceptions to the same time zone as DTSTART
            if (isDateTime(recurrenceId) && recurrenceId.timeZone != dtStart.timeZone) {
                Constants.log.fine("Changing timezone of $recurrenceId to same time zone as dtStart: $dtStart")
                recurrenceId.timeZone = dtStart.timeZone
                    exception.recurrenceId = recurrenceId
            }

                // create VEVENT for exception
            // create and add VEVENT for exception
            val vException = exception.toVEvent()
            components += vException

@@ -220,7 +240,6 @@ class Event: ICalendar() {
            exception.dtStart?.timeZone?.let(usedTimeZones::add)
            exception.dtEnd?.timeZone?.let(usedTimeZones::add)
        }
        }

        // add VTIMEZONE components
        usedTimeZones.forEach {
@@ -239,7 +258,7 @@ class Event: ICalendar() {
     * @return generated VEvent
     */
    private fun toVEvent(): VEvent {
        val event = VEvent(true /* generates DTSTAMP */)
        val event = VEvent(/* generates DTSTAMP */)
        val props = event.properties
        props += Uid(uid)

@@ -254,7 +273,7 @@ class Event: ICalendar() {
        description?.let { props += Description(it) }
        color?.let { props += Color(null, it.name) }

        props += dtStart
        dtStart?.let { props += it }
        dtEnd?.let { props += it }
        duration?.let { props += it }

@@ -271,6 +290,8 @@ class Event: ICalendar() {
        organizer?.let { props += it }
        props.addAll(attendees)

        if (categories.isNotEmpty())
            props += Categories(TextList(categories.toTypedArray()))
        props.addAll(unknownProperties)

        lastModified?.let { props += it }
@@ -283,6 +304,11 @@ class Event: ICalendar() {

    // helpers

    fun isAllDay() = !isDateTime(dtStart)
    /**
     * Determines whether this Event is an all-day event.
     *
     * @return *true* if [dtStart] is a DATE value; *false* otherwise ([dtStart] is a DATETIME value or *null*)
     */
    fun isAllDay() = dtStart != null && !isDateTime(dtStart)

}
Loading