diff --git a/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt b/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt index 34e2adb2..b29f04c7 100644 --- a/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt +++ b/src/androidTest/java/at/bitfire/ical4android/JtxICalObjectTest.kt @@ -6,7 +6,9 @@ package at.bitfire.ical4android import android.accounts.Account import android.content.ContentProviderClient +import android.content.ContentResolver import android.content.ContentValues +import android.content.Context import android.database.DatabaseUtils import android.os.ParcelFileDescriptor import androidx.test.platform.app.InstrumentationRegistry @@ -38,14 +40,14 @@ class JtxICalObjectTest { companion object { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val contentResolver = context.contentResolver + private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + private val contentResolver: ContentResolver = context.contentResolver private lateinit var client: ContentProviderClient @JvmField @ClassRule - val permissionRule = GrantPermissionRule.grant(*TaskProvider.PERMISSIONS_JTX) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(*TaskProvider.PERMISSIONS_JTX) @BeforeClass @JvmStatic @@ -65,8 +67,8 @@ class JtxICalObjectTest { } private val testAccount = Account("TEST", JtxContract.JtxCollection.TEST_ACCOUNT_TYPE) - var collection: JtxCollection? = null - var sample: at.bitfire.ical4android.JtxICalObject? = null + private var collection: JtxCollection? = null + private var sample: at.bitfire.ical4android.JtxICalObject? = null private val url = "https://jtx.techbee.at" private val displayname = "jtxTest" @@ -95,6 +97,7 @@ class JtxICalObjectTest { this.dtend = System.currentTimeMillis() this.dtendTimezone = "Europe/Paris" this.status = JtxICalObject.StatusJournal.FINAL.name + this.xstatus = "my status" this.classification = JtxICalObject.Classification.PUBLIC.name this.url = "https://jtx.techbee.at" this.contact = "jtx@techbee.at" @@ -102,6 +105,7 @@ class JtxICalObjectTest { this.geoLong = 16.3738 this.location = "Vienna" this.locationAltrep = "Wien" + this.geofenceRadius = 10 this.percent = 99 this.priority = 1 this.due = System.currentTimeMillis() @@ -144,6 +148,12 @@ class JtxICalObjectTest { @Test fun check_DTEND() = insertRetrieveAssertLong(JtxICalObject.DTEND, sample?.dtend, Component.VJOURNAL.name) @Test fun check_DTEND_TIMEZONE() = insertRetrieveAssertString(JtxICalObject.DTEND_TIMEZONE, sample?.dtendTimezone, Component.VJOURNAL.name) @Test fun check_STATUS() = insertRetrieveAssertString(JtxICalObject.STATUS, sample?.status, Component.VJOURNAL.name) + @Test fun check_XSTATUS() { + val jtxVersionCode = context.packageManager.getPackageInfo("at.techbee.jtx", 0).longVersionCode + Assume.assumeTrue(jtxVersionCode > 204020003) + insertRetrieveAssertString(JtxICalObject.EXTENDED_STATUS, sample?.xstatus, Component.VJOURNAL.name) + } + @Test fun check_CLASSIFICATION() = insertRetrieveAssertString(JtxICalObject.CLASSIFICATION, sample?.classification, Component.VJOURNAL.name) @Test fun check_URL() = insertRetrieveAssertString(JtxICalObject.URL, sample?.url, Component.VJOURNAL.name) @Test fun check_CONTACT() = insertRetrieveAssertString(JtxICalObject.CONTACT, sample?.contact, Component.VJOURNAL.name) @@ -151,6 +161,12 @@ class JtxICalObjectTest { @Test fun check_GEO_LONG() = insertRetrieveAssertDouble(JtxICalObject.GEO_LONG, sample?.geoLong, Component.VJOURNAL.name) @Test fun check_LOCATION() = insertRetrieveAssertString(JtxICalObject.LOCATION, sample?.location, Component.VJOURNAL.name) @Test fun check_LOCATION_ALTREP() = insertRetrieveAssertString(JtxICalObject.LOCATION_ALTREP, sample?.locationAltrep, Component.VJOURNAL.name) + @Test fun check_GEOFENCE_RADIUS() { + val jtxVersionCode = context.packageManager.getPackageInfo("at.techbee.jtx", 0).longVersionCode + Assume.assumeTrue(jtxVersionCode > 204020003) + insertRetrieveAssertInt(JtxICalObject.GEOFENCE_RADIUS, sample?.geofenceRadius, Component.VJOURNAL.name) + } + @Test fun check_PERCENT() = insertRetrieveAssertInt(JtxICalObject.PERCENT, sample?.percent, Component.VJOURNAL.name) @Test fun check_PRIORITY() = insertRetrieveAssertInt(JtxICalObject.PRIORITY, sample?.priority, Component.VJOURNAL.name) @Test fun check_DUE() = insertRetrieveAssertLong(JtxICalObject.DUE, sample?.due, Component.VJOURNAL.name) diff --git a/src/main/java/at/bitfire/ical4android/JtxICalObject.kt b/src/main/java/at/bitfire/ical4android/JtxICalObject.kt index c5fc44ac..79d2ec1a 100644 --- a/src/main/java/at/bitfire/ical4android/JtxICalObject.kt +++ b/src/main/java/at/bitfire/ical4android/JtxICalObject.kt @@ -91,6 +91,7 @@ open class JtxICalObject( var geoLong: Double? = null var location: String? = null var locationAltrep: String? = null + var geofenceRadius: Int? = null var uid: String = UUID.randomUUID().toString() @@ -229,6 +230,7 @@ open class JtxICalObject( const val X_PARAM_ATTACH_LABEL = "X-LABEL" // used for filename in KOrganizer const val X_PARAM_FILENAME = "FILENAME" // used for filename in GNOME Evolution const val X_PROP_XSTATUS = "X-STATUS" // used to define an extended status (additionally to standard status) + const val X_PROP_GEOFENCE_RADIUS = "X-GEOFENCE-RADIUS" // used to define a Geofence-Radius to notifiy the user when close /** * Parses an iCalendar resource and extracts the VTODOs and/or VJOURNALS. @@ -543,6 +545,7 @@ open class JtxICalObject( else -> when(prop.name) { X_PROP_COMPLETEDTIMEZONE -> iCalObject.completedTimezone = prop.value X_PROP_XSTATUS -> iCalObject.xstatus = prop.value + X_PROP_GEOFENCE_RADIUS -> iCalObject.geofenceRadius = try { prop.value.toInt() } catch (e: NumberFormatException) { Ical4Android.log.warning("Wrong format for geofenceRadius: ${prop.value}"); null } else -> iCalObject.unknown.add(Unknown(value = UnknownProperty.toJsonString(prop))) // save the whole property for unknown properties } } @@ -718,6 +721,9 @@ open class JtxICalObject( if (geoLat != null && geoLong != null) { props += Geo(geoLat!!.toBigDecimal(), geoLong!!.toBigDecimal()) } + geofenceRadius?.let { geofenceRadius -> + props += XProperty(X_PROP_GEOFENCE_RADIUS, geofenceRadius.toString()) + } color?.let { props += Color(null, Css3Color.nearestMatch(it).name) } url?.let { try { @@ -1398,6 +1404,7 @@ duration?.let(props::add) this.locationAltrep = newData.locationAltrep this.geoLat = newData.geoLat this.geoLong = newData.geoLong + this.geofenceRadius = newData.geofenceRadius this.percent = newData.percent this.classification = newData.classification this.status = newData.status @@ -1457,6 +1464,7 @@ duration?.let(props::add) values.getAsDouble(JtxContract.JtxICalObject.GEO_LONG)?.let { geoLong -> this.geoLong = geoLong } values.getAsString(JtxContract.JtxICalObject.LOCATION)?.let { location -> this.location = location } values.getAsString(JtxContract.JtxICalObject.LOCATION_ALTREP)?.let { locationAltrep -> this.locationAltrep = locationAltrep } + values.getAsInteger(JtxContract.JtxICalObject.GEOFENCE_RADIUS)?.let { geofenceRadius -> this.geofenceRadius = geofenceRadius } values.getAsInteger(JtxContract.JtxICalObject.PERCENT)?.let { percent -> this.percent = percent } values.getAsInteger(JtxContract.JtxICalObject.PRIORITY)?.let { priority -> this.priority = priority } values.getAsLong(JtxContract.JtxICalObject.DUE)?.let { due -> this.due = due } @@ -1682,6 +1690,7 @@ duration?.let(props::add) put(JtxContract.JtxICalObject.GEO_LONG, geoLong) put(JtxContract.JtxICalObject.LOCATION, location) put(JtxContract.JtxICalObject.LOCATION_ALTREP, locationAltrep) + put(JtxContract.JtxICalObject.GEOFENCE_RADIUS, geofenceRadius) put(JtxContract.JtxICalObject.PERCENT, percent) put(JtxContract.JtxICalObject.DTSTAMP, dtstamp) put(JtxContract.JtxICalObject.DTSTART, dtstart) diff --git a/src/main/java/at/techbee/jtx/JtxContract.kt b/src/main/java/at/techbee/jtx/JtxContract.kt index 9328a96a..7d5eace0 100644 --- a/src/main/java/at/techbee/jtx/JtxContract.kt +++ b/src/main/java/at/techbee/jtx/JtxContract.kt @@ -42,7 +42,7 @@ object JtxContract { const val AUTHORITY = "at.techbee.jtx.provider" /** The version of this SyncContentProviderContract */ - const val VERSION = 4 + const val VERSION = 5 /** Constructs an Uri for the Jtx Sync Adapter with the given Account * @param [account] The account that should be appended to the Base Uri @@ -279,6 +279,13 @@ object JtxContract { */ const val EXTENDED_STATUS = "xstatus" + /** + * Purpose: Defines the radius for a geofence in meters + * This is put into an extended property in the iCalendar-file + * Type: [String] + */ + const val GEOFENCE_RADIUS = "geofenceRadius" + /** * Purpose: This property defines the access classification for a calendar component. * The possible values of a status are defined in the enum [Classification].