1
0
mirror of https://github.com/etesync/android synced 2024-11-26 01:48:34 +00:00
This commit is contained in:
Tom Hacohen 2019-01-06 11:20:54 +00:00
parent 7209d634a5
commit 88dad0a538
4 changed files with 159 additions and 260 deletions

View File

@ -199,7 +199,7 @@ class LocalAddressBook(
accountManager.removeAccount(account, null, null) accountManager.removeAccount(account, null, null)
} }
fun findAll(): List<LocalAddress> = queryContacts(RawContacts.DELETED + "== 0", null) override fun findAll(): List<LocalAddress> = queryContacts(RawContacts.DELETED + "== 0", null)
/** /**
* Returns an array of local contacts/groups which have been deleted locally. (DELETED != 0). * Returns an array of local contacts/groups which have been deleted locally. (DELETED != 0).

View File

@ -13,82 +13,114 @@ import android.content.ContentProviderClient
import android.content.ContentProviderOperation import android.content.ContentProviderOperation
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.RemoteException import android.os.RemoteException
import android.provider.CalendarContract import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars import android.provider.CalendarContract.*
import android.provider.CalendarContract.Events import at.bitfire.ical4android.*
import android.provider.CalendarContract.Reminders
import android.text.TextUtils
import com.etesync.syncadapter.App import com.etesync.syncadapter.App
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity import com.etesync.syncadapter.model.JournalEntity
import net.fortuna.ical4j.model.component.VTimeZone
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.LinkedList import java.util.*
import java.util.logging.Level
import at.bitfire.ical4android.AndroidCalendar class LocalCalendar private constructor(
import at.bitfire.ical4android.AndroidCalendarFactory account: Account,
import at.bitfire.ical4android.BatchOperation provider: ContentProviderClient,
import at.bitfire.ical4android.CalendarStorageException id: Long
import at.bitfire.ical4android.DateUtils ): AndroidCalendar<LocalEvent>(account, provider, LocalEvent.Factory, id), LocalCollection<LocalEvent> {
class LocalCalendar protected constructor(account: Account, provider: ContentProviderClient, id: Long) : AndroidCalendar(account, provider, LocalEvent.Factory.INSTANCE, id), LocalCollection<LocalEvent> { companion object {
val defaultColor = -0x743cb6 // light green 500
override val deleted: Array<LocalEvent> val COLUMN_CTAG = Calendars.CAL_SYNC1
@Throws(CalendarStorageException::class)
get() = queryEvents(Events.DELETED + "!=0 AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>
override val withoutFileName: Array<LocalEvent> fun create(account: Account, provider: ContentProviderClient, journalEntity: JournalEntity): Uri {
@Throws(CalendarStorageException::class) val values = valuesFromCollectionInfo(journalEntity, true)
get() = queryEvents(Events._SYNC_ID + " IS NULL AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
values.put(Calendars.ACCOUNT_NAME, account.name)
values.put(Calendars.ACCOUNT_TYPE, account.type)
values.put(Calendars.OWNER_ACCOUNT, account.name)
val all: Array<LocalEvent> // flag as visible & synchronizable at creation, might be changed by user at any time
@Throws(CalendarStorageException::class) values.put(Calendars.VISIBLE, 1)
get() = queryEvents(null, null) as Array<LocalEvent> values.put(Calendars.SYNC_EVENTS, 1)
override// get dirty events which are required to have an increased SEQUENCE value return AndroidCalendar.create(account, provider, values)
// sequence has not been assigned yet (i.e. this event was just locally created)
val dirty: Array<LocalEvent>
@Throws(CalendarStorageException::class, FileNotFoundException::class)
get() {
val dirty = LinkedList<LocalEvent>()
for (event in queryEvents(Events.DIRTY + "!=0 AND " + Events.DELETED + "==0 AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>) {
if (event.event.sequence == null)
event.event.sequence = 0
else if (event.weAreOrganizer)
event.event.sequence++
dirty.add(event)
} }
return dirty.toTypedArray() fun findByName(account: Account, provider: ContentProviderClient, factory: Factory, name: String): LocalCalendar?
= AndroidCalendar.find(account, provider, factory, Calendars.NAME + "==?", arrayOf(name)).firstOrNull()
private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val info = journalEntity.info
val values = ContentValues()
values.put(Calendars.NAME, info.uid)
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.displayName)
if (withColor)
values.put(Calendars.CALENDAR_COLOR, if (info.color != null) info.color else defaultColor)
if (journalEntity.isReadOnly)
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)
else {
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER)
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1)
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1)
} }
override fun eventBaseInfoColumns(): Array<String> { info.timeZone?.let { tzData ->
return BASE_INFO_COLUMNS try {
val timeZone = DateUtils.parseVTimeZone(tzData)
timeZone.timeZoneId?.let { tzId ->
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(tzId.value))
}
} catch(e: IllegalArgumentException) {
App.log.log(Level.WARNING, "Couldn't parse calendar default time zone", e)
}
}
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT)
values.put(Calendars.ALLOWED_AVAILABILITY, StringUtils.join(intArrayOf(Reminders.AVAILABILITY_TENTATIVE, Reminders.AVAILABILITY_FREE, Reminders.AVAILABILITY_BUSY), ","))
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, StringUtils.join(intArrayOf(CalendarContract.Attendees.TYPE_OPTIONAL, CalendarContract.Attendees.TYPE_REQUIRED, CalendarContract.Attendees.TYPE_RESOURCE), ", "))
return values
}
} }
@Throws(CalendarStorageException::class) fun update(journalEntity: JournalEntity, updateColor: Boolean) =
fun update(journalEntity: JournalEntity, updateColor: Boolean) {
update(valuesFromCollectionInfo(journalEntity, updateColor)) update(valuesFromCollectionInfo(journalEntity, updateColor))
override fun findDeleted() =
queryEvents("${Events.DELETED}!=0 AND ${Events.ORIGINAL_ID} IS NULL", null)
override fun findDirty(): List<LocalEvent> {
val dirty = LinkedList<LocalEvent>()
// get dirty events which are required to have an increased SEQUENCE value
for (localEvent in queryEvents("${Events.DIRTY}!=0 AND ${Events.ORIGINAL_ID} IS NULL", null)) {
val event = localEvent.event!!
val sequence = event.sequence
if (event.sequence == null) // sequence has not been assigned yet (i.e. this event was just locally created)
event.sequence = 0
else if (localEvent.weAreOrganizer)
event.sequence = sequence!! + 1
dirty += localEvent
} }
@Throws(CalendarStorageException::class) return dirty
override fun findByUid(uid: String): LocalEvent? {
val ret = queryEvents(Events._SYNC_ID + " =? ", arrayOf(uid)) as Array<LocalEvent>
return if (ret != null && ret.size > 0) {
ret[0]
} else null
} }
@Throws(CalendarStorageException::class) override fun findWithoutFileName(): List<LocalEvent>
= queryEvents(Events._SYNC_ID + " IS NULL AND " + Events.ORIGINAL_ID + " IS NULL", null)
override fun findAll(): List<LocalEvent>
= queryEvents(null, null)
override fun findByUid(uid: String): LocalEvent?
= queryEvents(Events._SYNC_ID + " =? ", arrayOf(uid)).firstOrNull()
fun processDirtyExceptions() { fun processDirtyExceptions() {
// process deleted exceptions // process deleted exceptions
App.log.info("Processing deleted exceptions") App.log.info("Processing deleted exceptions")
@ -163,19 +195,15 @@ class LocalCalendar protected constructor(account: Account, provider: ContentPro
} }
@Throws(CalendarStorageException::class)
override fun count(): Long { override fun count(): Long {
val where = Events.CALENDAR_ID + "=?"
val whereArgs = arrayOf(id.toString())
try { try {
val cursor = provider.query( val cursor = provider.query(
syncAdapterURI(Events.CONTENT_URI), null, syncAdapterURI(Events.CONTENT_URI), null,
where, whereArgs, null) Events.CALENDAR_ID + "=?", arrayOf(id.toString()), null)
try { try {
return cursor.count.toLong() return cursor?.count.toLong()
} finally { } finally {
cursor.close() cursor?.close()
} }
} catch (e: RemoteException) { } catch (e: RemoteException) {
throw CalendarStorageException("Couldn't query calendar events", e) throw CalendarStorageException("Couldn't query calendar events", e)
@ -183,20 +211,6 @@ class LocalCalendar protected constructor(account: Account, provider: ContentPro
} }
class Factory : AndroidCalendarFactory {
override fun newInstance(account: Account, provider: ContentProviderClient, id: Long): AndroidCalendar {
return LocalCalendar(account, provider, id)
}
override fun newArray(size: Int): Array<AndroidCalendar?> {
return arrayOfNulls<LocalCalendar>(size) as Array<AndroidCalendar?>
}
companion object {
val INSTANCE = Factory()
}
}
/** Fix all of the etags of all of the non-dirty events to be non-null. /** Fix all of the etags of all of the non-dirty events to be non-null.
* Currently set to all ones.. */ * Currently set to all ones.. */
@ -218,67 +232,9 @@ class LocalCalendar protected constructor(account: Account, provider: ContentPro
} }
companion object { object Factory: AndroidCalendarFactory<LocalCalendar> {
val defaultColor = -0x743cb6 // light green 500 override fun newInstance(account: Account, provider: ContentProviderClient, id: Long) =
LocalCalendar(account, provider, id)
val COLUMN_CTAG = Calendars.CAL_SYNC1
internal var BASE_INFO_COLUMNS = arrayOf(Events._ID, Events._SYNC_ID, LocalEvent.COLUMN_ETAG)
@Throws(CalendarStorageException::class)
fun create(account: Account, provider: ContentProviderClient, journalEntity: JournalEntity): Uri {
val values = valuesFromCollectionInfo(journalEntity, true)
// ACCOUNT_NAME and ACCOUNT_TYPE are required (see docs)! If it's missing, other apps will crash.
values.put(Calendars.ACCOUNT_NAME, account.name)
values.put(Calendars.ACCOUNT_TYPE, account.type)
values.put(Calendars.OWNER_ACCOUNT, account.name)
// flag as visible & synchronizable at creation, might be changed by user at any time
values.put(Calendars.VISIBLE, 1)
values.put(Calendars.SYNC_EVENTS, 1)
return AndroidCalendar.create(account, provider, values)
}
@Throws(FileNotFoundException::class, CalendarStorageException::class)
fun findByName(account: Account, provider: ContentProviderClient, factory: AndroidCalendarFactory, name: String): LocalCalendar? {
val ret = LocalCalendar.find(account, provider, factory, Calendars.NAME + "==?", arrayOf(name))
if (ret.size == 1) {
return ret[0]
} else {
App.log.severe("No calendar found for name $name")
return null
}
}
private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val info = journalEntity.info
val values = ContentValues()
values.put(Calendars.NAME, info.uid)
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.displayName)
if (withColor)
values.put(Calendars.CALENDAR_COLOR, if (info.color != null) info.color else defaultColor)
if (journalEntity.isReadOnly)
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ)
else {
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER)
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1)
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1)
}
if (!TextUtils.isEmpty(info.timeZone)) {
val timeZone = DateUtils.parseVTimeZone(info.timeZone)
if (timeZone != null && timeZone.timeZoneId != null)
values.put(Calendars.CALENDAR_TIME_ZONE, DateUtils.findAndroidTimezoneID(timeZone.timeZoneId.value))
}
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT)
values.put(Calendars.ALLOWED_AVAILABILITY, StringUtils.join(intArrayOf(Reminders.AVAILABILITY_TENTATIVE, Reminders.AVAILABILITY_FREE, Reminders.AVAILABILITY_BUSY), ","))
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, StringUtils.join(intArrayOf(CalendarContract.Attendees.TYPE_OPTIONAL, CalendarContract.Attendees.TYPE_REQUIRED, CalendarContract.Attendees.TYPE_RESOURCE), ", "))
return values
}
} }
} }

