Loading src/androidTest/java/at/bitfire/ical4android/MiscUtilsTest.kt +24 −0 Original line number Diff line number Diff line Loading @@ -9,10 +9,14 @@ package at.bitfire.ical4android import android.content.ContentValues import android.database.MatrixCursor import androidx.test.filters.SmallTest import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import at.bitfire.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 Loading Loading @@ -98,6 +102,26 @@ class MiscUtilsTest { } @Test @SmallTest fun testCursorToValues() { val columns = arrayOf("col1", "col2") val c = MatrixCursor(columns) c.addRow(arrayOf("row1_val1", "row1_val2")) c.moveToFirst() val values = c.toValues() assertEquals("row1_val1", values.getAsString("col1")) 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 { private val s = "test" Loading src/main/java/at/bitfire/ical4android/AndroidCalendar.kt +3 −6 Original line number Diff line number Diff line Loading @@ -12,10 +12,10 @@ import android.accounts.Account import android.content.ContentProviderClient import android.content.ContentUris import android.content.ContentValues import android.database.DatabaseUtils import android.net.Uri import android.provider.CalendarContract import android.provider.CalendarContract.* import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import java.io.FileNotFoundException import java.util.* import java.util.logging.Level Loading Loading @@ -177,11 +177,8 @@ abstract class AndroidCalendar<out T: AndroidEvent>( val events = LinkedList<T>() provider.query(eventsSyncURI(), null, where, whereArgs, null)?.use { cursor -> while (cursor.moveToNext()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) events += eventFactory.fromProvider(this, values) } while (cursor.moveToNext()) events += eventFactory.fromProvider(this, cursor.toValues()) } return events } Loading src/main/java/at/bitfire/ical4android/AndroidEvent.kt +2 −3 Original line number Diff line number Diff line Loading @@ -13,12 +13,12 @@ import android.content.ContentProviderOperation.Builder import android.content.ContentUris import android.content.ContentValues import android.content.EntityIterator import android.database.DatabaseUtils import android.net.Uri import android.os.RemoteException import android.provider.CalendarContract import android.provider.CalendarContract.* import android.util.Base64 import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import net.fortuna.ical4j.model.* import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.TimeZone Loading Loading @@ -361,8 +361,7 @@ abstract class AndroidEvent( null, Events.ORIGINAL_ID + "=?", arrayOf(id.toString()), null)?.use { c -> while (c.moveToNext()) { val values = ContentValues(c.columnCount) DatabaseUtils.cursorRowToContentValues(c, values) val values = c.toValues() try { val exception = calendar.eventFactory.fromProvider(calendar, values) Loading src/main/java/at/bitfire/ical4android/AndroidTask.kt +61 −7 Original line number Diff line number Diff line Loading @@ -12,14 +12,16 @@ import android.content.ContentProviderOperation import android.content.ContentProviderOperation.Builder import android.content.ContentUris import android.content.ContentValues import android.database.DatabaseUtils import android.net.Uri import android.os.RemoteException import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.DateTime import net.fortuna.ical4j.model.Dur import net.fortuna.ical4j.model.property.* import org.dmfs.tasks.contract.TaskContract.Tasks import org.dmfs.tasks.contract.TaskContract.* import org.dmfs.tasks.contract.TaskContract.Properties import org.dmfs.tasks.contract.TaskContract.Property.Category import java.io.FileNotFoundException import java.net.URI import java.net.URISyntaxException Loading Loading @@ -66,11 +68,25 @@ abstract class AndroidTask( val id = requireNotNull(id) task = Task() taskList.provider.client.query(taskSyncURI(), null, null, null, null)?.use { cursor -> val client = taskList.provider.client client.query(taskSyncURI(), null, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) val values = cursor.toValues() Constants.log.log(Level.FINER, "Found task", values) populateTask(values) if (values.getAsInteger(Tasks.HAS_PROPERTIES) != 0) // fetch properties client.query(taskList.tasksPropertiesSyncUri(), null, "${Properties.TASK_ID}=?", arrayOf(id.toString()), null)?.use { propCursor -> while (propCursor.moveToNext()) { val propValues = propCursor.toValues() Constants.log.log(Level.FINER, "Found property", propValues) populateProperty(propValues) } } return task } } Loading Loading @@ -161,6 +177,17 @@ abstract class AndroidTask( values.getAsString(Tasks.RRULE)?.let { task.rRule = RRule(it) } } protected open fun populateProperty(values: ContentValues) { val task = requireNotNull(task) val type = values.getAsString(Properties.MIMETYPE) when (type) { Category.CONTENT_ITEM_TYPE -> task.categories += values.getAsString(Category.CATEGORY_NAME) else -> Constants.log.warning("Found unknown property of type $type") } } fun add(): Uri { val batch = BatchOperation(taskList.provider.client) Loading @@ -169,8 +196,13 @@ abstract class AndroidTask( batch.enqueue(BatchOperation.Operation(builder)) batch.commit() // TODO use backref mechanism so that only one commit is required for the whole task val result = batch.getResult(0) ?: throw CalendarStorageException("Empty result from provider when adding a task") id = ContentUris.parseId(result.uri) insertProperties(batch) batch.commit() return result.uri } Loading @@ -182,10 +214,30 @@ abstract class AndroidTask( val builder = ContentProviderOperation.newUpdate(uri) buildTask(builder, true) batch.enqueue(BatchOperation.Operation(builder)) val deleteProperties = ContentProviderOperation.newDelete(taskList.tasksPropertiesSyncUri()) .withSelection("${Properties.TASK_ID}=?", arrayOf(id.toString())) batch.enqueue(BatchOperation.Operation(deleteProperties)) insertProperties(batch) batch.commit() return uri } private fun insertProperties(batch: BatchOperation) { val task = requireNotNull(task) // insert categories for (category in task.categories) { val builder = ContentProviderOperation.newInsert(taskList.tasksPropertiesSyncUri()) builder .withValue(Category.TASK_ID, id) .withValue(Category.MIMETYPE, Category.CONTENT_ITEM_TYPE) .withValue(Category.CATEGORY_NAME, category) Constants.log.log(Level.FINE, "Inserting category", builder.build()) batch.enqueue(BatchOperation.Operation(builder)) } } fun delete(): Int { try { return taskList.provider.client.delete(taskSyncURI(), null, null) Loading Loading @@ -268,7 +320,6 @@ abstract class AndroidTask( .withValue(Tasks.RRULE, task.rRule?.value) builder .withValue(Tasks.EXDATE, if (task.exDates.isEmpty()) null else DateUtils.recurrenceSetsToAndroidString(task.exDates, allDay)) Constants.log.log(Level.FINE, "Built task object", builder.build()) } Loading @@ -286,7 +337,10 @@ abstract class AndroidTask( protected fun taskSyncURI(): Uri { val id = requireNotNull(id) return ContentUris.withAppendedId(taskList.tasksSyncUri(), id) val builder = taskList.tasksSyncUri().buildUpon() return ContentUris.appendId(builder, id) .appendQueryParameter(LOAD_PROPERTIES, "1") .build() } Loading src/main/java/at/bitfire/ical4android/AndroidTaskList.kt +7 −11 Original line number Diff line number Diff line Loading @@ -12,14 +12,15 @@ import android.accounts.Account import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.database.DatabaseUtils import android.net.Uri import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import org.dmfs.tasks.contract.TaskContract import org.dmfs.tasks.contract.TaskContract.TaskLists import org.dmfs.tasks.contract.TaskContract.Tasks import java.io.FileNotFoundException import java.util.* /** * Represents a locally stored task list, containing AndroidTasks (whose data objects are Tasks). * Communicates with third-party content providers to store the tasks. Loading Loading @@ -67,9 +68,7 @@ abstract class AndroidTaskList<out T: AndroidTask>( provider.client.query(TaskProvider.syncAdapterUri(ContentUris.withAppendedId(provider.taskListsUri(), id), account), null, null, null, null)?.use { cursor -> if (cursor.moveToNext()) { val taskList = factory.newInstance(account, provider, id) val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) taskList.populate(values) taskList.populate(cursor.toValues()) return taskList } } Loading @@ -80,8 +79,7 @@ abstract class AndroidTaskList<out T: AndroidTask>( val taskLists = LinkedList<T>() provider.client.query(TaskProvider.syncAdapterUri(provider.taskListsUri(), account), null, where, whereArgs, null)?.use { cursor -> while (cursor.moveToNext()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) val values = cursor.toValues() val taskList = factory.newInstance(account, provider, values.getAsLong(TaskLists._ID)) taskList.populate(values) taskLists += taskList Loading Loading @@ -127,11 +125,8 @@ abstract class AndroidTaskList<out T: AndroidTask>( tasksSyncUri(), null, where, whereArgs, null)?.use { cursor -> while (cursor.moveToNext()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) tasks += taskFactory.fromProvider(this, values) } while (cursor.moveToNext()) tasks += taskFactory.fromProvider(this, cursor.toValues()) } return tasks } Loading @@ -142,5 +137,6 @@ abstract class AndroidTaskList<out T: AndroidTask>( fun taskListSyncUri() = TaskProvider.syncAdapterUri(ContentUris.withAppendedId(provider.taskListsUri(), id), account) fun tasksSyncUri() = TaskProvider.syncAdapterUri(provider.tasksUri(), account) fun tasksPropertiesSyncUri() = TaskProvider.syncAdapterUri(provider.propertiesUri(), account) } Loading
src/androidTest/java/at/bitfire/ical4android/MiscUtilsTest.kt +24 −0 Original line number Diff line number Diff line Loading @@ -9,10 +9,14 @@ package at.bitfire.ical4android import android.content.ContentValues import android.database.MatrixCursor import androidx.test.filters.SmallTest import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import at.bitfire.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 Loading Loading @@ -98,6 +102,26 @@ class MiscUtilsTest { } @Test @SmallTest fun testCursorToValues() { val columns = arrayOf("col1", "col2") val c = MatrixCursor(columns) c.addRow(arrayOf("row1_val1", "row1_val2")) c.moveToFirst() val values = c.toValues() assertEquals("row1_val1", values.getAsString("col1")) 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 { private val s = "test" Loading
src/main/java/at/bitfire/ical4android/AndroidCalendar.kt +3 −6 Original line number Diff line number Diff line Loading @@ -12,10 +12,10 @@ import android.accounts.Account import android.content.ContentProviderClient import android.content.ContentUris import android.content.ContentValues import android.database.DatabaseUtils import android.net.Uri import android.provider.CalendarContract import android.provider.CalendarContract.* import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import java.io.FileNotFoundException import java.util.* import java.util.logging.Level Loading Loading @@ -177,11 +177,8 @@ abstract class AndroidCalendar<out T: AndroidEvent>( val events = LinkedList<T>() provider.query(eventsSyncURI(), null, where, whereArgs, null)?.use { cursor -> while (cursor.moveToNext()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) events += eventFactory.fromProvider(this, values) } while (cursor.moveToNext()) events += eventFactory.fromProvider(this, cursor.toValues()) } return events } Loading
src/main/java/at/bitfire/ical4android/AndroidEvent.kt +2 −3 Original line number Diff line number Diff line Loading @@ -13,12 +13,12 @@ import android.content.ContentProviderOperation.Builder import android.content.ContentUris import android.content.ContentValues import android.content.EntityIterator import android.database.DatabaseUtils import android.net.Uri import android.os.RemoteException import android.provider.CalendarContract import android.provider.CalendarContract.* import android.util.Base64 import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import net.fortuna.ical4j.model.* import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.TimeZone Loading Loading @@ -361,8 +361,7 @@ abstract class AndroidEvent( null, Events.ORIGINAL_ID + "=?", arrayOf(id.toString()), null)?.use { c -> while (c.moveToNext()) { val values = ContentValues(c.columnCount) DatabaseUtils.cursorRowToContentValues(c, values) val values = c.toValues() try { val exception = calendar.eventFactory.fromProvider(calendar, values) Loading
src/main/java/at/bitfire/ical4android/AndroidTask.kt +61 −7 Original line number Diff line number Diff line Loading @@ -12,14 +12,16 @@ import android.content.ContentProviderOperation import android.content.ContentProviderOperation.Builder import android.content.ContentUris import android.content.ContentValues import android.database.DatabaseUtils import android.net.Uri import android.os.RemoteException import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import net.fortuna.ical4j.model.Date import net.fortuna.ical4j.model.DateTime import net.fortuna.ical4j.model.Dur import net.fortuna.ical4j.model.property.* import org.dmfs.tasks.contract.TaskContract.Tasks import org.dmfs.tasks.contract.TaskContract.* import org.dmfs.tasks.contract.TaskContract.Properties import org.dmfs.tasks.contract.TaskContract.Property.Category import java.io.FileNotFoundException import java.net.URI import java.net.URISyntaxException Loading Loading @@ -66,11 +68,25 @@ abstract class AndroidTask( val id = requireNotNull(id) task = Task() taskList.provider.client.query(taskSyncURI(), null, null, null, null)?.use { cursor -> val client = taskList.provider.client client.query(taskSyncURI(), null, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) val values = cursor.toValues() Constants.log.log(Level.FINER, "Found task", values) populateTask(values) if (values.getAsInteger(Tasks.HAS_PROPERTIES) != 0) // fetch properties client.query(taskList.tasksPropertiesSyncUri(), null, "${Properties.TASK_ID}=?", arrayOf(id.toString()), null)?.use { propCursor -> while (propCursor.moveToNext()) { val propValues = propCursor.toValues() Constants.log.log(Level.FINER, "Found property", propValues) populateProperty(propValues) } } return task } } Loading Loading @@ -161,6 +177,17 @@ abstract class AndroidTask( values.getAsString(Tasks.RRULE)?.let { task.rRule = RRule(it) } } protected open fun populateProperty(values: ContentValues) { val task = requireNotNull(task) val type = values.getAsString(Properties.MIMETYPE) when (type) { Category.CONTENT_ITEM_TYPE -> task.categories += values.getAsString(Category.CATEGORY_NAME) else -> Constants.log.warning("Found unknown property of type $type") } } fun add(): Uri { val batch = BatchOperation(taskList.provider.client) Loading @@ -169,8 +196,13 @@ abstract class AndroidTask( batch.enqueue(BatchOperation.Operation(builder)) batch.commit() // TODO use backref mechanism so that only one commit is required for the whole task val result = batch.getResult(0) ?: throw CalendarStorageException("Empty result from provider when adding a task") id = ContentUris.parseId(result.uri) insertProperties(batch) batch.commit() return result.uri } Loading @@ -182,10 +214,30 @@ abstract class AndroidTask( val builder = ContentProviderOperation.newUpdate(uri) buildTask(builder, true) batch.enqueue(BatchOperation.Operation(builder)) val deleteProperties = ContentProviderOperation.newDelete(taskList.tasksPropertiesSyncUri()) .withSelection("${Properties.TASK_ID}=?", arrayOf(id.toString())) batch.enqueue(BatchOperation.Operation(deleteProperties)) insertProperties(batch) batch.commit() return uri } private fun insertProperties(batch: BatchOperation) { val task = requireNotNull(task) // insert categories for (category in task.categories) { val builder = ContentProviderOperation.newInsert(taskList.tasksPropertiesSyncUri()) builder .withValue(Category.TASK_ID, id) .withValue(Category.MIMETYPE, Category.CONTENT_ITEM_TYPE) .withValue(Category.CATEGORY_NAME, category) Constants.log.log(Level.FINE, "Inserting category", builder.build()) batch.enqueue(BatchOperation.Operation(builder)) } } fun delete(): Int { try { return taskList.provider.client.delete(taskSyncURI(), null, null) Loading Loading @@ -268,7 +320,6 @@ abstract class AndroidTask( .withValue(Tasks.RRULE, task.rRule?.value) builder .withValue(Tasks.EXDATE, if (task.exDates.isEmpty()) null else DateUtils.recurrenceSetsToAndroidString(task.exDates, allDay)) Constants.log.log(Level.FINE, "Built task object", builder.build()) } Loading @@ -286,7 +337,10 @@ abstract class AndroidTask( protected fun taskSyncURI(): Uri { val id = requireNotNull(id) return ContentUris.withAppendedId(taskList.tasksSyncUri(), id) val builder = taskList.tasksSyncUri().buildUpon() return ContentUris.appendId(builder, id) .appendQueryParameter(LOAD_PROPERTIES, "1") .build() } Loading
src/main/java/at/bitfire/ical4android/AndroidTaskList.kt +7 −11 Original line number Diff line number Diff line Loading @@ -12,14 +12,15 @@ import android.accounts.Account import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.database.DatabaseUtils import android.net.Uri import at.bitfire.ical4android.MiscUtils.CursorHelper.toValues import org.dmfs.tasks.contract.TaskContract import org.dmfs.tasks.contract.TaskContract.TaskLists import org.dmfs.tasks.contract.TaskContract.Tasks import java.io.FileNotFoundException import java.util.* /** * Represents a locally stored task list, containing AndroidTasks (whose data objects are Tasks). * Communicates with third-party content providers to store the tasks. Loading Loading @@ -67,9 +68,7 @@ abstract class AndroidTaskList<out T: AndroidTask>( provider.client.query(TaskProvider.syncAdapterUri(ContentUris.withAppendedId(provider.taskListsUri(), id), account), null, null, null, null)?.use { cursor -> if (cursor.moveToNext()) { val taskList = factory.newInstance(account, provider, id) val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) taskList.populate(values) taskList.populate(cursor.toValues()) return taskList } } Loading @@ -80,8 +79,7 @@ abstract class AndroidTaskList<out T: AndroidTask>( val taskLists = LinkedList<T>() provider.client.query(TaskProvider.syncAdapterUri(provider.taskListsUri(), account), null, where, whereArgs, null)?.use { cursor -> while (cursor.moveToNext()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) val values = cursor.toValues() val taskList = factory.newInstance(account, provider, values.getAsLong(TaskLists._ID)) taskList.populate(values) taskLists += taskList Loading Loading @@ -127,11 +125,8 @@ abstract class AndroidTaskList<out T: AndroidTask>( tasksSyncUri(), null, where, whereArgs, null)?.use { cursor -> while (cursor.moveToNext()) { val values = ContentValues(cursor.columnCount) DatabaseUtils.cursorRowToContentValues(cursor, values) tasks += taskFactory.fromProvider(this, values) } while (cursor.moveToNext()) tasks += taskFactory.fromProvider(this, cursor.toValues()) } return tasks } Loading @@ -142,5 +137,6 @@ abstract class AndroidTaskList<out T: AndroidTask>( fun taskListSyncUri() = TaskProvider.syncAdapterUri(ContentUris.withAppendedId(provider.taskListsUri(), id), account) fun tasksSyncUri() = TaskProvider.syncAdapterUri(provider.tasksUri(), account) fun tasksPropertiesSyncUri() = TaskProvider.syncAdapterUri(provider.propertiesUri(), account) }