Don't listen for account changes all the time (#780)

* Delete account: don't rely on cleanup worker

* Don't list for account changes all the time
This commit is contained in:
Ricki Hirner 2024-05-04 20:52:50 +02:00 committed by GitHub
parent 1ad8e892b6
commit 952cb52b95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 48 additions and 68 deletions

View file

@ -9,22 +9,22 @@ import android.os.StrictMode
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.syncadapter.AccountsUpdatedListener
import at.bitfire.davdroid.syncadapter.AccountsCleanupWorker
import at.bitfire.davdroid.ui.DebugInfoActivity
import at.bitfire.davdroid.ui.NotificationUtils
import at.bitfire.davdroid.ui.UiUtils
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.logging.Level
import javax.inject.Inject
import kotlin.concurrent.thread
import kotlin.system.exitProcess
@HiltAndroidApp
class App: Application(), Thread.UncaughtExceptionHandler, Configuration.Provider {
@Inject lateinit var accountsUpdatedListener: AccountsUpdatedListener
@Inject lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
@ -58,15 +58,15 @@ class App: Application(), Thread.UncaughtExceptionHandler, Configuration.Provide
// don't block UI for some background checks
@OptIn(DelicateCoroutinesApi::class)
thread {
// watch for account changes/deletions
accountsUpdatedListener.listen()
GlobalScope.launch(Dispatchers.Default) {
// clean up orphaned accounts in DB from time to time
AccountsCleanupWorker.enqueue(this@App)
// watch installed/removed tasks apps over whole app lifetime and update sync settings accordingly
TasksAppWatcher.watchInstalledTaskApps(this, GlobalScope)
TasksAppWatcher.watchInstalledTaskApps(this@App, this)
// create/update app shortcuts
UiUtils.updateShortcuts(this)
UiUtils.updateShortcuts(this@App)
}
}

View file

@ -31,6 +31,9 @@ interface ServiceDao {
@Query("DELETE FROM service")
fun deleteAll()
@Query("DELETE FROM service WHERE accountName=:accountName")
suspend fun deleteByAccount(accountName: String)
@Query("DELETE FROM service WHERE accountName NOT IN (:accountNames)")
fun deleteExceptAccounts(accountNames: Array<String>)

View file

@ -142,6 +142,13 @@ class AccountRepository @Inject constructor(
// blocks calling thread
future.result
}
// delete address book accounts
LocalAddressBook.deleteByAccount(context, accountName)
// delete from database
serviceRepository.deleteByAccount(accountName)
true
} catch (e: Exception) {
Logger.log.log(Level.WARNING, "Couldn't remove account $accountName", e)
@ -232,7 +239,7 @@ class AccountRepository @Inject constructor(
}
// update account name references in database
serviceRepository.onAccountRenamed(oldName, newName)
serviceRepository.renameAccount(oldName, newName)
// update main account of address book accounts
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED)

View file

@ -9,18 +9,22 @@ import at.bitfire.davdroid.db.Service
import javax.inject.Inject
class DavServiceRepository @Inject constructor(
private val db: AppDatabase
db: AppDatabase
) {
private val dao = db.serviceDao()
suspend fun deleteByAccount(accountName: String) {
dao.deleteByAccount(accountName)
}
fun getCalDavServiceFlow(accountName: String) =
dao.getByAccountAndTypeFlow(accountName, Service.TYPE_CALDAV)
fun getCardDavServiceFlow(accountName: String) =
dao.getByAccountAndTypeFlow(accountName, Service.TYPE_CARDDAV)
suspend fun onAccountRenamed(oldName: String, newName: String) {
suspend fun renameAccount(oldName: String, newName: String) {
dao.renameAccount(oldName, newName)
}

View file

@ -24,7 +24,6 @@ import at.bitfire.davdroid.db.SyncState
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.syncadapter.AccountUtils
import at.bitfire.davdroid.util.DavUtils
import at.bitfire.davdroid.util.lastSegment
import at.bitfire.davdroid.util.setAndVerifyUserData
import at.bitfire.vcard4android.AndroidAddressBook
@ -84,6 +83,13 @@ open class LocalAddressBook(
return addressBook
}
fun deleteByAccount(context: Context, accountName: String) {
val mainAccount = Account(accountName, context.getString(R.string.account_type))
findAll(context, null, mainAccount).forEach {
it.delete()
}
}
/**
* Finds and returns all the local address books belonging to a given main account
*

View file

@ -8,8 +8,8 @@ import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
@ -19,8 +19,8 @@ import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalAddressBook
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import java.time.Duration
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.logging.Level
@HiltWorker
@ -42,12 +42,15 @@ class AccountsCleanupWorker @AssistedInject constructor(
/** Must be called exactly one time after calling `lockAccountsCleanup`. */
fun unlockAccountsCleanup() = mutex.release()
/**
* Enqueues [AccountsCleanupWorker] to be run regularly (but not necessarily now).
*/
fun enqueue(context: Context) {
WorkManager.getInstance(context).enqueueUniqueWork(NAME, ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<AccountsCleanupWorker>()
.setInitialDelay(15, TimeUnit.SECONDS) // wait some time before cleaning up accouts
.build())
// run every day
val rq = PeriodicWorkRequestBuilder<AccountsCleanupWorker>(Duration.ofDays(1))
WorkManager.getInstance(context).enqueueUniquePeriodicWork(NAME, ExistingPeriodicWorkPolicy.UPDATE, rq.build())
}
}
override fun doWork(): Result {

View file

@ -1,46 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.syncadapter
import android.accounts.Account
import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.content.Context
import androidx.annotation.AnyThread
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
class AccountsUpdatedListener private constructor(
val context: Context
): OnAccountsUpdateListener {
@Module
@InstallIn(SingletonComponent::class)
object AccountsUpdatedListenerModule {
@Provides
@Singleton
fun accountsUpdatedListener(@ApplicationContext context: Context) = AccountsUpdatedListener(context)
}
fun listen() {
val accountManager = AccountManager.get(context)
accountManager.addOnAccountsUpdatedListener(this, null, true)
}
/**
* Called when the system accounts have been updated. The interesting case for us is when
* a DAVx5 account has been removed. Then we enqueue an [AccountsCleanupWorker] to remove
* the orphaned entries from the database.
*/
@AnyThread
override fun onAccountsUpdated(accounts: Array<out Account>) {
AccountsCleanupWorker.enqueue(context)
}
}

View file

@ -13,7 +13,6 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asFlow
import androidx.lifecycle.switchMap
import androidx.lifecycle.viewModelScope
@ -31,7 +30,9 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOf
@ -135,9 +136,11 @@ class AccountScreenModel @AssistedInject constructor(
// actions
private val notInterruptibleScope = CoroutineScope(SupervisorJob())
/** Deletes the account from the system (won't touch collections on the server). */
fun deleteAccount() {
viewModelScope.launch {
notInterruptibleScope.launch {
accountRepository.delete(account.name)
}
}
@ -157,7 +160,7 @@ class AccountScreenModel @AssistedInject constructor(
* @param newName new account name
*/
fun renameAccount(newName: String) {
viewModelScope.launch {
notInterruptibleScope.launch {
try {
accountRepository.rename(account.name, newName)