From 090a786030102b52d9fb498c23e618b2286a4eae Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 19 May 2023 18:13:13 +0600 Subject: [PATCH 1/6] fix syncAllAccount ignores murena & google accounts --- .../bitfire/davdroid/syncadapter/SyncUtils.kt | 25 +++++++++++++++++++ .../bitfire/davdroid/ui/AccountsActivity.kt | 20 ++------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt index 4b3278562..66150ebaf 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt @@ -14,10 +14,13 @@ import android.content.pm.PackageManager import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Build +import android.view.MenuItem +import androidx.annotation.StringRes import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import at.bitfire.davdroid.Constants +import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.PermissionUtils import at.bitfire.davdroid.R @@ -157,4 +160,26 @@ object SyncUtils { } } + private fun allAccounts(context: Context): List { + val accountManager = AccountManager.get(context) + val accounts = mutableListOf() + + accounts.addAll(getAccountsByType(context, accountManager, R.string.account_type)) + accounts.addAll(getAccountsByType(context, accountManager, R.string.eelo_account_type)) + accounts.addAll(getAccountsByType(context, accountManager, R.string.google_account_type)) + + return accounts + } + + private fun getAccountsByType(context: Context, accountManager: AccountManager, @StringRes type: Int): Array { + val accountType = context.getString(type) + return accountManager.getAccountsByType(accountType) + } + + fun syncAllAccounts(context: Context) { + allAccounts(context) + .forEach { + DavUtils.requestSync(context, it) + } + } } \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt index e24ab4f80..1a51058d8 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountsActivity.kt @@ -4,28 +4,19 @@ package at.bitfire.davdroid.ui -import android.accounts.AccountManager import android.app.Activity import android.content.Intent -import android.content.SyncStatusObserver -import android.content.pm.ShortcutManager -import android.os.Build import android.os.Bundle import android.view.MenuItem import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.GravityCompat -import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.ActivityAccountsBinding +import at.bitfire.davdroid.syncadapter.SyncUtils import at.bitfire.davdroid.ui.setup.LoginActivity import com.google.android.material.navigation.NavigationView import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint @@ -92,14 +83,7 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele return true } - - private fun allAccounts() = - AccountManager.get(this).getAccountsByType(getString(R.string.account_type)) - fun syncAllAccounts(item: MenuItem? = null) { - val accounts = allAccounts() - for (account in accounts) - DavUtils.requestSync(this, account) + SyncUtils.syncAllAccounts(this) } - } -- GitLab From cc2b728485275e56aa929e1a39cb9aa0a166b498 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 23 May 2023 12:33:05 +0600 Subject: [PATCH 2/6] Setup syncAllAccount after login issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1124 Just right after login, the sync requires some time to be setup, so it can't be performed right after login. To resolve this, we have to implement `run & try` formula, which after some delay for a specific time limit retry sync over & over again, so user doesn't have to wait until next peridic sync to get all his calendar, tasks, contacts etc account to available. --- .../syncadapter/SyncAllAccountWorker.kt | 96 +++++++++++++++++++ .../bitfire/davdroid/syncadapter/SyncUtils.kt | 16 +++- .../ui/setup/AccountDetailsFragment.kt | 8 +- 3 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt new file mode 100644 index 000000000..36140997c --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt @@ -0,0 +1,96 @@ +/* + * Copyright MURENA SAS 2023 + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package at.bitfire.davdroid.syncadapter + +import android.content.Context +import androidx.hilt.work.HiltWorker +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.Worker +import androidx.work.WorkerParameters +import at.bitfire.davdroid.log.Logger +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import java.util.concurrent.TimeUnit +import java.util.logging.Level + +// Right after login to the cloud account, there is no guarantee the sync setup is finished, so user's might not see the calendar, task, contact accounts setup. +// To resolve this we need to try syncing for a specific time limit with a multiplying wait time. +// We need to use OneTimeWorker instead of PeriodicTimeWorker, because PeriodicTimWorker requires >= 15 min between it's job. But in our case, the job delays are in seconds. +// issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1124 +@HiltWorker +class SyncAllAccountWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted workerParameters: WorkerParameters, +) : Worker(appContext, workerParameters) { + + companion object { + private const val NAME = "sync-all-accounts" + private const val WAIT_SEC = "wait-sec" + private const val DEFAULT_WAIT_SEC: Long = 5 + private const val WAIT_LIMIT_IN_SEC: Long = 120 + + // enqueue next sync call + // if the requested time is greater than WAIT_TIME_LIMIT, that means we have tried enough times, so ignore it. + fun enqueue(context: Context, waitSec: Long = DEFAULT_WAIT_SEC) { + if (waitSec > WAIT_LIMIT_IN_SEC) { + return + } + + val data = Data.Builder() + data.putLong(WAIT_SEC, waitSec) + + WorkManager.getInstance(context).enqueueUniqueWork( + NAME, ExistingWorkPolicy.REPLACE, + OneTimeWorkRequestBuilder() + .setInitialDelay( + waitSec, + TimeUnit.SECONDS + ) // wait some time before sync all accounts + .setInputData(data.build()) + .build() + ) + } + } + + // sync for all accounts & start next periodic sync + // if sync failed for all accounts, don't retry again + // initially sync work can start faster than DEFAULT_WAIT_TIME. To avoid very close sync call, we need to round up the next call double to DEFAULT_WAIT_TIME. + override fun doWork(): Result { + Logger.log.log(Level.FINE, "sync all accounts work started") + + val result = SyncUtils.syncAllAccounts(applicationContext) + if (!result) { + return Result.success() + } + + enqueueNext() + return Result.success() + } + + private fun enqueueNext() { + var waitSec = inputData.getLong(WAIT_SEC, DEFAULT_WAIT_SEC) + + if (waitSec < DEFAULT_WAIT_SEC) { + waitSec = DEFAULT_WAIT_SEC + } + + enqueue(applicationContext, waitSec * 2) + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt index 66150ebaf..b2d663b69 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncUtils.kt @@ -176,10 +176,16 @@ object SyncUtils { return accountManager.getAccountsByType(accountType) } - fun syncAllAccounts(context: Context) { - allAccounts(context) - .forEach { + fun syncAllAccounts(context: Context): Boolean { + val accounts = allAccounts(context) + if (accounts.isEmpty()) { + return false + } + + accounts.forEach { DavUtils.requestSync(context, it) - } + } + + return true } -} \ No newline at end of file +} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 90c1e7297..235eb48eb 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -24,7 +24,11 @@ import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.lifecycle.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import at.bitfire.davdroid.Constants import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.InvalidAccountException @@ -41,6 +45,7 @@ import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.AccountUtils +import at.bitfire.davdroid.syncadapter.SyncAllAccountWorker import at.bitfire.vcard4android.GroupMethod import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint @@ -352,6 +357,7 @@ class AccountDetailsFragment : Fragment() { } DavUtils.requestSync(activity, account) + SyncAllAccountWorker.enqueue(context, 2) result.postValue(true) } -- GitLab From 0eeb33f89985033cf663a331b15716e57f4477a0 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 23 May 2023 14:30:32 +0600 Subject: [PATCH 3/6] Update comment marker according to review --- .../syncadapter/SyncAllAccountWorker.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt index 36140997c..ee397353f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt @@ -30,10 +30,11 @@ import dagger.assisted.AssistedInject import java.util.concurrent.TimeUnit import java.util.logging.Level -// Right after login to the cloud account, there is no guarantee the sync setup is finished, so user's might not see the calendar, task, contact accounts setup. -// To resolve this we need to try syncing for a specific time limit with a multiplying wait time. -// We need to use OneTimeWorker instead of PeriodicTimeWorker, because PeriodicTimWorker requires >= 15 min between it's job. But in our case, the job delays are in seconds. -// issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1124 +/** Right after login to the cloud account, there is no guarantee the sync setup is finished, so user's might not see the calendar, task, contact accounts setup. + * To resolve this we need to try syncing for a specific time limit with a multiplying wait time. + * We need to use OneTimeWorker instead of PeriodicTimeWorker, because PeriodicTimWorker requires >= 15 min between it's job. But in our case, the job delays are in seconds. + * issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1124 + **/ @HiltWorker class SyncAllAccountWorker @AssistedInject constructor( @Assisted appContext: Context, @@ -46,8 +47,9 @@ class SyncAllAccountWorker @AssistedInject constructor( private const val DEFAULT_WAIT_SEC: Long = 5 private const val WAIT_LIMIT_IN_SEC: Long = 120 - // enqueue next sync call - // if the requested time is greater than WAIT_TIME_LIMIT, that means we have tried enough times, so ignore it. + /* enqueue next sync call + if the requested time is greater than WAIT_TIME_LIMIT, that means we have tried enough times, so ignore it. + */ fun enqueue(context: Context, waitSec: Long = DEFAULT_WAIT_SEC) { if (waitSec > WAIT_LIMIT_IN_SEC) { return @@ -69,9 +71,10 @@ class SyncAllAccountWorker @AssistedInject constructor( } } - // sync for all accounts & start next periodic sync - // if sync failed for all accounts, don't retry again - // initially sync work can start faster than DEFAULT_WAIT_TIME. To avoid very close sync call, we need to round up the next call double to DEFAULT_WAIT_TIME. + /* sync for all accounts & start next periodic sync + if sync failed for all accounts, don't retry again + initially sync work can start faster than DEFAULT_WAIT_TIME. To avoid very close sync call, we need to round up the next call double to DEFAULT_WAIT_TIME. + */ override fun doWork(): Result { Logger.log.log(Level.FINE, "sync all accounts work started") -- GitLab From 44afa770bab433977e135102abf2581bc017a808 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 23 May 2023 14:31:23 +0600 Subject: [PATCH 4/6] remove unwanted comment --- .../java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt index ee397353f..36b25c8b4 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt @@ -33,7 +33,6 @@ import java.util.logging.Level /** Right after login to the cloud account, there is no guarantee the sync setup is finished, so user's might not see the calendar, task, contact accounts setup. * To resolve this we need to try syncing for a specific time limit with a multiplying wait time. * We need to use OneTimeWorker instead of PeriodicTimeWorker, because PeriodicTimWorker requires >= 15 min between it's job. But in our case, the job delays are in seconds. - * issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1124 **/ @HiltWorker class SyncAllAccountWorker @AssistedInject constructor( -- GitLab From 7a88466686072d34d8cd04abc054d99e2090e666 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 23 May 2023 16:36:17 +0600 Subject: [PATCH 5/6] refactor according to review --- .../{SyncAllAccountWorker.kt => FirstSyncWorker.kt} | 11 ++--------- .../davdroid/ui/setup/AccountDetailsFragment.kt | 4 ++-- 2 files changed, 4 insertions(+), 11 deletions(-) rename app/src/main/java/at/bitfire/davdroid/syncadapter/{SyncAllAccountWorker.kt => FirstSyncWorker.kt} (84%) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/FirstSyncWorker.kt similarity index 84% rename from app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt rename to app/src/main/java/at/bitfire/davdroid/syncadapter/FirstSyncWorker.kt index 36b25c8b4..6ebc7ef95 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/FirstSyncWorker.kt @@ -35,7 +35,7 @@ import java.util.logging.Level * We need to use OneTimeWorker instead of PeriodicTimeWorker, because PeriodicTimWorker requires >= 15 min between it's job. But in our case, the job delays are in seconds. **/ @HiltWorker -class SyncAllAccountWorker @AssistedInject constructor( +class FirstSyncWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted workerParameters: WorkerParameters, ) : Worker(appContext, workerParameters) { @@ -46,9 +46,6 @@ class SyncAllAccountWorker @AssistedInject constructor( private const val DEFAULT_WAIT_SEC: Long = 5 private const val WAIT_LIMIT_IN_SEC: Long = 120 - /* enqueue next sync call - if the requested time is greater than WAIT_TIME_LIMIT, that means we have tried enough times, so ignore it. - */ fun enqueue(context: Context, waitSec: Long = DEFAULT_WAIT_SEC) { if (waitSec > WAIT_LIMIT_IN_SEC) { return @@ -59,7 +56,7 @@ class SyncAllAccountWorker @AssistedInject constructor( WorkManager.getInstance(context).enqueueUniqueWork( NAME, ExistingWorkPolicy.REPLACE, - OneTimeWorkRequestBuilder() + OneTimeWorkRequestBuilder() .setInitialDelay( waitSec, TimeUnit.SECONDS @@ -70,10 +67,6 @@ class SyncAllAccountWorker @AssistedInject constructor( } } - /* sync for all accounts & start next periodic sync - if sync failed for all accounts, don't retry again - initially sync work can start faster than DEFAULT_WAIT_TIME. To avoid very close sync call, we need to round up the next call double to DEFAULT_WAIT_TIME. - */ override fun doWork(): Result { Logger.log.log(Level.FINE, "sync all accounts work started") diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 235eb48eb..4bf9e876e 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -45,7 +45,7 @@ import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.AccountUtils -import at.bitfire.davdroid.syncadapter.SyncAllAccountWorker +import at.bitfire.davdroid.syncadapter.FirstSyncWorker import at.bitfire.vcard4android.GroupMethod import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint @@ -357,7 +357,7 @@ class AccountDetailsFragment : Fragment() { } DavUtils.requestSync(activity, account) - SyncAllAccountWorker.enqueue(context, 2) + FirstSyncWorker.enqueue(context, 2) result.postValue(true) } -- GitLab From e7a77c8b6843dc9956b0687fbbfa4b1f651bb08b Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 23 May 2023 18:06:51 +0600 Subject: [PATCH 6/6] refactor according to review --- .../{FirstSyncWorker.kt => SyncAllAccountWorker.kt} | 4 ++-- .../at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename app/src/main/java/at/bitfire/davdroid/syncadapter/{FirstSyncWorker.kt => SyncAllAccountWorker.kt} (96%) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/FirstSyncWorker.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt similarity index 96% rename from app/src/main/java/at/bitfire/davdroid/syncadapter/FirstSyncWorker.kt rename to app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt index 6ebc7ef95..ae3b0c1e4 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/FirstSyncWorker.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAllAccountWorker.kt @@ -35,7 +35,7 @@ import java.util.logging.Level * We need to use OneTimeWorker instead of PeriodicTimeWorker, because PeriodicTimWorker requires >= 15 min between it's job. But in our case, the job delays are in seconds. **/ @HiltWorker -class FirstSyncWorker @AssistedInject constructor( +class SyncAllAccountWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted workerParameters: WorkerParameters, ) : Worker(appContext, workerParameters) { @@ -56,7 +56,7 @@ class FirstSyncWorker @AssistedInject constructor( WorkManager.getInstance(context).enqueueUniqueWork( NAME, ExistingWorkPolicy.REPLACE, - OneTimeWorkRequestBuilder() + OneTimeWorkRequestBuilder() .setInitialDelay( waitSec, TimeUnit.SECONDS diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 4bf9e876e..235eb48eb 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -45,7 +45,7 @@ import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.AccountUtils -import at.bitfire.davdroid.syncadapter.FirstSyncWorker +import at.bitfire.davdroid.syncadapter.SyncAllAccountWorker import at.bitfire.vcard4android.GroupMethod import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint @@ -357,7 +357,7 @@ class AccountDetailsFragment : Fragment() { } DavUtils.requestSync(activity, account) - FirstSyncWorker.enqueue(context, 2) + SyncAllAccountWorker.enqueue(context, 2) result.postValue(true) } -- GitLab