Loading app/src/main/java/at/bitfire/davdroid/DavService.kt +50 −40 Original line number Diff line number Diff line Loading @@ -9,11 +9,13 @@ package at.bitfire.davdroid import android.accounts.Account import android.app.IntentService import android.app.PendingIntent import android.content.ContentResolver import android.content.Intent import android.os.Binder import android.os.Bundle import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import at.bitfire.dav4jvm.DavResource Loading @@ -27,9 +29,6 @@ import at.bitfire.davdroid.model.Collection import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.NotificationUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.HttpUrl import okhttp3.OkHttpClient import java.lang.ref.WeakReference Loading @@ -37,7 +36,8 @@ import java.util.* import java.util.logging.Level import kotlin.collections.* class DavService: android.app.Service() { @Suppress("DEPRECATION") class DavService: IntentService("DavService") { companion object { const val ACTION_REFRESH_COLLECTIONS = "refreshCollections" Loading @@ -61,12 +61,23 @@ class DavService: android.app.Service() { } private val runningRefresh = HashSet<Long>() private val refreshingStatusListeners = LinkedList<WeakReference<RefreshingStatusListener>>() /** * List of [Service] IDs for which the collections are currently refreshed */ private val runningRefresh = Collections.synchronizedSet(HashSet<Long>()) /** * Currently registered [RefreshingStatusListener]s, which will be notified * when a collection refresh status changes */ private val refreshingStatusListeners = Collections.synchronizedList(LinkedList<WeakReference<RefreshingStatusListener>>()) @WorkerThread override fun onHandleIntent(intent: Intent?) { if (intent == null) return override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.let { val id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1) when (intent.action) { Loading @@ -75,13 +86,10 @@ class DavService: android.app.Service() { refreshingStatusListeners.forEach { listener -> listener.get()?.onDavRefreshStatusChanged(id, true) } CoroutineScope(Dispatchers.IO).launch { val db = AppDatabase.getInstance(this@DavService) db.runInTransaction { refreshCollections(db, id) } } } ACTION_FORCE_SYNC -> { val uri = intent.data!! Loading @@ -95,9 +103,6 @@ class DavService: android.app.Service() { } } return START_NOT_STICKY } /* BOUND SERVICE PART for communicating with the activities Loading @@ -115,14 +120,17 @@ class DavService: android.app.Service() { fun addRefreshingStatusListener(listener: RefreshingStatusListener, callImmediateIfRunning: Boolean) { refreshingStatusListeners += WeakReference<RefreshingStatusListener>(listener) if (callImmediateIfRunning) runningRefresh.forEach { id -> listener.onDavRefreshStatusChanged(id, true) } synchronized(runningRefresh) { for (id in runningRefresh) listener.onDavRefreshStatusChanged(id, true) } } fun removeRefreshingStatusListener(listener: RefreshingStatusListener) { val iter = refreshingStatusListeners.iterator() while (iter.hasNext()) { val item = iter.next().get() if (listener == item) if (item == listener || item == null) iter.remove() } } Loading Loading @@ -355,6 +363,7 @@ class DavService: android.app.Service() { } } db.runInTransaction { saveHomesets() // use refHomeSet (if available) to determine homeset ID Loading @@ -363,6 +372,7 @@ class DavService: android.app.Service() { collection.homeSetId = homeSet.id } saveCollections() } } catch(e: InvalidAccountException) { Logger.log.log(Level.SEVERE, "Invalid account", e) Loading app/src/main/java/at/bitfire/davdroid/model/ServiceDao.kt +2 −4 Original line number Diff line number Diff line package at.bitfire.davdroid.model import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy Loading @@ -12,14 +13,11 @@ interface ServiceDao { fun getByAccountAndType(accountName: String, type: String): Service? @Query("SELECT id FROM service WHERE accountName=:accountName AND type=:type") fun getIdByAccountAndType(accountName: String, type: String): Long? fun getIdByAccountAndType(accountName: String, type: String): LiveData<Long> @Query("SELECT * FROM service WHERE id=:id") fun get(id: Long): Service? @Query("SELECT * FROM service WHERE type=:type") fun getByType(type: String): List<Service> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplace(service: Service): Long Loading app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +25 −7 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package at.bitfire.davdroid.ui.account import android.accounts.Account import android.accounts.AccountManager import android.accounts.OnAccountsUpdateListener import android.app.Application import android.content.Intent import android.os.Build Loading Loading @@ -53,6 +54,11 @@ class AccountActivity: AppCompatActivity() { setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) model.accountExists.observe(this, Observer { accountExists -> if (!accountExists) finish() }) tab_layout.setupWithViewPager(view_pager) val tabsAdapter = TabsAdapter(this) view_pager.adapter = tabsAdapter Loading Loading @@ -172,6 +178,7 @@ class AccountActivity: AppCompatActivity() { idxWebcal = null } // reflect changes in UI notifyDataSetChanged() } Loading Loading @@ -208,6 +215,9 @@ class AccountActivity: AppCompatActivity() { throw IllegalArgumentException() } // required to reload all fragments override fun getItemPosition(obj: Any) = POSITION_NONE override fun getPageTitle(position: Int): String = when (position) { idxCardDav -> activity.getString(R.string.account_carddav) Loading @@ -224,7 +234,7 @@ class AccountActivity: AppCompatActivity() { class Model( application: Application, val account: Account ): AndroidViewModel(application) { ): AndroidViewModel(application), OnAccountsUpdateListener { class Factory( val application: Application, Loading @@ -236,20 +246,20 @@ class AccountActivity: AppCompatActivity() { } private val db = AppDatabase.getInstance(application) val accountManager = AccountManager.get(application) val accountSettings by lazy { AccountSettings(getApplication(), account) } val cardDavService = MutableLiveData<Long>() val calDavService = MutableLiveData<Long>() val accountExists = MutableLiveData<Boolean>() val cardDavService = db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV) val calDavService = db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV) val showOnlyPersonal = MutableLiveData<Boolean>() val showOnlyPersonal_writable = MutableLiveData<Boolean>() init { accountManager.addOnAccountsUpdatedListener(this, null, true) viewModelScope.launch(Dispatchers.IO) { cardDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV)) calDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV)) accountSettings.getShowOnlyPersonal().let { (value, locked) -> showOnlyPersonal.postValue(value) showOnlyPersonal_writable.postValue(locked) Loading @@ -257,6 +267,14 @@ class AccountActivity: AppCompatActivity() { } } override fun onCleared() { accountManager.removeOnAccountsUpdatedListener(this) } override fun onAccountsUpdated(accounts: Array<out Account>) { accountExists.postValue(accounts.contains(account)) } fun toggleReadOnly(item: Collection) { viewModelScope.launch(Dispatchers.IO + NonCancellable) { val newItem = item.copy(forceReadOnly = !item.forceReadOnly) Loading app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt +6 −7 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ import android.provider.CalendarContract import android.provider.ContactsContract import android.view.* import android.widget.PopupMenu import androidx.annotation.WorkerThread import androidx.annotation.AnyThread import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels Loading @@ -24,12 +24,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import at.bitfire.davdroid.Constants import at.bitfire.davdroid.DavService import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.model.AppDatabase import at.bitfire.davdroid.model.Collection import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import kotlinx.android.synthetic.main.account_collections.* import kotlinx.coroutines.Dispatchers Loading Loading @@ -314,18 +312,19 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList context.startService(intent) } @WorkerThread @AnyThread override fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean) { if (id == serviceId.value) isRefreshing.postValue(refreshing) } @AnyThread override fun onStatusChanged(which: Int) { viewModelScope.launch(Dispatchers.Default) { checkSyncStatus() } } @AnyThread @Synchronized private fun checkSyncStatus() { if (collectionType == Collection.TYPE_ADDRESSBOOK) { val mainAuthority = context.getString(R.string.address_books_authority) Loading Loading
app/src/main/java/at/bitfire/davdroid/DavService.kt +50 −40 Original line number Diff line number Diff line Loading @@ -9,11 +9,13 @@ package at.bitfire.davdroid import android.accounts.Account import android.app.IntentService import android.app.PendingIntent import android.content.ContentResolver import android.content.Intent import android.os.Binder import android.os.Bundle import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import at.bitfire.dav4jvm.DavResource Loading @@ -27,9 +29,6 @@ import at.bitfire.davdroid.model.Collection import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.NotificationUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.HttpUrl import okhttp3.OkHttpClient import java.lang.ref.WeakReference Loading @@ -37,7 +36,8 @@ import java.util.* import java.util.logging.Level import kotlin.collections.* class DavService: android.app.Service() { @Suppress("DEPRECATION") class DavService: IntentService("DavService") { companion object { const val ACTION_REFRESH_COLLECTIONS = "refreshCollections" Loading @@ -61,12 +61,23 @@ class DavService: android.app.Service() { } private val runningRefresh = HashSet<Long>() private val refreshingStatusListeners = LinkedList<WeakReference<RefreshingStatusListener>>() /** * List of [Service] IDs for which the collections are currently refreshed */ private val runningRefresh = Collections.synchronizedSet(HashSet<Long>()) /** * Currently registered [RefreshingStatusListener]s, which will be notified * when a collection refresh status changes */ private val refreshingStatusListeners = Collections.synchronizedList(LinkedList<WeakReference<RefreshingStatusListener>>()) @WorkerThread override fun onHandleIntent(intent: Intent?) { if (intent == null) return override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.let { val id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1) when (intent.action) { Loading @@ -75,13 +86,10 @@ class DavService: android.app.Service() { refreshingStatusListeners.forEach { listener -> listener.get()?.onDavRefreshStatusChanged(id, true) } CoroutineScope(Dispatchers.IO).launch { val db = AppDatabase.getInstance(this@DavService) db.runInTransaction { refreshCollections(db, id) } } } ACTION_FORCE_SYNC -> { val uri = intent.data!! Loading @@ -95,9 +103,6 @@ class DavService: android.app.Service() { } } return START_NOT_STICKY } /* BOUND SERVICE PART for communicating with the activities Loading @@ -115,14 +120,17 @@ class DavService: android.app.Service() { fun addRefreshingStatusListener(listener: RefreshingStatusListener, callImmediateIfRunning: Boolean) { refreshingStatusListeners += WeakReference<RefreshingStatusListener>(listener) if (callImmediateIfRunning) runningRefresh.forEach { id -> listener.onDavRefreshStatusChanged(id, true) } synchronized(runningRefresh) { for (id in runningRefresh) listener.onDavRefreshStatusChanged(id, true) } } fun removeRefreshingStatusListener(listener: RefreshingStatusListener) { val iter = refreshingStatusListeners.iterator() while (iter.hasNext()) { val item = iter.next().get() if (listener == item) if (item == listener || item == null) iter.remove() } } Loading Loading @@ -355,6 +363,7 @@ class DavService: android.app.Service() { } } db.runInTransaction { saveHomesets() // use refHomeSet (if available) to determine homeset ID Loading @@ -363,6 +372,7 @@ class DavService: android.app.Service() { collection.homeSetId = homeSet.id } saveCollections() } } catch(e: InvalidAccountException) { Logger.log.log(Level.SEVERE, "Invalid account", e) Loading
app/src/main/java/at/bitfire/davdroid/model/ServiceDao.kt +2 −4 Original line number Diff line number Diff line package at.bitfire.davdroid.model import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy Loading @@ -12,14 +13,11 @@ interface ServiceDao { fun getByAccountAndType(accountName: String, type: String): Service? @Query("SELECT id FROM service WHERE accountName=:accountName AND type=:type") fun getIdByAccountAndType(accountName: String, type: String): Long? fun getIdByAccountAndType(accountName: String, type: String): LiveData<Long> @Query("SELECT * FROM service WHERE id=:id") fun get(id: Long): Service? @Query("SELECT * FROM service WHERE type=:type") fun getByType(type: String): List<Service> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrReplace(service: Service): Long Loading
app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +25 −7 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ package at.bitfire.davdroid.ui.account import android.accounts.Account import android.accounts.AccountManager import android.accounts.OnAccountsUpdateListener import android.app.Application import android.content.Intent import android.os.Build Loading Loading @@ -53,6 +54,11 @@ class AccountActivity: AppCompatActivity() { setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) model.accountExists.observe(this, Observer { accountExists -> if (!accountExists) finish() }) tab_layout.setupWithViewPager(view_pager) val tabsAdapter = TabsAdapter(this) view_pager.adapter = tabsAdapter Loading Loading @@ -172,6 +178,7 @@ class AccountActivity: AppCompatActivity() { idxWebcal = null } // reflect changes in UI notifyDataSetChanged() } Loading Loading @@ -208,6 +215,9 @@ class AccountActivity: AppCompatActivity() { throw IllegalArgumentException() } // required to reload all fragments override fun getItemPosition(obj: Any) = POSITION_NONE override fun getPageTitle(position: Int): String = when (position) { idxCardDav -> activity.getString(R.string.account_carddav) Loading @@ -224,7 +234,7 @@ class AccountActivity: AppCompatActivity() { class Model( application: Application, val account: Account ): AndroidViewModel(application) { ): AndroidViewModel(application), OnAccountsUpdateListener { class Factory( val application: Application, Loading @@ -236,20 +246,20 @@ class AccountActivity: AppCompatActivity() { } private val db = AppDatabase.getInstance(application) val accountManager = AccountManager.get(application) val accountSettings by lazy { AccountSettings(getApplication(), account) } val cardDavService = MutableLiveData<Long>() val calDavService = MutableLiveData<Long>() val accountExists = MutableLiveData<Boolean>() val cardDavService = db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV) val calDavService = db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV) val showOnlyPersonal = MutableLiveData<Boolean>() val showOnlyPersonal_writable = MutableLiveData<Boolean>() init { accountManager.addOnAccountsUpdatedListener(this, null, true) viewModelScope.launch(Dispatchers.IO) { cardDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CARDDAV)) calDavService.postValue(db.serviceDao().getIdByAccountAndType(account.name, Service.TYPE_CALDAV)) accountSettings.getShowOnlyPersonal().let { (value, locked) -> showOnlyPersonal.postValue(value) showOnlyPersonal_writable.postValue(locked) Loading @@ -257,6 +267,14 @@ class AccountActivity: AppCompatActivity() { } } override fun onCleared() { accountManager.removeOnAccountsUpdatedListener(this) } override fun onAccountsUpdated(accounts: Array<out Account>) { accountExists.postValue(accounts.contains(account)) } fun toggleReadOnly(item: Collection) { viewModelScope.launch(Dispatchers.IO + NonCancellable) { val newItem = item.copy(forceReadOnly = !item.forceReadOnly) Loading
app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt +6 −7 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ import android.provider.CalendarContract import android.provider.ContactsContract import android.view.* import android.widget.PopupMenu import androidx.annotation.WorkerThread import androidx.annotation.AnyThread import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels Loading @@ -24,12 +24,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import at.bitfire.davdroid.Constants import at.bitfire.davdroid.DavService import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.model.AppDatabase import at.bitfire.davdroid.model.Collection import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import kotlinx.android.synthetic.main.account_collections.* import kotlinx.coroutines.Dispatchers Loading Loading @@ -314,18 +312,19 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList context.startService(intent) } @WorkerThread @AnyThread override fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean) { if (id == serviceId.value) isRefreshing.postValue(refreshing) } @AnyThread override fun onStatusChanged(which: Int) { viewModelScope.launch(Dispatchers.Default) { checkSyncStatus() } } @AnyThread @Synchronized private fun checkSyncStatus() { if (collectionType == Collection.TYPE_ADDRESSBOOK) { val mainAuthority = context.getString(R.string.address_books_authority) Loading