Loading app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +0 −1 Original line number Diff line number Diff line Loading @@ -267,7 +267,6 @@ class AccountActivity: AppCompatActivity() { thread { cardDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV)) calDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV)) //servicesLoaded.postValue(null) } } Loading app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt +130 −45 Original line number Diff line number Diff line Loading @@ -3,6 +3,8 @@ package at.bitfire.davdroid.ui.account import android.Manifest import android.app.Activity import android.app.Application import android.content.ContentProviderClient import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.database.ContentObserver Loading @@ -18,8 +20,7 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.WorkerThread import androidx.core.content.ContextCompat import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.* import androidx.room.Transaction import at.bitfire.dav4jvm.UrlUtils import at.bitfire.davdroid.Constants Loading @@ -31,6 +32,7 @@ import at.bitfire.davdroid.model.Collection import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.account_caldav_item.view.* import okhttp3.HttpUrl import java.util.logging.Level class WebcalFragment: CollectionsFragment() { Loading @@ -40,9 +42,20 @@ class WebcalFragment: CollectionsFragment() { super.onCreate(savedInstanceState) webcalModel = ViewModelProviders.of(this).get(WebcalModel::class.java) webcalModel.calendarPermission.observe(this, Observer { granted -> if (!granted) requestPermissions(arrayOf(Manifest.permission.READ_CALENDAR), 0) }) webcalModel.subscribedUrls.observe(this, Observer { urls -> Logger.log.log(Level.FINE, "Got calendar list", urls.keys) }) webcalModel.initialize(arguments?.getLong(EXTRA_SERVICE_ID) ?: throw IllegalArgumentException("EXTRA_SERVICE_ID required")) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) = webcalModel.calendarPermission.check() override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = inflater.inflate(R.menu.caldav_actions, menu) Loading Loading @@ -136,72 +149,134 @@ class WebcalFragment: CollectionsFragment() { private var initialized = false private var serviceId: Long = 0 private val workerThread = HandlerThread(javaClass.simpleName) init { workerThread.start() } val workerHandler = Handler(workerThread.looper) private val db = AppDatabase.getInstance(application) private val resolver = application.contentResolver private val calendarPermissions = ContextCompat.checkSelfPermission(application, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED private val calendarProvider = if (calendarPermissions) resolver.acquireContentProviderClient(CalendarContract.AUTHORITY) else null val calendarPermission = CalendarPermission(application) private val calendarProvider = object: MediatorLiveData<ContentProviderClient>() { var havePermission = false private val subscribedUrls = mutableMapOf<HttpUrl, Long>() init { addSource(calendarPermission) { granted -> havePermission = granted if (granted) connect() else disconnect() } } private val workerThread = HandlerThread(javaClass.simpleName) init { workerThread.start() } val workerHandler = Handler(workerThread.looper) override fun onActive() { super.onActive() connect() } val observer = object: ContentObserver(workerHandler) { override fun onChange(selfChange: Boolean) { queryCalendars() fun connect() { if (havePermission && value == null) value = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY) } override fun onInactive() { super.onInactive() disconnect() } fun initialize(dbServiceId: Long) { if (initialized) return initialized = true fun disconnect() { value?.closeCompat() value = null } } val subscribedUrls = object: MediatorLiveData<MutableMap<HttpUrl, Long>>() { var provider: ContentProviderClient? = null var observer: ContentObserver? = null init { addSource(calendarProvider) { provider -> this.provider = provider if (provider != null) { connect() } else unregisterObserver() } } serviceId = dbServiceId override fun onActive() { super.onActive() connect() } if (calendarPermissions) resolver.registerContentObserver(Calendars.CONTENT_URI, false, observer) private fun connect() { unregisterObserver() provider?.let { provider -> val newObserver = object: ContentObserver(workerHandler) { override fun onChange(selfChange: Boolean) { queryCalendars(provider) } } getApplication<Application>().contentResolver.registerContentObserver(Calendars.CONTENT_URI, false, newObserver) observer = newObserver workerHandler.post { queryCalendars() queryCalendars(provider) } } } override fun onCleared() { if (calendarPermissions) resolver.unregisterContentObserver(observer) override fun onInactive() { super.onInactive() unregisterObserver() } calendarProvider?.closeCompat() private fun unregisterObserver() { observer?.let { application.contentResolver.unregisterContentObserver(it) observer = null } } @WorkerThread @Transaction private fun queryCalendars() { private fun queryCalendars(provider: ContentProviderClient) { // query subscribed URLs from Android calendar list subscribedUrls.clear() calendarProvider?.query(Calendars.CONTENT_URI, arrayOf(Calendars._ID, Calendars.NAME),null, null, null)?.use { cursor -> val subscriptions = mutableMapOf<HttpUrl, Long>() provider.query(Calendars.CONTENT_URI, arrayOf(Calendars._ID, Calendars.NAME),null, null, null)?.use { cursor -> while (cursor.moveToNext()) HttpUrl.parse(cursor.getString(1))?.let { url -> subscribedUrls[url] = cursor.getLong(0) subscriptions[url] = cursor.getLong(0) } } // update "sync" field in database accordingly (will update UI) db.collectionDao().getByServiceAndType(serviceId, Collection.TYPE_WEBCAL).forEach { webcal -> val newSync = subscribedUrls.keys val newSync = subscriptions.keys .any { webcal.source?.let { source -> UrlUtils.equals(source, it) } ?: false } if (newSync != webcal.sync) db.collectionDao().update(webcal.copy(sync = newSync)) } postValue(subscriptions) } } fun initialize(dbServiceId: Long) { if (initialized) return initialized = true serviceId = dbServiceId calendarPermission.check() } fun unsubscribe(webcal: Collection) { workerHandler.post { subscribedUrls[webcal.source]?.let { id -> subscribedUrls.value?.get(webcal.source)?.let { id -> // delete subscription from Android calendar list calendarProvider?.delete(Calendars.CONTENT_URI, calendarProvider.value?.delete(Calendars.CONTENT_URI, "${Calendars._ID}=?", arrayOf(id.toString())) } } Loading @@ -209,4 +284,14 @@ class WebcalFragment: CollectionsFragment() { } class CalendarPermission(val context: Context): LiveData<Boolean>() { init { check() } fun check() { value = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED } } } No newline at end of file Loading
app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +0 −1 Original line number Diff line number Diff line Loading @@ -267,7 +267,6 @@ class AccountActivity: AppCompatActivity() { thread { cardDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV)) calDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV)) //servicesLoaded.postValue(null) } } Loading
app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt +130 −45 Original line number Diff line number Diff line Loading @@ -3,6 +3,8 @@ package at.bitfire.davdroid.ui.account import android.Manifest import android.app.Activity import android.app.Application import android.content.ContentProviderClient import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.database.ContentObserver Loading @@ -18,8 +20,7 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.WorkerThread import androidx.core.content.ContextCompat import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.* import androidx.room.Transaction import at.bitfire.dav4jvm.UrlUtils import at.bitfire.davdroid.Constants Loading @@ -31,6 +32,7 @@ import at.bitfire.davdroid.model.Collection import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.account_caldav_item.view.* import okhttp3.HttpUrl import java.util.logging.Level class WebcalFragment: CollectionsFragment() { Loading @@ -40,9 +42,20 @@ class WebcalFragment: CollectionsFragment() { super.onCreate(savedInstanceState) webcalModel = ViewModelProviders.of(this).get(WebcalModel::class.java) webcalModel.calendarPermission.observe(this, Observer { granted -> if (!granted) requestPermissions(arrayOf(Manifest.permission.READ_CALENDAR), 0) }) webcalModel.subscribedUrls.observe(this, Observer { urls -> Logger.log.log(Level.FINE, "Got calendar list", urls.keys) }) webcalModel.initialize(arguments?.getLong(EXTRA_SERVICE_ID) ?: throw IllegalArgumentException("EXTRA_SERVICE_ID required")) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) = webcalModel.calendarPermission.check() override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = inflater.inflate(R.menu.caldav_actions, menu) Loading Loading @@ -136,72 +149,134 @@ class WebcalFragment: CollectionsFragment() { private var initialized = false private var serviceId: Long = 0 private val workerThread = HandlerThread(javaClass.simpleName) init { workerThread.start() } val workerHandler = Handler(workerThread.looper) private val db = AppDatabase.getInstance(application) private val resolver = application.contentResolver private val calendarPermissions = ContextCompat.checkSelfPermission(application, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED private val calendarProvider = if (calendarPermissions) resolver.acquireContentProviderClient(CalendarContract.AUTHORITY) else null val calendarPermission = CalendarPermission(application) private val calendarProvider = object: MediatorLiveData<ContentProviderClient>() { var havePermission = false private val subscribedUrls = mutableMapOf<HttpUrl, Long>() init { addSource(calendarPermission) { granted -> havePermission = granted if (granted) connect() else disconnect() } } private val workerThread = HandlerThread(javaClass.simpleName) init { workerThread.start() } val workerHandler = Handler(workerThread.looper) override fun onActive() { super.onActive() connect() } val observer = object: ContentObserver(workerHandler) { override fun onChange(selfChange: Boolean) { queryCalendars() fun connect() { if (havePermission && value == null) value = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY) } override fun onInactive() { super.onInactive() disconnect() } fun initialize(dbServiceId: Long) { if (initialized) return initialized = true fun disconnect() { value?.closeCompat() value = null } } val subscribedUrls = object: MediatorLiveData<MutableMap<HttpUrl, Long>>() { var provider: ContentProviderClient? = null var observer: ContentObserver? = null init { addSource(calendarProvider) { provider -> this.provider = provider if (provider != null) { connect() } else unregisterObserver() } } serviceId = dbServiceId override fun onActive() { super.onActive() connect() } if (calendarPermissions) resolver.registerContentObserver(Calendars.CONTENT_URI, false, observer) private fun connect() { unregisterObserver() provider?.let { provider -> val newObserver = object: ContentObserver(workerHandler) { override fun onChange(selfChange: Boolean) { queryCalendars(provider) } } getApplication<Application>().contentResolver.registerContentObserver(Calendars.CONTENT_URI, false, newObserver) observer = newObserver workerHandler.post { queryCalendars() queryCalendars(provider) } } } override fun onCleared() { if (calendarPermissions) resolver.unregisterContentObserver(observer) override fun onInactive() { super.onInactive() unregisterObserver() } calendarProvider?.closeCompat() private fun unregisterObserver() { observer?.let { application.contentResolver.unregisterContentObserver(it) observer = null } } @WorkerThread @Transaction private fun queryCalendars() { private fun queryCalendars(provider: ContentProviderClient) { // query subscribed URLs from Android calendar list subscribedUrls.clear() calendarProvider?.query(Calendars.CONTENT_URI, arrayOf(Calendars._ID, Calendars.NAME),null, null, null)?.use { cursor -> val subscriptions = mutableMapOf<HttpUrl, Long>() provider.query(Calendars.CONTENT_URI, arrayOf(Calendars._ID, Calendars.NAME),null, null, null)?.use { cursor -> while (cursor.moveToNext()) HttpUrl.parse(cursor.getString(1))?.let { url -> subscribedUrls[url] = cursor.getLong(0) subscriptions[url] = cursor.getLong(0) } } // update "sync" field in database accordingly (will update UI) db.collectionDao().getByServiceAndType(serviceId, Collection.TYPE_WEBCAL).forEach { webcal -> val newSync = subscribedUrls.keys val newSync = subscriptions.keys .any { webcal.source?.let { source -> UrlUtils.equals(source, it) } ?: false } if (newSync != webcal.sync) db.collectionDao().update(webcal.copy(sync = newSync)) } postValue(subscriptions) } } fun initialize(dbServiceId: Long) { if (initialized) return initialized = true serviceId = dbServiceId calendarPermission.check() } fun unsubscribe(webcal: Collection) { workerHandler.post { subscribedUrls[webcal.source]?.let { id -> subscribedUrls.value?.get(webcal.source)?.let { id -> // delete subscription from Android calendar list calendarProvider?.delete(Calendars.CONTENT_URI, calendarProvider.value?.delete(Calendars.CONTENT_URI, "${Calendars._ID}=?", arrayOf(id.toString())) } } Loading @@ -209,4 +284,14 @@ class WebcalFragment: CollectionsFragment() { } class CalendarPermission(val context: Context): LiveData<Boolean>() { init { check() } fun check() { value = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED } } } No newline at end of file