View File

@ -12,6 +12,7 @@ interface LocalCollection<out T: LocalResource<*>> {
fun findDeleted(): List<T> fun findDeleted(): List<T>
fun findDirty(): List<T> fun findDirty(): List<T>
fun findWithoutFileName(): List<T> fun findWithoutFileName(): List<T>
fun findAll(): List<T>
fun findByUid(uid: String): T? fun findByUid(uid: String): T?

View File

@ -9,82 +9,92 @@
package com.etesync.syncadapter.resource package com.etesync.syncadapter.resource
import android.accounts.Account import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.RemoteException import android.os.RemoteException
import com.etesync.syncadapter.model.CollectionInfo
import org.dmfs.provider.tasks.TaskContract.TaskLists
import org.dmfs.provider.tasks.TaskContract.Tasks
import java.io.FileNotFoundException
import at.bitfire.ical4android.AndroidTaskList import at.bitfire.ical4android.AndroidTaskList
import at.bitfire.ical4android.AndroidTaskListFactory import at.bitfire.ical4android.AndroidTaskListFactory
import at.bitfire.ical4android.CalendarStorageException import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.TaskProvider import at.bitfire.ical4android.TaskProvider
import com.etesync.syncadapter.model.JournalEntity
import org.dmfs.tasks.contract.TaskContract.TaskLists
import org.dmfs.tasks.contract.TaskContract.Tasks
class LocalTaskList protected constructor(account: Account, provider: TaskProvider, id: Long) : AndroidTaskList(account, provider, LocalTask.Factory.INSTANCE, id), LocalCollection<LocalTask> { class LocalTaskList private constructor(
account: Account,
provider: TaskProvider,
id: Long
): AndroidTaskList<LocalTask>(account, provider, LocalTask.Factory, id), LocalCollection<LocalTask> {
companion object {
val defaultColor = -0x3c1592 // "DAVdroid green"
override val deleted: Array<LocalTask> fun tasksProviderAvailable(context: Context): Boolean {
@Throws(CalendarStorageException::class) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
get() = queryTasks(Tasks._DELETED + "!=0", null) as Array<LocalTask> return context.packageManager.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
else {
val provider = TaskProvider.acquire(context, TaskProvider.ProviderName.OpenTasks)
provider?.use { return true }
return false
}
}
override val withoutFileName: Array<LocalTask> fun create(account: Account, provider: TaskProvider, journalEntity: JournalEntity): Uri {
@Throws(CalendarStorageException::class) val values = valuesFromCollectionInfo(journalEntity, true)
get() = queryTasks(Tasks._SYNC_ID + " IS NULL", null) as Array<LocalTask> values.put(TaskLists.OWNER, account.name)
values.put(TaskLists.SYNC_ENABLED, 1)
values.put(TaskLists.VISIBLE, 1)
return create(account, provider, values)
}
override// sequence has not been assigned yet (i.e. this task was just locally created) private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val dirty: Array<LocalTask> val info = journalEntity.info
@Throws(CalendarStorageException::class, FileNotFoundException::class) val values = ContentValues(3)
get() { values.put(TaskLists._SYNC_ID, info.uid)
val tasks = queryTasks(Tasks._DIRTY + "!=0 AND " + Tasks._DELETED + "== 0", null) as Array<LocalTask> values.put(TaskLists.LIST_NAME, if (info.displayName.isNullOrBlank()) info.uid else info.displayName)
for (task in tasks) {
if (task.task.sequence == null) if (withColor)
task.task.sequence = 0 values.put(TaskLists.LIST_COLOR, info.color ?: defaultColor)
return values
}
}
fun update(journalEntity: JournalEntity, updateColor: Boolean) =
update(valuesFromCollectionInfo(journalEntity, updateColor))
override fun findDeleted() = queryTasks("${Tasks._DELETED}!=0", null)
override fun findDirty(): List<LocalTask> {
val tasks = queryTasks("${Tasks._DIRTY}!=0", null)
for (localTask in tasks) {
val task = requireNotNull(localTask.task)
val sequence = task.sequence
if (sequence == null) // sequence has not been assigned yet (i.e. this task was just locally created)
task.sequence = 0
else else
task.task.sequence++ task.sequence = sequence + 1
} }
return tasks return tasks
} }
override fun findWithoutFileName(): List<LocalTask>
= queryTasks(Tasks._SYNC_ID + " IS NULL", null)
override fun taskBaseInfoColumns(): Array<String> { override fun findByUid(uid: String): LocalTask?
return BASE_INFO_COLUMNS = queryTasks(Tasks._SYNC_ID + " =? ", arrayOf(uid)).firstOrNull()
}
@Throws(CalendarStorageException::class)
fun update(info: CollectionInfo, updateColor: Boolean) {
update(valuesFromCollectionInfo(info, updateColor))
}
@Throws(CalendarStorageException::class)
override fun findByUid(uid: String): LocalTask? {
val ret = queryTasks(Tasks._SYNC_ID + " =? ", arrayOf(uid)) as Array<LocalTask>
return if (ret != null && ret.size > 0) {
ret[0]
} else null
}
@Throws(CalendarStorageException::class)
override fun count(): Long { override fun count(): Long {
val where = Tasks.LIST_ID + "=?"
val whereArgs = arrayOf(id.toString())
try { try {
val cursor = provider.client.query( val cursor = provider.client.query(
syncAdapterURI(provider.tasksUri()), null, TaskProvider.syncAdapterUri(provider.tasksUri()), null,
where, whereArgs, null) Tasks.LIST_ID + "=?", arrayOf(id.toString()), null)
try { try {
return cursor.count.toLong() return cursor?.count.toLong()
} finally { } finally {
cursor.close() cursor?.close()
} }
} catch (e: RemoteException) { } catch (e: RemoteException) {
throw CalendarStorageException("Couldn't query calendar events", e) throw CalendarStorageException("Couldn't query calendar events", e)
@ -92,78 +102,10 @@ class LocalTaskList protected constructor(account: Account, provider: TaskProvid
} }
object Factory: AndroidTaskListFactory<LocalTaskList> {
class Factory : AndroidTaskListFactory { override fun newInstance(account: Account, provider: TaskProvider, id: Long) =
LocalTaskList(account, provider, id)
override fun newInstance(account: Account, provider: TaskProvider, id: Long): AndroidTaskList {
return LocalTaskList(account, provider, id)
}
override fun newArray(size: Int): Array<AndroidTaskList?> {
return arrayOfNulls<LocalTaskList>(size) as Array<AndroidTaskList?>
}
companion object {
val INSTANCE = Factory()
}
}
companion object {
val defaultColor = -0x3c1592 // "DAVdroid green"
val COLUMN_CTAG = TaskLists.SYNC_VERSION
internal var BASE_INFO_COLUMNS = arrayOf(Tasks._ID, Tasks._SYNC_ID, LocalTask.COLUMN_ETAG)
@Throws(CalendarStorageException::class)
fun create(account: Account, provider: TaskProvider, info: CollectionInfo): Uri {
val values = valuesFromCollectionInfo(info, true)
values.put(TaskLists.OWNER, account.name)
values.put(TaskLists.SYNC_ENABLED, 1)
values.put(TaskLists.VISIBLE, 1)
return AndroidTaskList.create(account, provider, values)
}
private fun valuesFromCollectionInfo(info: CollectionInfo, withColor: Boolean): ContentValues {
val values = ContentValues()
values.put(TaskLists._SYNC_ID, info.uid)
values.put(TaskLists.LIST_NAME, info.displayName)
if (withColor)
values.put(TaskLists.LIST_COLOR, if (info.color != null) info.color else defaultColor)
return values
}
// helpers
fun tasksProviderAvailable(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.packageManager.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
else {
val provider = TaskProvider.acquire(context.contentResolver, TaskProvider.ProviderName.OpenTasks)
try {
return provider != null
} finally {
provider?.close()
}
}
}
// HELPERS
@Throws(RemoteException::class)
fun onRenameAccount(resolver: ContentResolver, oldName: String, newName: String) {
val client = resolver.acquireContentProviderClient(TaskProvider.ProviderName.OpenTasks.authority)
if (client != null) {
val values = ContentValues(1)
values.put(Tasks.ACCOUNT_NAME, newName)
client.update(Tasks.getContentUri(TaskProvider.ProviderName.OpenTasks.authority), values, Tasks.ACCOUNT_NAME + "=?", arrayOf(oldName))
client.release()
}
}
}
} }
}