1
0
mirror of https://github.com/etesync/android synced 2024-12-22 22:58:29 +00:00

Merge: add Tasks.org tasks provider integration

Merge of #122
This commit is contained in:
Tom Hacohen 2020-08-07 09:04:27 +03:00
commit dc3db43d70
25 changed files with 296 additions and 126 deletions

View File

@ -110,6 +110,19 @@
android:resource="@xml/sync_tasks"/>
</service>
<service
android:name=".syncadapter.TasksOrgSyncAdapterService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_tasks_org"/>
</service>
<!-- Address book account -->
<service

View File

@ -24,6 +24,7 @@ import android.provider.ContactsContract
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.AndroidCalendar
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.*
@ -33,6 +34,7 @@ import com.etesync.syncadapter.ui.AccountsActivity
import com.etesync.syncadapter.utils.HintManager
import com.etesync.syncadapter.utils.LanguageUtils
import com.etesync.syncadapter.utils.NotificationUtils
import com.etesync.syncadapter.utils.TaskProviderHandling
import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource
import io.requery.meta.EntityModel
@ -85,8 +87,10 @@ class App : Application() {
tasksFilter.addDataScheme("package")
registerReceiver(PackageChangedReceiver(), tasksFilter)
// check whether a tasks app is currently installed
PackageChangedReceiver.updateTaskSync(this@App)
OPENTASK_PROVIDERS.forEach {
// check whether a tasks app is currently installed
TaskProviderHandling.updateTaskSync(this@App, it)
}
}
}
@ -274,6 +278,7 @@ class App : Application() {
val OVERRIDE_PROXY = "overrideProxy"
val OVERRIDE_PROXY_HOST = "overrideProxyHost"
val OVERRIDE_PROXY_PORT = "overrideProxyPort"
val PREFER_TASKSORG = "preferTasksOrg"
val FORCE_LANGUAGE = "forceLanguage"
val CHANGE_NOTIFICATION = "show_change_notification"

View File

@ -8,47 +8,21 @@
package com.etesync.syncadapter
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import at.bitfire.ical4android.TaskProvider
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.resource.LocalTaskList
import com.etesync.syncadapter.utils.TaskProviderHandling.Companion.updateTaskSync
class PackageChangedReceiver : BroadcastReceiver() {
@SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_PACKAGE_ADDED == intent.action || Intent.ACTION_PACKAGE_FULLY_REMOVED == intent.action)
updateTaskSync(context)
}
companion object {
internal fun updateTaskSync(context: Context) {
val tasksInstalled = LocalTaskList.tasksProviderAvailable(context)
Logger.log.info("Package (un)installed; OpenTasks provider now available = $tasksInstalled")
for (account in AccountManager.get(context).getAccountsByType(App.accountType)) {
val settings = AccountSettings(context, account)
val calendarSyncInterval = settings.getSyncInterval(CalendarContract.AUTHORITY)
if (tasksInstalled) {
if (calendarSyncInterval == null) {
// do nothing atm
} else if (ContentResolver.getIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority) <= 0) {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1)
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, calendarSyncInterval)
}
} else {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0)
}
if (Intent.ACTION_PACKAGE_ADDED == intent.action || Intent.ACTION_PACKAGE_FULLY_REMOVED == intent.action) {
TaskProvider.OPENTASK_PROVIDERS.forEach {
updateTaskSync(context, it)
}
}
}
}

View File

