1
0
mirror of https://github.com/etesync/android synced 2024-11-25 17:38:13 +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)
}
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).

View File

@ -13,82 +13,114 @@ import android.content.ContentProviderClient
import android.content.ContentProviderOperation
import android.content.ContentUris
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
import android.os.RemoteException
import android.provider.CalendarContract
import android.provider.CalendarContract.Calendars
import android.provider.CalendarContract.Events
import android.provider.CalendarContract.Reminders
import android.text.TextUtils
import android.provider.CalendarContract.*
import at.bitfire.ical4android.*
import com.etesync.syncadapter.App
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import net.fortuna.ical4j.model.component.VTimeZone
import org.apache.commons.lang3.StringUtils
import java.io.FileNotFoundException
import java.util.LinkedList
import java.util.*
import java.util.logging.Level
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.AndroidCalendarFactory
import at.bitfire.ical4android.BatchOperation
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.DateUtils
class LocalCalendar private constructor(
account: Account,
provider: ContentProviderClient,
id: Long
): 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>
@Throws(CalendarStorageException::class)
get() = queryEvents(Events.DELETED + "!=0 AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>
val COLUMN_CTAG = Calendars.CAL_SYNC1
override val withoutFileName: Array<LocalEvent>
@Throws(CalendarStorageException::class)
get() = queryEvents(Events._SYNC_ID + " IS NULL AND " + Events.ORIGINAL_ID + " IS NULL", null) as Array<LocalEvent>
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)
val all: Array<LocalEvent>
@Throws(CalendarStorageException::class)
get() = queryEvents(null, null) as Array<LocalEvent>
// 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)
override// get dirty events which are required to have an increased SEQUENCE value
// 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()
return AndroidCalendar.create(account, provider, values)
}
override fun eventBaseInfoColumns(): Array<String> {
return BASE_INFO_COLUMNS
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)
}
info.timeZone?.let { tzData ->
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) {
update(valuesFromCollectionInfo(journalEntity, updateColor))
fun update(journalEntity: JournalEntity, updateColor: Boolean) =
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
}
return dirty
}
@Throws(CalendarStorageException::class)
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
}
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()
@Throws(CalendarStorageException::class)
fun processDirtyExceptions() {
// process 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 {
val where = Events.CALENDAR_ID + "=?"
val whereArgs = arrayOf(id.toString())
try {
val cursor = provider.query(
syncAdapterURI(Events.CONTENT_URI), null,
where, whereArgs, null)
Events.CALENDAR_ID + "=?", arrayOf(id.toString()), null)
try {
return cursor.count.toLong()
return cursor?.count.toLong()
} finally {
cursor.close()
cursor?.close()
}
} catch (e: RemoteException) {
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.
* 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
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
}
override fun newInstance(account: Account, provider: ContentProviderClient, id: Long) =
LocalCalendar(account, provider, id)
}
}

View File

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

View File

@ -9,82 +9,92 @@
package com.etesync.syncadapter.resource
import android.accounts.Account
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
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.AndroidTaskListFactory
import at.bitfire.ical4android.CalendarStorageException
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>
@Throws(CalendarStorageException::class)
get() = queryTasks(Tasks._DELETED + "!=0", null) as Array<LocalTask>
override val withoutFileName: Array<LocalTask>
@Throws(CalendarStorageException::class)
get() = queryTasks(Tasks._SYNC_ID + " IS NULL", null) as Array<LocalTask>
override// sequence has not been assigned yet (i.e. this task was just locally created)
val dirty: Array<LocalTask>
@Throws(CalendarStorageException::class, FileNotFoundException::class)
get() {
val tasks = queryTasks(Tasks._DIRTY + "!=0 AND " + Tasks._DELETED + "== 0", null) as Array<LocalTask>
for (task in tasks) {
if (task.task.sequence == null)
task.task.sequence = 0
else
task.task.sequence++
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, TaskProvider.ProviderName.OpenTasks)
provider?.use { return true }
return false
}
return tasks
}
fun create(account: Account, provider: TaskProvider, journalEntity: JournalEntity): Uri {
val values = valuesFromCollectionInfo(journalEntity, true)
values.put(TaskLists.OWNER, account.name)
values.put(TaskLists.SYNC_ENABLED, 1)
values.put(TaskLists.VISIBLE, 1)
return create(account, provider, values)
}
private fun valuesFromCollectionInfo(journalEntity: JournalEntity, withColor: Boolean): ContentValues {
val info = journalEntity.info
val values = ContentValues(3)
values.put(TaskLists._SYNC_ID, info.uid)
values.put(TaskLists.LIST_NAME, if (info.displayName.isNullOrBlank()) info.uid else info.displayName)
if (withColor)
values.put(TaskLists.LIST_COLOR, info.color ?: defaultColor)
return values
}
override fun taskBaseInfoColumns(): Array<String> {
return BASE_INFO_COLUMNS
}
@Throws(CalendarStorageException::class)
fun update(info: CollectionInfo, updateColor: Boolean) {
update(valuesFromCollectionInfo(info, updateColor))
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
task.sequence = sequence + 1
}
return tasks
}
@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
}
override fun findWithoutFileName(): List<LocalTask>
= queryTasks(Tasks._SYNC_ID + " IS NULL", null)
override fun findByUid(uid: String): LocalTask?
= queryTasks(Tasks._SYNC_ID + " =? ", arrayOf(uid)).firstOrNull()
@Throws(CalendarStorageException::class)
override fun count(): Long {
val where = Tasks.LIST_ID + "=?"
val whereArgs = arrayOf(id.toString())
try {
val cursor = provider.client.query(
syncAdapterURI(provider.tasksUri()), null,
where, whereArgs, null)
TaskProvider.syncAdapterUri(provider.tasksUri()), null,
Tasks.LIST_ID + "=?", arrayOf(id.toString()), null)
try {
return cursor.count.toLong()
return cursor?.count.toLong()
} finally {
cursor.close()
cursor?.close()
}
} catch (e: RemoteException) {
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()
}
}
}
}