diff --git a/app/core/src/main/AndroidManifest.xml b/app/core/src/main/AndroidManifest.xml index e4c26a20a16b8f63ae8e50a5ff7aa8a858c4d620..a3ae99a7bb178275aa5797fd6a10ea22a7eee73f 100644 --- a/app/core/src/main/AndroidManifest.xml +++ b/app/core/src/main/AndroidManifest.xml @@ -2,6 +2,12 @@ + + diff --git a/app/core/src/main/java/com/fsck/k9/OsAccountManagerUtil.kt b/app/core/src/main/java/com/fsck/k9/OsAccountManagerUtil.kt index 9229ca256520e8bb69897b7f022fb4e189c66e56..3c2330118ea03c1c81accaa97112d0a8cca05e38 100644 --- a/app/core/src/main/java/com/fsck/k9/OsAccountManagerUtil.kt +++ b/app/core/src/main/java/com/fsck/k9/OsAccountManagerUtil.kt @@ -32,30 +32,35 @@ object OsAccountManagerUtil { fun isSyncEnable(context: Context, email: String): Boolean { val accountManager = AccountManager.get(context) - val murenaAccounts = accountManager.getAccountsByType(AccountManagerConstants.EELO_ACCOUNT_TYPE) - var syncEnable = isSyncEnable(accountManager, murenaAccounts, email) - if (syncEnable.isAccountFound()) { - return syncEnable.getStatus() - } + var syncStatus = true - val googleAccounts = accountManager.getAccountsByType(AccountManagerConstants.GOOGLE_ACCOUNT_TYPE) - syncEnable = isSyncEnable(accountManager, googleAccounts, email) - if (syncEnable.isAccountFound()) { - return syncEnable.getStatus() + AccountManagerConstants.ALL_ACCOUNT_TYPES.forEach { + val accounts = accountManager.getAccountsByType(it) + val syncEnable = isSyncEnable(accountManager, accounts, email) + if (syncEnable.isAccountFound()) { + syncStatus = syncEnable.getStatus() + return@forEach + } } - return true + return syncStatus } private fun isSyncEnable(accountManager: AccountManager, accounts: Array, email: String): Syncable { accounts.forEach { try { - val accountEmail: String = accountManager.getUserData(it, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY) + val accountEmail: String = + accountManager.getUserData(it, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY) if (accountEmail == email) { // if master sync disable, then account sync is disable return if (!ContentResolver.getMasterSyncAutomatically()) { Syncable.NOT_SYNCABLE - } else Syncable.getSyncable(ContentResolver.getSyncAutomatically(it, AccountManagerConstants.MAIL_CONTENT_AUTHORITY)) + } else Syncable.getSyncable( + ContentResolver.getSyncAutomatically( + it, + AccountManagerConstants.MAIL_CONTENT_AUTHORITY, + ), + ) } } catch (e: Throwable) { Timber.e(e) @@ -78,4 +83,4 @@ object OsAccountManagerUtil { fun isAccountFound() = this != ACCOUNT_NOT_FOUND } -} \ No newline at end of file +} diff --git a/app/core/src/main/java/com/fsck/k9/setup/AccountManagerConstants.kt b/app/core/src/main/java/com/fsck/k9/setup/AccountManagerConstants.kt index b70aad6478f429b13042a57ab6a054e5d46a2f52..2cb7c4bc013d9385871ce662a33f83b57a1ba800 100644 --- a/app/core/src/main/java/com/fsck/k9/setup/AccountManagerConstants.kt +++ b/app/core/src/main/java/com/fsck/k9/setup/AccountManagerConstants.kt @@ -19,6 +19,7 @@ package com.fsck.k9.setup object AccountManagerConstants { const val EELO_ACCOUNT_TYPE = "e.foundation.webdav.eelo" const val GOOGLE_ACCOUNT_TYPE = "e.foundation.webdav.google" + const val YAHOO_ACCOUNT_TYPE = "e.foundation.webdav.yahoo" const val ACCOUNT_EMAIL_ADDRESS_KEY = "email_address" const val MAIL_CONTENT_AUTHORITY = "foundation.e.mail.provider.AppContentProvider" const val AUTH_TOKEN_TYPE = "oauth2-access-token" @@ -28,4 +29,15 @@ object AccountManagerConstants { const val OPEN_APP_ACTIVITY_AFTER_AUTH = "open_app_activity_after_auth" const val IGNORE_ACCOUNT_SETUP = "ignore_account_setup" + + val OPENID_ACCOUNT_TYPES = listOf(GOOGLE_ACCOUNT_TYPE, YAHOO_ACCOUNT_TYPE) + val ALL_ACCOUNT_TYPES = listOf(EELO_ACCOUNT_TYPE, GOOGLE_ACCOUNT_TYPE, YAHOO_ACCOUNT_TYPE) + + fun getOpenIdAccountTypeByHostName(hostname: String): String? { + return when(hostname) { + in listOf("imap.gmail.com", "imap.googlemail.com", "smtp.gmail.com", "smtp.googlemail.com") -> GOOGLE_ACCOUNT_TYPE + in listOf("imap.mail.yahoo.com", "smtp.mail.yahoo.com") -> YAHOO_ACCOUNT_TYPE + else -> null + } + } } diff --git a/app/core/src/main/java/com/fsck/k9/setup/EeloAccountHelper.kt b/app/core/src/main/java/com/fsck/k9/setup/EeloAccountHelper.kt index 10b69fbe9ed9866d7b8792eeca12fa6088ee11e2..e273661fcd54f52ec9f098b28c075d10b2e50382 100644 --- a/app/core/src/main/java/com/fsck/k9/setup/EeloAccountHelper.kt +++ b/app/core/src/main/java/com/fsck/k9/setup/EeloAccountHelper.kt @@ -16,13 +16,13 @@ package com.fsck.k9.setup -import android.accounts.Account as OsAccount -import android.accounts.AccountManager as OsAccountManager import android.content.Context import com.fsck.k9.Account import com.fsck.k9.mail.AuthType import com.fsck.k9.preferences.AccountManager import timber.log.Timber +import android.accounts.Account as OsAccount +import android.accounts.AccountManager as OsAccountManager object EeloAccountHelper { @@ -44,10 +44,28 @@ object EeloAccountHelper { } val osAccountManager = OsAccountManager.get(context) - val googleAccount = retrieveGoogleAccountFromAccountManager(osAccountManager, account.email) ?: return false + + var result = false + AccountManagerConstants.OPENID_ACCOUNT_TYPES.forEach { + val openIdAccount = retrieveOpenIdAccountFromAccountManager(osAccountManager, it, account.email) + if (openIdAccount != null) { + updateOAuthState(account, osAccountManager, openIdAccount, accountManager) + result = true + return@forEach + } + } + + return result + } + + private fun updateOAuthState( + account: Account, + osAccountManager: android.accounts.AccountManager, + googleAccount: android.accounts.Account?, + accountManager: AccountManager, + ) { account.oAuthState = osAccountManager.getUserData(googleAccount, AccountManagerConstants.KEY_AUTH_STATE) accountManager.saveAccount(account) - return true } // If token is updated by mail, also update the accountManager @@ -55,7 +73,7 @@ object EeloAccountHelper { context: Context?, account: OsAccount?, authState: String?, - accessToken: String? + accessToken: String?, ) { if (context == null || account == null || authState == null || accessToken == null) { Timber.w("updating account for accountManager failed, invalid param.") @@ -67,30 +85,44 @@ object EeloAccountHelper { accountManager.setUserData(account, AccountManagerConstants.KEY_AUTH_STATE, authState) } - fun retrieveGoogleAccountFromAccountManager(context: Context?, email: String?): OsAccount? { + fun retrieveOpenIdAccountFromAccountManager(context: Context?, email: String?): OsAccount? { if (context == null) { Timber.w("retrieve google accounts from accountManager failed, null context.") return null } val accountManager = OsAccountManager.get(context) - return retrieveGoogleAccountFromAccountManager(accountManager, email) + + var resultAccount: OsAccount? = null + + AccountManagerConstants.OPENID_ACCOUNT_TYPES.forEach { + resultAccount = retrieveOpenIdAccountFromAccountManager(accountManager, it, email) + if (resultAccount != null) { + return@forEach + } + } + + return resultAccount } - private fun retrieveGoogleAccountFromAccountManager(accountManager: OsAccountManager?, email: String?): OsAccount? { - if (accountManager == null || email == null || email.isEmpty()) { - Timber.w("retrieve google account from accountManager failed, invalid param") + private fun retrieveOpenIdAccountFromAccountManager( + accountManager: OsAccountManager?, + accountType: String, + email: String?, + ): OsAccount? { + if (accountManager == null || email.isNullOrEmpty()) { + Timber.w("retrieve $accountType account from accountManager failed, invalid param") return null } - val googleAccounts = accountManager.getAccountsByType(AccountManagerConstants.GOOGLE_ACCOUNT_TYPE) - for (googleAccount in googleAccounts) { - val emailId = accountManager.getUserData(googleAccount, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY) + val openIdAccounts = accountManager.getAccountsByType(accountType) + for (openIdAccount in openIdAccounts) { + val emailId = accountManager.getUserData(openIdAccount, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY) if (email.equals(emailId, ignoreCase = true)) { - return googleAccount + return openIdAccount } } return null } -} \ No newline at end of file +} diff --git a/app/k9mail/src/main/java/com/fsck/k9/backends/RealOAuth2TokenProvider.kt b/app/k9mail/src/main/java/com/fsck/k9/backends/RealOAuth2TokenProvider.kt index be4ac7f6576cd354795a7a92b8ded77ab5084d97..ed65f485a15d297f3446ecb7dfca887b1b24fe45 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/backends/RealOAuth2TokenProvider.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/backends/RealOAuth2TokenProvider.kt @@ -31,7 +31,7 @@ class RealOAuth2TokenProvider( ?: throw AuthenticationFailedException("Login required") } - val accountManagerAccount = EeloAccountHelper.retrieveGoogleAccountFromAccountManager(context, email) + val accountManagerAccount = EeloAccountHelper.retrieveOpenIdAccountFromAccountManager(context, email) val latch = CountDownLatch(1) var token: String? = null 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 a1f9617c8019569988fd36bdac4fda6c9b5250a4..f34820adb7e16bae642c24baa0c263ccbf5f4af4 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 @@ -20,6 +20,8 @@ import android.accounts.AccountManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.Build +import androidx.annotation.RequiresApi import com.fsck.k9.Account import com.fsck.k9.Preferences import com.fsck.k9.activity.setup.accountmanager.EeloAccountCreator @@ -29,7 +31,6 @@ import com.fsck.k9.setup.AccountManagerConstants import java.util.concurrent.Executors import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import timber.log.Timber class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { @@ -39,7 +40,6 @@ class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { private const val ACCOUNT_CREATION_ACTION = "$ACTION_PREFIX.create" private const val ACCOUNT_REMOVAL_ACTION = "android.accounts.action.ACCOUNT_REMOVED" - private val ACCOUNT_TYPES = listOf(AccountManagerConstants.EELO_ACCOUNT_TYPE, AccountManagerConstants.GOOGLE_ACCOUNT_TYPE) } private val pushController: PushController by inject() @@ -47,6 +47,7 @@ class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { private val accountRemover: BackgroundAccountRemover by inject() private val jobManager: K9JobManager by inject() + @RequiresApi(Build.VERSION_CODES.N) override fun onReceive(context: Context?, intent: Intent?) { if (intent == null) { return @@ -58,6 +59,7 @@ class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { } } + @RequiresApi(Build.VERSION_CODES.N) private fun createNewAccount(context: Context?) { pushController.init() context?.let { @@ -81,7 +83,7 @@ class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { private fun getAccount(intent: Intent) : Account? { val accountType = intent.extras?.getString(AccountManager.KEY_ACCOUNT_TYPE) - if (!ACCOUNT_TYPES.contains(accountType)) { + if (!AccountManagerConstants.ALL_ACCOUNT_TYPES.contains(accountType)) { return null } @@ -95,4 +97,4 @@ class AccountSyncReceiver : BroadcastReceiver(), KoinComponent { return null } -} \ No newline at end of file +} 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 5d528da898ff2b5794c037b7dcb6c781df04aa0e..97a18c9241274ba072694663c88049893a0edfdb 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 @@ -137,6 +137,7 @@ class AccountSetupBasics : K9Activity() { advancedOptionsContainer.isVisible = false nextButton.setOnClickListener { attemptAutoSetupUsingOnlyEmailAddress() } } + UiState.PASSWORD_FLOW -> { passwordLayout.isVisible = true advancedOptionsContainer.isVisible = true @@ -211,8 +212,7 @@ class AccountSetupBasics : K9Activity() { } private fun startOAuthFlow(connectionSettings: ConnectionSettings) { - if (oAuthConfigurationProvider.isGoogle(connectionSettings.incoming.host!!)) { - startGoogleOAuthFlow() + if (handleOpenIdAuthFlow(connectionSettings.incoming.host!!)) { return } @@ -222,12 +222,19 @@ class AccountSetupBasics : K9Activity() { startActivityForResult(intent, REQUEST_CODE_OAUTH) } - private fun startGoogleOAuthFlow() { + private fun handleOpenIdAuthFlow(hostname: String): Boolean { + val accountType = AccountManagerConstants.getOpenIdAccountTypeByHostName(hostname) ?: return false + + startOpenIdOAuthFlow(accountType) + return true + } + + private fun startOpenIdOAuthFlow(accountType: String) { val osAccountManager = AccountManager.get(this) val options = Bundle() options.putString(AccountManagerConstants.OPEN_APP_PACKAGE_AFTER_AUTH, packageName) options.putString(AccountManagerConstants.OPEN_APP_ACTIVITY_AFTER_AUTH, MessageList::class.java.name) - osAccountManager.addAccount(AccountManagerConstants.GOOGLE_ACCOUNT_TYPE, null, null, options, this, null, null) + osAccountManager.addAccount(accountType, null, null, options, this, null, null) } private fun startPasswordFlow() { 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 7d550abfcab54a43c277d9c782bf78a82f0bf588..4cc9cd0077e21b0713baf522f499337af551c1f0 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 @@ -65,7 +65,8 @@ public class EeloAccountCreator { deleteIncompleteAccounts(accounts, accountRemover); loadEeloAccounts(context, accounts, accountManager, jobManager); - loadGoogleAccounts(context, accounts, accountManager, jobManager); + + AccountManagerConstants.INSTANCE.getOPENID_ACCOUNT_TYPES().forEach(accountType -> loadOpenIdAccounts(context, accountType, accounts, accountManager, jobManager)); } catch (SecurityException e) { Timber.e(e, "Failed to load accounts from accountManager because of security violation"); } @@ -89,18 +90,18 @@ public class EeloAccountCreator { } @RequiresApi(api = VERSION_CODES.N) - private static void loadGoogleAccounts(@NonNull Context context, List accounts, + private static void loadOpenIdAccounts(@NonNull Context context, @NonNull String accountType, List accounts, @NonNull AccountManager accountManager, @NonNull K9JobManager jobManager) { - android.accounts.Account[] googleAccounts = - accountManager.getAccountsByType(AccountManagerConstants.GOOGLE_ACCOUNT_TYPE); - for (android.accounts.Account googleAccount : googleAccounts) { + android.accounts.Account[] openIdAccounts = + accountManager.getAccountsByType(accountType); + for (android.accounts.Account openIdAccount : openIdAccounts) { String emailId = - accountManager.getUserData(googleAccount, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY); + accountManager.getUserData(openIdAccount, AccountManagerConstants.ACCOUNT_EMAIL_ADDRESS_KEY); if (isInvalidEmail(emailId)) { continue; } - if (!isSyncable(googleAccount)) { + if (!isSyncable(openIdAccount)) { continue; } @@ -110,7 +111,7 @@ public class EeloAccountCreator { .findAny(); if (!existenceAccount.isPresent()) { - String authState = accountManager.getUserData(googleAccount, AccountManagerConstants.KEY_AUTH_STATE); + String authState = accountManager.getUserData(openIdAccount, AccountManagerConstants.KEY_AUTH_STATE); createAccount(context, emailId, "", authState); continue; }