@ -18,6 +18,7 @@ import at.bitfire.ical4android.AndroidTaskList
import at.bitfire.ical4android.AndroidTaskListFactory
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.ProviderName
import com.etesync.syncadapter.model.JournalEntity
import org.dmfs.tasks.contract.TaskContract.TaskLists
import org.dmfs.tasks.contract.TaskContract.Tasks
@ -30,12 +31,12 @@ class LocalTaskList private constructor(
companion object {
val defaultColor = -0x743cb6 // light green 500
fun tasksProviderAvailable(context: Context): Boolean {
fun tasksProviderAvailable(context: Context, provider: ProviderName): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return context.packageManager.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
return context.packageManager.resolveContentProvider(provider.authority, 0) != null
else {
try {
TaskProvider.acquire(context, TaskProvider.ProviderName.OpenTasks)?.use {
TaskProvider.acquire(context, provider)?.use {
return true
}
} catch (e: Exception) {

View File

@ -2,16 +2,21 @@ package com.etesync.syncadapter.syncadapter
import android.accounts.Account
import android.content.ContentResolver
import android.content.Context
import android.os.Bundle
import android.provider.CalendarContract
import at.bitfire.ical4android.TaskProvider
import com.etesync.syncadapter.App
import com.etesync.syncadapter.utils.TaskProviderHandling
fun requestSync(account: Account?) {
val authorities = arrayOf(App.addressBooksAuthority, CalendarContract.AUTHORITY, TaskProvider.ProviderName.OpenTasks.authority)
fun requestSync(context: Context, account: Account?) {
val authorities = arrayOf(
App.addressBooksAuthority,
CalendarContract.AUTHORITY,
TaskProviderHandling.getWantedTaskSyncProvider(context)?.authority
)
for (authority in authorities) {
for (authority in authorities.filterNotNull()) {
val extras = Bundle()
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) // manual sync
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) // run immediately (don't queue)

View File

@ -0,0 +1,20 @@
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter.syncadapter
import at.bitfire.ical4android.TaskProvider
/**
* Synchronization manager for CalDAV collections; handles tasks ({@code VTODO}).
*/
class TasksOrgSyncAdapterService: SyncAdapterService() {
override fun syncAdapter() =
TasksSyncAdapterService.TasksSyncAdapter(this, TaskProvider.ProviderName.TasksOrg)
}

View File

@ -17,6 +17,7 @@ import android.os.Build
import android.os.Bundle
import at.bitfire.ical4android.AndroidTaskList
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.ProviderName
import com.etesync.syncadapter.AccountSettings
import com.etesync.syncadapter.App
import com.etesync.syncadapter.Constants
@ -35,18 +36,18 @@ import java.util.*
*/
class TasksSyncAdapterService: SyncAdapterService() {
override fun syncAdapter() = TasksSyncAdapter(this)
override fun syncAdapter() = TasksSyncAdapter(this, ProviderName.OpenTasks)
class TasksSyncAdapter(
context: Context
context: Context,
private val name: ProviderName
): SyncAdapter(context) {
override val syncErrorTitle = R.string.sync_error_tasks
override val notificationManager = SyncNotification(context, "journals-tasks", Constants.NOTIFICATION_TASK_SYNC)
override fun onPerformSyncDo(account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult) {
val taskProvider = TaskProvider.fromProviderClient(context, provider)
val taskProvider = TaskProvider.fromProviderClient(context, provider, name)
// make sure account can be seen by OpenTasks
if (Build.VERSION.SDK_INT >= 26)

View File

@ -26,6 +26,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.*
import com.etesync.journalmanager.Crypto
@ -285,7 +286,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
listTaskDAV!!.adapter = adapter
listTaskDAV!!.onItemClickListener = onItemClickListener
if (!packageInstalled(this, openTasksPackage)) {
if (!packageInstalled(this, tasksOrgPackage) && !packageInstalled(this, openTasksPackage)) {
val opentasksWarning = findViewById<View>(R.id.taskdav_opentasks_warning)
opentasksWarning.visibility = View.VISIBLE
}
@ -378,7 +379,9 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
info.taskdav = AccountInfo.ServiceInfo()
info.taskdav!!.id = id
info.taskdav!!.refreshing = davService != null && davService!!.isRefreshing(id) ||
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority)
OPENTASK_PROVIDERS.any {
ContentResolver.isSyncActive(account, it.authority)
}
info.taskdav!!.journals = JournalEntity.getJournals(data, serviceEntity)
}
}
@ -470,7 +473,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
private fun requestSync() {
requestSync(account)
requestSync(applicationContext, account)
Snackbar.make(findViewById(R.id.parent), R.string.account_synchronizing_now, Snackbar.LENGTH_LONG).show()
}

View File

@ -23,6 +23,7 @@ import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import androidx.preference.*
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
import com.etesync.syncadapter.R
@ -121,7 +122,9 @@ class AccountSettingsActivity : BaseActivity() {
val newInterval = java.lang.Long.parseLong(newValue as String)
settings.setSyncInterval(App.addressBooksAuthority, newInterval)
settings.setSyncInterval(CalendarContract.AUTHORITY, newInterval)
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, newInterval)
OPENTASK_PROVIDERS.forEach {
settings.setSyncInterval(it.authority, newInterval)
}
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
false
}

View File

@ -43,6 +43,8 @@ class AppSettingsActivity : BaseActivity() {
internal lateinit var dbHelper: ServiceDB.OpenHelper
internal lateinit var settings: Settings
internal lateinit var prefPreferTasksOrg: SwitchPreferenceCompat
internal lateinit var prefResetHints: Preference
internal lateinit var prefOverrideProxy: SwitchPreferenceCompat
internal lateinit var prefDistrustSystemCerts: SwitchPreferenceCompat
@ -86,6 +88,14 @@ class AppSettingsActivity : BaseActivity() {
true
}
prefPreferTasksOrg = findPreference("prefer_tasksorg") as SwitchPreferenceCompat
prefPreferTasksOrg.isChecked = context!!.defaultSharedPreferences.getBoolean(App.PREFER_TASKSORG, false)
prefPreferTasksOrg.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
context!!.defaultSharedPreferences.edit().putBoolean(App.PREFER_TASKSORG, newValue as Boolean).apply()
Snackbar.make(view!!, getString(R.string.app_settings_prefer_tasksorg_snack), Snackbar.LENGTH_LONG).show()
true
}
prefProxyHost = findPreference("proxy_host") as EditTextPreference
val proxyHost = settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT)
prefProxyHost.text = proxyHost

View File

@ -134,7 +134,7 @@ open class ChangeEncryptionPasswordActivity : BaseActivity() {
this@ChangeEncryptionPasswordActivity.finish()
}.show()
requestSync(account)
requestSync(applicationContext, account)
}
} catch (e: Exception) {
uiThread {

View File

@ -19,7 +19,6 @@ import androidx.fragment.app.DialogFragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import at.bitfire.ical4android.TaskProvider
import com.etesync.syncadapter.*
import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions
@ -27,6 +26,7 @@ import com.etesync.journalmanager.JournalManager
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.JournalModel
import com.etesync.syncadapter.utils.TaskProviderHandling
import okhttp3.HttpUrl
class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Exception> {
@ -84,15 +84,16 @@ class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks
override fun loadInBackground(): Exception? {
try {
var authority: String = ""
val data = (context.applicationContext as App).data
val context = context.applicationContext
val data = (context as App).data
// 1. find service ID
when (info.enumType){
CollectionInfo.Type.ADDRESS_BOOK -> authority = App.addressBooksAuthority
CollectionInfo.Type.CALENDAR -> authority = CalendarContract.AUTHORITY
CollectionInfo.Type.TASKS -> authority = TaskProvider.ProviderName.OpenTasks.authority
val authority = when (info.enumType){
CollectionInfo.Type.ADDRESS_BOOK -> App.addressBooksAuthority
CollectionInfo.Type.CALENDAR -> CalendarContract.AUTHORITY
CollectionInfo.Type.TASKS ->
TaskProviderHandling.getWantedTaskSyncProvider(context)?.authority
else -> null
}
val serviceEntity = JournalModel.Service.fetchOrCreate(data, account.name, info.enumType)
@ -127,7 +128,7 @@ class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks
journalManager.update(journal)
}
requestSync(authority)
authority?.let { requestSync(it) }
} catch (e: IllegalStateException) {
return e
} catch (e: Exceptions.HttpException) {

View File

@ -25,6 +25,7 @@ import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.TaskProvider.ProviderName
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
@ -156,7 +157,7 @@ class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String>
.append(if (powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) "yes" else "no")
.append("\n")
// permissions
for (permission in arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, PermissionsActivity.PERMISSION_READ_TASKS, PermissionsActivity.PERMISSION_WRITE_TASKS))
for (permission in arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR) + ProviderName.OpenTasks.permissions + ProviderName.TasksOrg.permissions)
report.append(permission).append(" permission: ")
.append(if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) "granted" else "denied")
.append("\n")
@ -169,7 +170,7 @@ class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String>
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
try {
val settings = AccountSettings(context, acct)
report.append("Account: ").append(acct.name).append("\n" + " Address book sync. interval: ").append(syncStatus(settings, App.addressBooksAuthority)).append("\n" + " Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + " OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" + " WiFi only: ").append(settings.syncWifiOnly)
report.append("Account: ").append(acct.name).append("\n" + " Address book sync. interval: ").append(syncStatus(settings, App.addressBooksAuthority)).append("\n" + " Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + " OpenTasks sync. interval: ").append(syncStatus(settings, ProviderName.OpenTasks.authority)).append("\n" + " Tasks.org sync. interval: ").append(syncStatus(settings, ProviderName.TasksOrg.authority)).append("\n" + " WiFi only: ").append(settings.syncWifiOnly)
if (settings.syncWifiOnlySSID != null)
report.append(", SSID: ").append(settings.syncWifiOnlySSID)
report.append("\n [CardDAV] Contact group method: ").append(settings.groupMethod)

View File

@ -29,6 +29,7 @@ import com.etesync.journalmanager.model.SyncEntry
import com.etesync.syncadapter.resource.*
import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment.Companion.setJournalEntryView
import com.etesync.syncadapter.utils.EventEmailInvitation
import com.etesync.syncadapter.utils.TaskProviderHandling
import com.google.android.material.tabs.TabLayout
import ezvcard.util.PartialDate
import org.jetbrains.anko.doAsync
@ -116,15 +117,17 @@ class JournalItemActivity : BaseActivity(), Refreshable {
}
}
CollectionInfo.Type.TASKS -> {
val provider = TaskProvider.acquire(this, TaskProvider.ProviderName.OpenTasks)!!
val localTaskList = LocalTaskList.findByName(account, provider, LocalTaskList.Factory, info.uid!!)!!
val task = Task.tasksFromReader(StringReader(syncEntry.content))[0]
var localTask = localTaskList.findByUid(task.uid!!)
if (localTask != null) {
localTask.updateAsDirty(task)
} else {
localTask = LocalTask(localTaskList, task, task.uid, null)
localTask.addAsDirty()
TaskProviderHandling.getWantedTaskSyncProvider(applicationContext)?.let {
val provider = TaskProvider.acquire(this, it)!!
val localTaskList = LocalTaskList.findByName(account, provider, LocalTaskList.Factory, info.uid!!)!!
val task = Task.tasksFromReader(StringReader(syncEntry.content))[0]
var localTask = localTaskList.findByUid(task.uid!!)
if (localTask != null) {
localTask.updateAsDirty(task)
} else {
localTask = LocalTask(localTaskList, task, task.uid, null)
localTask.addAsDirty()
}
}
}
CollectionInfo.Type.ADDRESS_BOOK -> {

View File

@ -13,8 +13,11 @@ import android.app.Activity
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.View
import androidx.annotation.IdRes
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import at.bitfire.ical4android.TaskProvider.ProviderName
import com.etesync.syncadapter.Constants
import com.etesync.syncadapter.R
import com.etesync.syncadapter.resource.LocalTaskList
@ -38,16 +41,10 @@ class PermissionsActivity : BaseActivity() {
val noContactsPermissions = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED
findViewById<View>(R.id.contacts_permissions).visibility = if (noContactsPermissions) View.VISIBLE else View.GONE
val noTaskPermissions: Boolean
if (LocalTaskList.tasksProviderAvailable(this)) {
noTaskPermissions = ActivityCompat.checkSelfPermission(this, PERMISSION_READ_TASKS) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, PERMISSION_WRITE_TASKS) != PackageManager.PERMISSION_GRANTED
findViewById<View>(R.id.opentasks_permissions).visibility = if (noTaskPermissions) View.VISIBLE else View.GONE
} else {
findViewById<View>(R.id.opentasks_permissions).visibility = View.GONE
noTaskPermissions = false
}
val needOpenTaskPermissions = setupPermissions(ProviderName.OpenTasks, R.id.opentasks_permissions)
val needTasksOrgPermissions = setupPermissions(ProviderName.TasksOrg, R.id.tasksorg_permissions)
if (!noCalendarPermissions && !noContactsPermissions && !noTaskPermissions) {
if (!noCalendarPermissions && !noContactsPermissions && !(needOpenTaskPermissions || needTasksOrgPermissions)) {
val nm = NotificationManagerCompat.from(this)
nm.cancel(Constants.NOTIFICATION_PERMISSIONS)
@ -55,6 +52,15 @@ class PermissionsActivity : BaseActivity() {
}
}
private fun setupPermissions(provider: ProviderName, @IdRes id: Int): Boolean {
val providerAvailable = LocalTaskList.tasksProviderAvailable(this, provider)
val hasPermissions = providerAvailable && provider.permissions.all {
ActivityCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
}
findViewById<View>(id).visibility = if (hasPermissions) View.GONE else View.VISIBLE
return providerAvailable && !hasPermissions
}
fun requestCalendarPermissions(v: View) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR), 0)
}
@ -64,7 +70,11 @@ class PermissionsActivity : BaseActivity() {
}
fun requestOpenTasksPermissions(v: View) {
ActivityCompat.requestPermissions(this, arrayOf(PERMISSION_READ_TASKS, PERMISSION_WRITE_TASKS), 0)
ActivityCompat.requestPermissions(this, ProviderName.OpenTasks.permissions, 0)
}
fun requestTasksOrgPermissions(v: View) {
ActivityCompat.requestPermissions(this, ProviderName.TasksOrg.permissions, 0)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
@ -75,11 +85,8 @@ class PermissionsActivity : BaseActivity() {
companion object {
private val REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124
val PERMISSION_READ_TASKS = "org.dmfs.permission.READ_TASKS"
val PERMISSION_WRITE_TASKS = "org.dmfs.permission.WRITE_TASKS"
fun requestAllPermissions(activity: Activity) {
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, PERMISSION_READ_TASKS, PERMISSION_WRITE_TASKS), REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) + OPENTASK_PROVIDERS.flatMap { it.permissions.toList() }, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)
}
}
}

View File

@ -38,6 +38,7 @@ import com.etesync.syncadapter.ui.importlocal.ImportActivity
import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment
import com.etesync.syncadapter.utils.HintManager
import com.etesync.syncadapter.utils.ShowcaseBuilder
import com.etesync.syncadapter.utils.TaskProviderHandling
import com.google.android.material.floatingactionbutton.FloatingActionButton
import tourguide.tourguide.ToolTip
import java.io.FileNotFoundException
@ -212,7 +213,9 @@ class ViewCollectionActivity : BaseActivity(), Refreshable {
}
CollectionInfo.Type.TASKS -> {
try {
val providerClient = TaskProvider.acquire(this@ViewCollectionActivity, TaskProvider.ProviderName.OpenTasks)
val providerClient = TaskProviderHandling.getWantedTaskSyncProvider(this@ViewCollectionActivity)?.let {
TaskProvider.acquire(this@ViewCollectionActivity, it)
}
if (providerClient == null) {
return null
}

View File

@ -15,6 +15,7 @@ import android.provider.CalendarContract
import android.provider.ContactsContract
import androidx.fragment.app.DialogFragment
import at.bitfire.ical4android.*
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import at.bitfire.vcard4android.BatchOperation
import at.bitfire.vcard4android.Contact
import at.bitfire.vcard4android.ContactsStorageException
@ -27,6 +28,7 @@ import com.etesync.syncadapter.resource.*
import com.etesync.syncadapter.syncadapter.ContactsSyncManager
import com.etesync.syncadapter.ui.Refreshable
import com.etesync.syncadapter.ui.importlocal.ResultFragment.ImportResult
import com.etesync.syncadapter.utils.TaskProviderHandling
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
@ -282,40 +284,46 @@ class ImportFragment : DialogFragment() {
finishParsingFile(tasks.size)
val provider = TaskProvider.acquire(context, TaskProvider.ProviderName.OpenTasks)
if (provider == null) {
result.e = Exception("Failed to acquire tasks content provider.")
return result
}
val localTaskList: LocalTaskList?
try {
localTaskList = LocalTaskList.findByName(account, provider, LocalTaskList.Factory, info.uid!!)
if (localTaskList == null) {
throw FileNotFoundException("Failed to load local resource.")
}
} catch (e: FileNotFoundException) {
Logger.log.info("Fail" + e.localizedMessage)
result.e = e
return result
}
for (task in tasks) {
try {
var localTask = localTaskList.findByUid(task.uid!!)
if (localTask != null) {
localTask.updateAsDirty(task)
result.updated++
} else {
localTask = LocalTask(localTaskList, task, task.uid, null)
localTask.addAsDirty()
result.added++
val provider = TaskProviderHandling.getWantedTaskSyncProvider(requireContext())
.let {
if (it == null) {
result.e = Exception("Failed to acquire tasks content provider.")
null
} else {
TaskProvider.acquire(context, it)
}
}
} catch (e: CalendarStorageException) {
e.printStackTrace()
provider?.let {
val localTaskList: LocalTaskList?
try {
localTaskList = LocalTaskList.findByName(account, it, LocalTaskList.Factory, info.uid!!)
if (localTaskList == null) {
throw FileNotFoundException("Failed to load local resource.")
}
} catch (e: FileNotFoundException) {
Logger.log.info("Fail" + e.localizedMessage)
result.e = e
return result
}
entryProcessed()
for (task in tasks) {
try {
var localTask = localTaskList.findByUid(task.uid!!)
if (localTask != null) {
localTask.updateAsDirty(task)
result.updated++
} else {
localTask = LocalTask(localTaskList, task, task.uid, null)
localTask.addAsDirty()
result.added++
}
} catch (e: CalendarStorageException) {
e.printStackTrace()
}
entryProcessed()
}
}
} else if (info.enumType == CollectionInfo.Type.ADDRESS_BOOK) {
val uidToLocalId = HashMap<String?, Long>()

View File

@ -21,6 +21,7 @@ import android.provider.CalendarContract
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import com.etesync.syncadapter.*
import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions
@ -32,6 +33,7 @@ import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.resource.LocalTaskList
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
import com.etesync.syncadapter.utils.AndroidCompat
import com.etesync.syncadapter.utils.TaskProviderHandling
import okhttp3.HttpUrl
import java.util.logging.Level
@ -153,11 +155,10 @@ class SetupEncryptionFragment : DialogFragment() {
// calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL.toLong())
// enable task sync if OpenTasks is installed
// further changes will be handled by PackageChangedReceiver
if (LocalTaskList.tasksProviderAvailable(context!!)) {
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1)
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL.toLong())
OPENTASK_PROVIDERS.forEach {
// enable task sync if OpenTasks is installed
// further changes will be handled by PackageChangedReceiver
TaskProviderHandling.updateTaskSync(context!!, it)
}
} else {
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0)

View File

@ -0,0 +1,56 @@
package com.etesync.syncadapter.utils
import android.accounts.AccountManager
import android.content.ContentResolver
import android.content.Context
import android.provider.CalendarContract
import at.bitfire.ical4android.TaskProvider
import com.etesync.syncadapter.AccountSettings
import com.etesync.syncadapter.App
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.resource.LocalTaskList
import org.jetbrains.anko.defaultSharedPreferences
class TaskProviderHandling {
companion object {
fun getWantedTaskSyncProvider(context: Context): TaskProvider.ProviderName? {
val openTasksAvailable = LocalTaskList.tasksProviderAvailable(context, TaskProvider.ProviderName.OpenTasks)
val tasksOrgAvailable = LocalTaskList.tasksProviderAvailable(context, TaskProvider.ProviderName.TasksOrg)
if (openTasksAvailable && tasksOrgAvailable) {
if (context.defaultSharedPreferences.getBoolean(App.PREFER_TASKSORG, false))
return TaskProvider.ProviderName.TasksOrg
else
return TaskProvider.ProviderName.OpenTasks
} else {
if (openTasksAvailable)
return TaskProvider.ProviderName.OpenTasks
else if (tasksOrgAvailable)
return TaskProvider.ProviderName.TasksOrg
else
return null
}
}
fun updateTaskSync(context: Context, provider: TaskProvider.ProviderName) {
for (account in AccountManager.get(context).getAccountsByType(App.accountType)) {
val settings = AccountSettings(context, account)
val calendarSyncInterval = settings.getSyncInterval(CalendarContract.AUTHORITY)
val wantedProvider = getWantedTaskSyncProvider(context)
val shouldSync = wantedProvider == provider
Logger.log.info("Package (un)installed; Syncing (${shouldSync}) for ${provider.name}")
if (shouldSync) {
if (calendarSyncInterval == null) {
// do nothing atm
} else if (ContentResolver.getIsSyncable(account, provider.authority) <= 0) {
ContentResolver.setIsSyncable(account, provider.authority, 1)
settings.setSyncInterval(provider.authority, calendarSyncInterval)
}
} else {
ContentResolver.setIsSyncable(account, provider.authority, 0)
}
}
}
}
}

View File

@ -77,6 +77,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp"
tools:ignore="UselessParent">
<TextView
@ -98,6 +99,31 @@
</LinearLayout>
<LinearLayout
android:id="@+id/tasksorg_permissions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UselessParent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextView.Heading"
android:text="@string/permissions_tasks_org"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/permissions_tasks_org_details"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/permissions_tasks_org_request"
android:onClick="requestTasksOrgPermissions"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -8,6 +8,9 @@
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/create_tasklist"
android:title="@string/create_tasklist"/>
<item android:id="@+id/install_tasksorg"
android:visible="false"
android:title="@string/install_tasksorg"/>
@ -15,8 +18,4 @@
<item android:id="@+id/install_opentasks"
android:visible="false"
android:title="@string/install_opentasks"/>
<item android:id="@+id/create_tasklist"
android:title="@string/create_tasklist"/>
</menu>

View File

@ -93,6 +93,11 @@
<string name="app_settings_reset_hints">Reset hints</string>
<string name="app_settings_reset_hints_summary">Re-enables hints which have been dismissed previously</string>
<string name="app_settings_reset_hints_success">All hints will be shown again</string>
<string name="app_settings_sync">Sync</string>
<string name="app_settings_prefer_tasksorg">Prefer Tasks.org tasks provider</string>
<string name="app_settings_prefer_tasksorg_on">Will sync tasks with Tasks.org if available</string>
<string name="app_settings_prefer_tasksorg_off">Will sync tasks with OpenTasks if available</string>
<string name="app_settings_prefer_tasksorg_snack">You may need to remove and re-add your account for these changes to take effect.</string>
<string name="app_settings_connection">Connection</string>
<string name="app_settings_override_proxy">Override proxy settings</string>
<string name="app_settings_override_proxy_on">Use custom proxy settings</string>
@ -130,13 +135,13 @@
<string name="account_delete_collection_last_text">Deleting the last collection is not allowed, please create a new one if you\'d like to delete this one.</string>
<string name="account_showcase_view_collection">You can click on an item to view the collection. From there you can view the journal, import, and much more...</string>
<string name="account_click_install_tasks">Click here to install OpenTasks!</string>
<string name="account_tasks_not_showing">* Only OpenTasks lists are shown below</string>
<string name="account_tasks_not_showing">* Not syncing tasks (install a provider)</string>
<string name="show_fingperprint_title">My Fingerprint</string>
<!-- ViewCollection -->
<string name="change_journal_title">Change Journal</string>
<string name="tasks_not_showing">* Log is only shown if OpenTasks is installed. We are working on a fix.</string>
<string name="tasks_not_showing">* Log is only shown if a task provider is installed.</string>
<string name="account_showcase_import">In order to import contacts and calendars into EteSync, you need to click on the menu, and choose \"Import\".</string>
<string name="account_owner">Owner: %s</string>
<string name="members_owner_only">Only the owner of this collection (%s) is allowed to view its members.</string>
@ -196,6 +201,9 @@
<string name="permissions_opentasks">OpenTasks permissions</string>
<string name="permissions_opentasks_details">To synchronize tasks with your local task lists, EteSync needs to access OpenTasks.</string>
<string name="permissions_opentasks_request">Request OpenTasks permissions</string>
<string name="permissions_tasks_org">Tasks.org permissions</string>
<string name="permissions_tasks_org_details">To synchronize tasks with your local task lists, EteSync needs to access Tasks.org.</string>
<string name="permissions_tasks_org_request">Request Tasks.org permissions</string>
<!-- AddAccountActivity -->
<string name="login_title">Add account</string>

View File

@ -26,6 +26,16 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/app_settings_sync">
<SwitchPreferenceCompat
android:key="prefer_tasksorg"
android:title="@string/app_settings_prefer_tasksorg"
android:summaryOn="@string/app_settings_prefer_tasksorg_on"
android:summaryOff="@string/app_settings_prefer_tasksorg_off"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/app_settings_connection">
<SwitchPreferenceCompat

View File

@ -0,0 +1,12 @@
<!--
~ Copyright © Ricki Hirner (bitfire web engineering).
~ All rights reserved. This program and the accompanying materials
~ are made available under the terms of the GNU Public License v3.0
~ which accompanies this distribution, and is available at
~ http://www.gnu.org/licenses/gpl.html
-->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type"
android:contentAuthority="org.tasks.opentasks"
android:supportsUploading="true" />

@ -1 +1 @@
Subproject commit c0459f905571ab2e08e8af9ac40a96200d8cb862
Subproject commit 0a070c7866792756d90be429522127ef9108eff8