From 433ab20aca291cdd2a04490e7ac46f3c22a1797b Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 6 Jul 2022 15:30:37 +0600 Subject: [PATCH 1/3] 5560-sync_mail_with_account_manager issue: https://gitlab.e.foundation/e/backlog/-/issues/5560 Introduce new broadCast receiver which will be called from accountManager & settings app when new account is confiured, or any account is removed, so that mail app accounts also got synced with them. - new AccountSyncReceiver implemented - refactor EeloAccountCreator code - Bug fix: invalid account created automatically on accountSync call - Bug fix: on new account creation account name always updated with email --- .../k9/controller/MessagingController.java | 9 +- .../java/com/fsck/k9/job/MailSyncWorker.kt | 4 +- app/k9mail/src/main/AndroidManifest.xml | 9 ++ .../fsck/k9/account/AccountSyncReceiver.kt | 65 +++++++++++++ .../java/com/fsck/k9/activity/MessageList.kt | 71 +------------- .../k9/activity/setup/AccountSetupBasics.kt | 4 +- .../accountmanager/EeloAccountCreator.java | 96 ++++++++++++++++++- 7 files changed, 185 insertions(+), 73 deletions(-) create mode 100644 app/ui/legacy/src/main/java/com/fsck/k9/account/AccountSyncReceiver.kt diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index 8fc4f9c04e..94a9678f1e 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -28,6 +28,7 @@ import android.os.SystemClock; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.work.ListenableWorker.Result; import com.fsck.k9.Account; import com.fsck.k9.Account.DeletePolicy; import com.fsck.k9.Account.Expunge; @@ -2243,7 +2244,7 @@ public class MessagingController { return !backend.getSupportsTrashFolder(); } - public boolean performPeriodicMailSync(Account account) { + public Result performPeriodicMailSync(Account account) { final CountDownLatch latch = new CountDownLatch(1); MutableBoolean syncError = new MutableBoolean(false); checkMail(account, false, false, true, new SimpleMessagingListener() { @@ -2269,13 +2270,17 @@ public class MessagingController { boolean success = !syncError.getValue(); if (success) { + if (preferences.getAccount(account.getUuid()) == null) { + Timber.e("Account %s not present. Can't perform mail sync.", account.getUuid()); + return Result.failure(); + } long now = System.currentTimeMillis(); Timber.v("Account %s successfully synced @ %tc", account, now); account.setLastSyncTime(now); preferences.saveAccount(account); } - return success; + return success ? Result.success() : Result.retry(); } /** diff --git a/app/core/src/main/java/com/fsck/k9/job/MailSyncWorker.kt b/app/core/src/main/java/com/fsck/k9/job/MailSyncWorker.kt index 193c24a5cc..d24ee920f3 100644 --- a/app/core/src/main/java/com/fsck/k9/job/MailSyncWorker.kt +++ b/app/core/src/main/java/com/fsck/k9/job/MailSyncWorker.kt @@ -44,9 +44,7 @@ class MailSyncWorker( return Result.success() } - val success = messagingController.performPeriodicMailSync(account) - - return if (success) Result.success() else Result.retry() + return messagingController.performPeriodicMailSync(account) } private fun isBackgroundSyncDisabled(): Boolean { diff --git a/app/k9mail/src/main/AndroidManifest.xml b/app/k9mail/src/main/AndroidManifest.xml index 575306f922..58f4db6613 100644 --- a/app/k9mail/src/main/AndroidManifest.xml +++ b/app/k9mail/src/main/AndroidManifest.xml @@ -343,6 +343,15 @@ + + + + + + + . + */ + +package com.fsck.k9.account + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.fsck.k9.Preferences +import com.fsck.k9.activity.setup.accountmanager.EeloAccountCreator +import com.fsck.k9.controller.push.PushController +import java.util.concurrent.Executors +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { + + private val pushController: PushController by inject() + private val preferences: Preferences by inject() + private val accountRemover: BackgroundAccountRemover by inject() + + override fun onReceive(context: Context?, intent: Intent?) { + intent?.let { + if (it.action.equals("foundation.e.accountmanager.account.create", ignoreCase = true)) { + createNewAccount(context) + } else if (it.action.equals("foundation.e.accountmanager.account.remove", ignoreCase = true)) { + removeAccount(it) + } + } + } + + private fun createNewAccount(context: Context?) { + pushController.init() + context?.let { + Executors.newSingleThreadExecutor().execute { + EeloAccountCreator.loadAccountsFromAccountManager(it.applicationContext, preferences, accountRemover, null) + } + } + } + + private fun removeAccount(intent: Intent) { + val account = intent.extras?.getString("account") + account?.let { email -> + preferences.accounts.forEach { + if (it.email == email) { + accountRemover.removeAccountAsync(it.uuid) + return@forEach + } + } + } + } +} \ No newline at end of file diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt index 19c1aecf2e..ee593e7344 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt @@ -16,7 +16,6 @@ package com.fsck.k9.activity -import android.accounts.AccountManager import android.annotation.SuppressLint import android.app.SearchManager import android.content.Context @@ -53,9 +52,6 @@ import com.fsck.k9.Preferences import com.fsck.k9.account.BackgroundAccountRemover import com.fsck.k9.activity.compose.MessageActions import com.fsck.k9.activity.setup.AccountSetupBasics -import com.fsck.k9.activity.setup.accountmanager.AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY -import com.fsck.k9.activity.setup.accountmanager.AccountManagerConstants.EELO_ACCOUNT_TYPE -import com.fsck.k9.activity.setup.accountmanager.AccountManagerConstants.GOOGLE_ACCOUNT_TYPE import com.fsck.k9.activity.setup.accountmanager.EeloAccountCreator import com.fsck.k9.controller.MessageReference import com.fsck.k9.controller.MessagingController @@ -157,17 +153,11 @@ open class MessageList : private var viewSwitcher: ViewSwitcher? = null private lateinit var recentChangesSnackbar: Snackbar - private lateinit var accountManager: AccountManager - public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setLayout(R.layout.message_list_loading) - val accounts = preferences.accounts - deleteIncompleteAccounts(accounts) - - accountManager = AccountManager.get(this) - addNewAccountsAutomatically(accounts, savedInstanceState) + addNewAccountsAutomatically(savedInstanceState) } private fun onAccountConfigurationFinish(savedInstanceState: Bundle?) { @@ -276,12 +266,6 @@ open class MessageList : displayViews() } - private fun deleteIncompleteAccounts(accounts: List) { - accounts.filter { !it.isFinishedSetup }.forEach { - accountRemover.removeAccountAsync(it.uuid) - } - } - private fun findFragments() { val fragmentManager = supportFragmentManager messageListFragment = fragmentManager.findFragmentById(R.id.message_list_container) as MessageListFragment? @@ -1704,61 +1688,16 @@ open class MessageList : val messageViewOnly: Boolean = false ) - private fun addNewAccountsAutomatically(accounts: List, savedInstanceState: Bundle?) { + private fun addNewAccountsAutomatically(savedInstanceState: Bundle?) { lifecycleScope.launch(Dispatchers.IO) { - try { - val eeloAccounts: Array = getEeloAccountsOnDevice() - val googleAccounts: Array = getGoogleAccountsOnDevice() - for (eeloAccount in eeloAccounts) { - val emailId: String = accountManager.getUserData(eeloAccount, ACCOUNT_EMAIL_ADDRESS_KEY) - if (!emailId.contains("@")) continue - var accountIsSignedIn = false - for (account in accounts) { - if (emailId == account.email) { - accountIsSignedIn = true - break - } - } - if (!accountIsSignedIn) { - val password: String = accountManager.getPassword(eeloAccount) - EeloAccountCreator.createAccount(applicationContext, emailId, password, false) - } + EeloAccountCreator.loadAccountsFromAccountManager(applicationContext, preferences, accountRemover) { + lifecycleScope.launch(Dispatchers.Main) { + onAccountConfigurationFinish(savedInstanceState) } - - for (googleAccount in googleAccounts) { - val emailId: String = accountManager.getUserData(googleAccount, ACCOUNT_EMAIL_ADDRESS_KEY) - - var accountIsSignedIn = false - for (account in accounts) { - if (emailId == account.email) { - if (account.name == null) { // we need to fix an old bug - account.name = emailId - Preferences.getPreferences(applicationContext).saveAccount(account) - } - accountIsSignedIn = true - break - } - } - if (!accountIsSignedIn) { - EeloAccountCreator.createAccount(applicationContext, emailId, "", true) - } - } - } catch (e: SecurityException) { - Timber.e(e) - } - lifecycleScope.launch(Dispatchers.Main) { - onAccountConfigurationFinish(savedInstanceState) } } } - private fun getEeloAccountsOnDevice(): Array { - return accountManager.getAccountsByType(EELO_ACCOUNT_TYPE) - } - - private fun getGoogleAccountsOnDevice(): Array { - return accountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE) - } companion object : KoinComponent { private const val EXTRA_SEARCH = "search_bytes" diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupBasics.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupBasics.kt index d1d3946c0f..cbfb08b70e 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupBasics.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupBasics.kt @@ -331,7 +331,9 @@ class AccountSetupBasics : K9Activity() { account.senderName = getOwnerName() account.email = email - account.name = email + if (account.name == null) { + account.name = email + } return account } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java index 16627d6565..cfb45a6005 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java @@ -16,14 +16,24 @@ package com.fsck.k9.activity.setup.accountmanager; + +import java.util.List; + +import android.accounts.AccountManager; import android.content.Context; +import android.os.Build.VERSION_CODES; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.WorkerThread; import com.fsck.k9.Account; import com.fsck.k9.Account.DeletePolicy; import com.fsck.k9.Core; import com.fsck.k9.DI; import com.fsck.k9.Preferences; import com.fsck.k9.account.AccountCreator; +import com.fsck.k9.account.BackgroundAccountRemover; import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings; import com.fsck.k9.autodiscovery.api.DiscoveryResults; import com.fsck.k9.autodiscovery.api.DiscoveryTarget; @@ -42,7 +52,87 @@ public class EeloAccountCreator { private static final AccountCreator accountCreator = DI.get(AccountCreator.class); private static final SpecialLocalFoldersCreator localFoldersCreator = DI.get(SpecialLocalFoldersCreator.class); - public static void createAccount(Context context, String emailId, String password, boolean isGoogleAccount) { + @RequiresApi(api = VERSION_CODES.N) + @WorkerThread + public static void loadAccountsFromAccountManager(@NonNull Context context, @NonNull Preferences preferences, + @NonNull BackgroundAccountRemover accountRemover, @Nullable OnAccountLoadCompleteCallBack callBack) { + try { + AccountManager accountManager = AccountManager.get(context); + + List accounts = preferences.getAccounts(); + deleteIncompleteAccounts(accounts, accountRemover); + + loadEeloAccounts(context, accounts, accountManager); + loadGoogleAccounts(context, accounts, accountManager); + } catch (Exception e) { + Timber.e(e); + } + if (callBack != null) { + callBack.onAccountLoadComplete(); + } + } + + @RequiresApi(api = VERSION_CODES.N) + private static void loadGoogleAccounts(@NonNull Context context, List accounts, + @NonNull AccountManager accountManager) { + android.accounts.Account[] googleAccounts = + accountManager.getAccountsByType(AccountManagerConstants.GOOGLE_ACCOUNT_TYPE); + for (android.accounts.Account googleAccount : googleAccounts) { + String emailId = + accountManager.getUserData(googleAccount, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY); + if (isInvalidEmail(emailId)) { + continue; + } + + boolean accountAlreadyPresent = accounts.stream() + .filter(account -> emailId.equalsIgnoreCase(account.getEmail())) + .peek(account -> updateAccountNameIfMissing(context, emailId, account)) + .findAny().isPresent(); + if (!accountAlreadyPresent) { + createAccount(context, emailId, "", true); + } + } + } + + private static void updateAccountNameIfMissing(@NonNull Context context, String emailId, Account account) { + if (account.getName() == null) { // we need to fix an old bug + account.setName(emailId); + Preferences.getPreferences(context).saveAccount(account); + } + } + + private static boolean isInvalidEmail(String emailId) { + return emailId == null || !emailId.contains("@"); + } + + @RequiresApi(api = VERSION_CODES.N) + private static void loadEeloAccounts(@NonNull Context context, List accounts, + @NonNull AccountManager accountManager) { + android.accounts.Account[] eeloAccounts = + accountManager.getAccountsByType(AccountManagerConstants.EELO_ACCOUNT_TYPE); + for (android.accounts.Account eeloAccount : eeloAccounts) { + String emailId = + accountManager.getUserData(eeloAccount, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY); + if (isInvalidEmail(emailId)) { + continue; + } + + boolean isNewAccount = accounts.stream() + .noneMatch(account -> emailId.equalsIgnoreCase(account.getEmail())); + if (isNewAccount) { + String password = accountManager.getPassword(eeloAccount); + createAccount(context, emailId, password, false); + } + } + } + + @RequiresApi(api = VERSION_CODES.N) + private static void deleteIncompleteAccounts(List accounts, BackgroundAccountRemover accountRemover) { + accounts.stream().filter(account -> !account.isFinishedSetup()) + .forEach(account -> accountRemover.removeAccountAsync(account.getUuid())); + } + + private static void createAccount(Context context, String emailId, String password, boolean isGoogleAccount) { Preferences preferences = Preferences.getPreferences(context); Account account = preferences.newAccount(); @@ -137,5 +227,9 @@ public class EeloAccountCreator { ) ); } + + public interface OnAccountLoadCompleteCallBack { + void onAccountLoadComplete(); + } } -- GitLab From ff076dd992c1d5e0746be1899f36817caff98bfd Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 7 Jul 2022 12:53:58 +0600 Subject: [PATCH 2/3] refactor automated accountSync code - refactor AccountSyncReceiver class - add separate exception catch on EeloAccountCreator class's loadAccountsFromAccountManager method --- .../fsck/k9/account/AccountSyncReceiver.kt | 37 ++++++++++++------- .../accountmanager/EeloAccountCreator.java | 4 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/account/AccountSyncReceiver.kt b/app/ui/legacy/src/main/java/com/fsck/k9/account/AccountSyncReceiver.kt index 6edcf06457..ce18077553 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/account/AccountSyncReceiver.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/account/AccountSyncReceiver.kt @@ -28,17 +28,23 @@ import org.koin.core.component.inject class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { + companion object { + private const val ACCOUNT_CREATION_ACTION = "foundation.e.accountmanager.account.create" + private const val ACCOUNT_REMOVAL_ACTION = "foundation.e.accountmanager.account.remove" + } + private val pushController: PushController by inject() private val preferences: Preferences by inject() private val accountRemover: BackgroundAccountRemover by inject() override fun onReceive(context: Context?, intent: Intent?) { - intent?.let { - if (it.action.equals("foundation.e.accountmanager.account.create", ignoreCase = true)) { - createNewAccount(context) - } else if (it.action.equals("foundation.e.accountmanager.account.remove", ignoreCase = true)) { - removeAccount(it) - } + if (intent == null) { + return + } + + when (intent.action) { + ACCOUNT_CREATION_ACTION -> createNewAccount(context) + ACCOUNT_REMOVAL_ACTION -> removeAccount(intent) } } @@ -46,19 +52,22 @@ class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { pushController.init() context?.let { Executors.newSingleThreadExecutor().execute { - EeloAccountCreator.loadAccountsFromAccountManager(it.applicationContext, preferences, accountRemover, null) + EeloAccountCreator.loadAccountsFromAccountManager( + it.applicationContext, + preferences, + accountRemover, + null + ) } } } private fun removeAccount(intent: Intent) { - val account = intent.extras?.getString("account") - account?.let { email -> - preferences.accounts.forEach { - if (it.email == email) { - accountRemover.removeAccountAsync(it.uuid) - return@forEach - } + val account = intent.extras?.getString("account") ?: return + preferences.accounts.forEach { + if (it.email == account) { + accountRemover.removeAccountAsync(it.uuid) + return@forEach } } } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java index cfb45a6005..c4524a829d 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java @@ -64,8 +64,10 @@ public class EeloAccountCreator { loadEeloAccounts(context, accounts, accountManager); loadGoogleAccounts(context, accounts, accountManager); + } catch (SecurityException e) { + Timber.e(e, "Failed to load accounts from accountManager because of security violation"); } catch (Exception e) { - Timber.e(e); + Timber.e(e, "Failed to load accounts from accountManager"); } if (callBack != null) { callBack.onAccountLoadComplete(); -- GitLab From 173f88372e07fc44f46a0043417891a347801b1d Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Thu, 7 Jul 2022 13:22:07 +0600 Subject: [PATCH 3/3] remove extra catch block from loadAccountsFromAccountManager --- .../k9/activity/setup/accountmanager/EeloAccountCreator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java index c4524a829d..07571c3ef3 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/accountmanager/EeloAccountCreator.java @@ -66,8 +66,6 @@ public class EeloAccountCreator { loadGoogleAccounts(context, accounts, accountManager); } catch (SecurityException e) { Timber.e(e, "Failed to load accounts from accountManager because of security violation"); - } catch (Exception e) { - Timber.e(e, "Failed to load accounts from accountManager"); } if (callBack != null) { callBack.onAccountLoadComplete(); -- GitLab