Commit 59dd6638 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Sync error notifications: retry action, lower importance of IOEXceptions

parent 44af04e3
......@@ -104,7 +104,6 @@ class LocalAddressBook(
}
override val title = account.name
override val uniqueId = "contacts-${account.name}"
/**
* Whether contact groups ([LocalGroup]) are included in query results
......
......@@ -88,8 +88,6 @@ class LocalCalendar private constructor(
override val title: String
get() = displayName ?: id.toString()
override val uniqueId = "calendar-$id"
override var lastSyncState: SyncState?
get() = provider.query(calendarSyncURI(), arrayOf(COLUMN_SYNC_STATE), null, null, null)?.let { cursor ->
if (cursor.moveToNext())
......
......@@ -14,8 +14,6 @@ interface LocalCollection<out T: LocalResource> {
/** collection title (used for user notifications etc.) **/
val title: String
/** unique ID, used to distinguish notifications for different collections **/
val uniqueId: String
var lastSyncState: SyncState?
......
......@@ -87,8 +87,6 @@ class LocalTaskList private constructor(
override val title: String
get() = name ?: id.toString()
override val uniqueId = "tasks-$id"
override var lastSyncState: SyncState?
get() {
try {
......
......@@ -181,8 +181,9 @@ class CalendarSyncManager(
syncResult.stats.numUpdates++
} else {
Logger.log.info("Adding $fileName to local calendar")
val newEvent = LocalEvent(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)
newEvent.add()
useLocal(LocalEvent(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT), {
it.add()
})
syncResult.stats.numInserts++
}
})
......
......@@ -24,7 +24,7 @@ import java.util.logging.Level
class ContactsSyncAdapterService: SyncAdapterService() {
companion object {
val PREVIOUS_GROUP_METHOD = "previous_group_method"
const val PREVIOUS_GROUP_METHOD = "previous_group_method"
}
override fun syncAdapter() = ContactsSyncAdapter(this)
......
......@@ -407,38 +407,39 @@ class ContactsSyncManager(
if (local == null) {
if (newData.group) {
Logger.log.log(Level.INFO, "Creating local group", newData)
val group = LocalGroup(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)
group.create()
local = group
useLocal(LocalGroup(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT), {
it.create()
local = it
})
} else {
Logger.log.log(Level.INFO, "Creating local contact", newData)
val contact = LocalContact(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)
contact.create()
local = contact
useLocal(LocalContact(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT), {
it.create()
local = it
})
}
syncResult.stats.numInserts++
}
if (groupMethod == GroupMethod.CATEGORIES && local is LocalContact) {
// VCard3: update group memberships from CATEGORIES
val batch = BatchOperation(provider)
Logger.log.log(Level.FINE, "Removing contact group memberships")
local.removeGroupMemberships(batch)
if (groupMethod == GroupMethod.CATEGORIES)
(local as? LocalContact)?.let { localContact ->
// VCard3: update group memberships from CATEGORIES
val batch = BatchOperation(provider)
Logger.log.log(Level.FINE, "Removing contact group memberships")
localContact.removeGroupMemberships(batch)
for (category in localContact.contact!!.categories) {
val groupID = localCollection.findOrCreateGroup(category)
Logger.log.log(Level.FINE, "Adding membership in group $category ($groupID)")
localContact.addToGroup(batch, groupID)
}
for (category in local.contact!!.categories) {
val groupID = localCollection.findOrCreateGroup(category)
Logger.log.log(Level.FINE, "Adding membership in group $category ($groupID)")
local.addToGroup(batch, groupID)
batch.commit()
}
batch.commit()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < Build.VERSION_CODES.O && local is LocalContact)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
// workaround for Android 7 which sets DIRTY flag when only meta-data is changed
local.updateHashCode(null)
(local as? LocalContact)?.updateHashCode(null)
})
}
......
......@@ -31,7 +31,7 @@ import java.util.logging.Level
abstract class SyncAdapterService: Service() {
companion object {
val runningSyncs = Collections.synchronizedSet(mutableSetOf<Pair<String, Account>>())!!
val runningSyncs: MutableSet<Pair<String, Account>> = Collections.synchronizedSet(mutableSetOf<Pair<String, Account>>())
}
protected abstract fun syncAdapter(): AbstractThreadedSyncAdapter
......@@ -67,7 +67,10 @@ abstract class SyncAdapterService: Service() {
return
}
sync(settings, account, extras, authority, provider, syncResult)
//if (runSync) {
SyncManager.cancelNotifications(NotificationManagerCompat.from(context), authority, account)
sync(settings, account, extras, authority, provider, syncResult)
//}
}
Logger.log.info("Sync for $authority complete")
} finally {
......
......@@ -61,7 +61,23 @@ abstract class SyncManager<out ResourceType: LocalResource, out CollectionType:
val localCollection: CollectionType
): AutoCloseable {
companion object {
fun cancelNotifications(manager: NotificationManagerCompat, authority: String, account: Account) =
manager.cancel(notificationTag(authority, account), NotificationUtils.NOTIFY_SYNC_ERROR)
private fun notificationTag(authority: String, account: Account) =
"$authority-${account.name}".hashCode().toString()
}
private val mainAccount = if (localCollection is LocalAddressBook)
localCollection.mainAccount
else
account
protected val notificationManager = NotificationManagerCompat.from(context)
protected val notificationTag = Companion.notificationTag(authority, mainAccount)
/** Local resource we're currently operating on. Used for error notifications. **/
protected val currentLocalResource = LinkedList<LocalResource>()
......@@ -71,7 +87,7 @@ abstract class SyncManager<out ResourceType: LocalResource, out CollectionType:
fun performSync() {
// dismiss previous error notifications
notificationManager.cancel(localCollection.uniqueId, NotificationUtils.NOTIFY_SYNC_ERROR)
notificationManager.cancel(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR)
try {
Logger.log.info("Preparing synchronization")
......@@ -326,6 +342,7 @@ abstract class SyncManager<out ResourceType: LocalResource, out CollectionType:
}
val contentIntent: Intent
var viewItemAction: NotificationCompat.Action? = null
if (e is UnauthorizedException) {
contentIntent = Intent(context, AccountSettingsActivity::class.java)
contentIntent.putExtra(AccountSettingsActivity.EXTRA_ACCOUNT, account)
......@@ -334,34 +351,28 @@ abstract class SyncManager<out ResourceType: LocalResource, out CollectionType:
contentIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e)
contentIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account)
contentIntent.putExtra(DebugInfoActivity.KEY_AUTHORITY, authority)
}
// use current local/remote resource
var viewItemAction: NotificationCompat.Action? = null
currentLocalResource.firstOrNull()?.let { local ->
// pass local resource info to debug info
contentIntent.putExtra(DebugInfoActivity.KEY_LOCAL_RESOURCE, local.toString())
// use current local/remote resource
currentLocalResource.firstOrNull()?.let { local ->
// pass local resource info to debug info
contentIntent.putExtra(DebugInfoActivity.KEY_LOCAL_RESOURCE, local.toString())
// generate "view item" action
viewItemAction = buildViewItemAction(local)
}
currentRemoteResource.firstOrNull()?.let { remote ->
contentIntent.putExtra(DebugInfoActivity.KEY_REMOTE_RESOURCE, remote.location.toString())
// generate "view item" action
viewItemAction = buildViewItemAction(local)
}
currentRemoteResource.firstOrNull()?.let { remote ->
contentIntent.putExtra(DebugInfoActivity.KEY_REMOTE_RESOURCE, remote.location.toString())
}
}
// to make the PendingIntent unique
contentIntent.data = Uri.parse("uri://${javaClass.name}/${localCollection.uniqueId}")
val account = if (localCollection is LocalAddressBook)
localCollection.mainAccount
else
account
contentIntent.data = Uri.parse("davdroid:exception/${e.hashCode()}")
val channel: String
val priority: Int
if (e is IOError) {
if (e is IOException) {
channel = NotificationUtils.CHANNEL_SYNC_IO_ERRORS
priority = NotificationCompat.PRIORITY_LOW
priority = NotificationCompat.PRIORITY_MIN
} else {
channel = NotificationUtils.CHANNEL_SYNC_ERRORS
priority = NotificationCompat.PRIORITY_DEFAULT
......@@ -372,24 +383,41 @@ abstract class SyncManager<out ResourceType: LocalResource, out CollectionType:
.setContentTitle(localCollection.title)
.setContentText(message)
.setStyle(NotificationCompat.BigTextStyle(builder).bigText(message))
.setSubText(account.name)
.setSubText(mainAccount.name)
.setOnlyAlertOnce(true)
.setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT))
.setPriority(priority)
.setCategory(NotificationCompat.CATEGORY_ERROR)
viewItemAction?.let { builder.addAction(it) }
builder.addAction(buildRetryAction())
notificationManager.notify(notificationTag, NotificationUtils.NOTIFY_SYNC_ERROR, builder.build())
}
private fun buildRetryAction(): NotificationCompat.Action {
val retryIntent = Intent(context, DavService::class.java)
retryIntent.action = DavService.ACTION_FORCE_SYNC
retryIntent.data = Uri.parse("contents://").buildUpon()
.authority(authority)
.appendPath(account.type)
.appendPath(account.name)
val syncAuthority: String
val syncAccount: Account
if (authority == ContactsContract.AUTHORITY) {
// if this is a contacts sync, retry syncing all address books of the main account
syncAuthority = context.getString(R.string.address_books_authority)
syncAccount = mainAccount
} else {
syncAuthority = authority
syncAccount = account
}
retryIntent.data = Uri.parse("sync://").buildUpon()
.authority(syncAuthority)
.appendPath(syncAccount.type)
.appendPath(syncAccount.name)
.build()
builder.addAction(android.R.drawable.ic_menu_rotate, context.getString(R.string.sync_error_retry),
PendingIntent.getService(context, 0, retryIntent, PendingIntent.FLAG_UPDATE_CURRENT))
notificationManager.notify(localCollection.uniqueId, NotificationUtils.NOTIFY_SYNC_ERROR, builder.build())
return NotificationCompat.Action(
android.R.drawable.ic_menu_rotate, context.getString(R.string.sync_error_retry),
PendingIntent.getService(context, 0, retryIntent, PendingIntent.FLAG_UPDATE_CURRENT))
}
private fun buildViewItemAction(local: LocalResource): NotificationCompat.Action? {
......
......@@ -164,8 +164,9 @@ class TasksSyncManager(
syncResult.stats.numUpdates++
} else {
Logger.log.info("Adding $fileName to local task list")
val newTask = LocalTask(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)
newTask.add()
useLocal(LocalTask(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT), {
it.add()
})
syncResult.stats.numInserts++
}
})
......
......@@ -46,7 +46,7 @@ object NotificationUtils {
nm.createNotificationChannelGroup(syncChannelGroup)
val syncChannels = arrayOf(
NotificationChannel(CHANNEL_SYNC_ERRORS, context.getString(R.string.notification_channel_sync_errors), NotificationManager.IMPORTANCE_DEFAULT),
NotificationChannel(CHANNEL_SYNC_IO_ERRORS, context.getString(R.string.notification_channel_sync_io_errors), NotificationManager.IMPORTANCE_LOW),
NotificationChannel(CHANNEL_SYNC_IO_ERRORS, context.getString(R.string.notification_channel_sync_io_errors), NotificationManager.IMPORTANCE_MIN),
NotificationChannel(CHANNEL_SYNC_STATUS, context.getString(R.string.notification_channel_sync_status), NotificationManager.IMPORTANCE_MIN)
)
syncChannels.forEach {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment