diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 18e97d13d6293b75f9ba5e9a7d53d3602853a016..00aaf4765e5a3912efb6656a67ecf37648377029 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -256,16 +256,16 @@ dependencies { implementation(libs.commons.codec) implementation(libs.commons.lang) - // e-Specific dependencies - (Avoid moving into toml) - implementation("foundation.e:elib:0.0.1-alpha11") - implementation("foundation.e:synctools:58bc6752") { + // e-Specific dependencies + implementation(libs.elib) + implementation(libs.ez.vcard) + implementation(libs.synctools) { exclude(group="androidx.test") exclude(group = "junit") } - implementation("org.mnode.ical4j:ical4j:3.2.19") { + implementation(libs.ical4j) { exclude(group = "commons-logging", module = "commons-logging") } - implementation("com.googlecode.ez-vcard:ez-vcard:0.12.1") // for tests androidTestImplementation(libs.androidx.arch.core.testing) diff --git a/app/src/androidTest/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStoreTest.kt b/app/src/androidTest/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStoreTest.kt index f4f92690d45631e9700e54bacb4383123d2b01e9..8d1409f0c8494f8b943fb80a30ecce51ab75f4d0 100644 --- a/app/src/androidTest/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStoreTest.kt +++ b/app/src/androidTest/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStoreTest.kt @@ -141,7 +141,7 @@ class LocalAddressBookStoreTest { } mockkObject(localAddressBookStore) - every { localAddressBookStore.createAddressBookAccount(any(), any(), any()) } returns null + every { localAddressBookStore.createAddressBookAccount(any(), any(), any(), any()) } returns null assertEquals(null, localAddressBookStore.create(provider, collection)) } diff --git a/app/src/androidTest/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt b/app/src/androidTest/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt index ee544093615271c34721dfe8b470b8b414378d17..c37523a9c1df37ad18fb346a4bb690e538ad15ad 100644 --- a/app/src/androidTest/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt +++ b/app/src/androidTest/kotlin/at/bitfire/davdroid/sync/SyncerTest.kt @@ -219,7 +219,7 @@ class SyncerTest { throw NotImplementedError() } - override fun updateAccount(oldAccount: Account, newAccount: Account) { + override fun updateAccount(oldAccount: Account, newAccount: Account, accountType: String) { throw NotImplementedError() } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/db/AppDatabase.kt b/app/src/main/kotlin/at/bitfire/davdroid/db/AppDatabase.kt index 3b2df226f97fd79505f8f6b26092864d2ffe4895..738d9794482c5b2ec84b0d281b19dceb44b6560f 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/db/AppDatabase.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/db/AppDatabase.kt @@ -33,6 +33,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import foundation.e.accountmanager.utils.AccountHelper import java.io.Writer import javax.inject.Singleton @@ -97,7 +98,7 @@ abstract class AppDatabase: RoomDatabase() { // remove all accounts because they're unfortunately useless without database val am = AccountManager.get(context) - for (account in am.getAccountsByType(context.getString(R.string.account_type))) + for (account in AccountHelper.getAllAccounts(am)) am.removeAccountExplicitly(account) } }) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/push/PushMessageHandler.kt b/app/src/main/kotlin/at/bitfire/davdroid/push/PushMessageHandler.kt index 8c36463bd6a90c917ddcc95cd38a9e4b133f5b7e..a59625ea387bbd8c1e04594e9d6fc0c205002567 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/push/PushMessageHandler.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/push/PushMessageHandler.kt @@ -15,6 +15,7 @@ import at.bitfire.davdroid.sync.SyncDataType import at.bitfire.davdroid.sync.TasksAppManager import at.bitfire.davdroid.sync.worker.SyncWorkerManager import dagger.Lazy +import foundation.e.accountmanager.AccountTypes import org.unifiedpush.android.connector.data.PushMessage import org.xmlpull.v1.XmlPullParserException import java.io.StringReader @@ -70,7 +71,7 @@ class PushMessageHandler @Inject constructor( syncDataTypes += SyncDataType.TASKS // Schedule sync for all the types identified - val account = accountRepository.fromName(service.accountName) + val account = accountRepository.fromName(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) for (syncDataType in syncDataTypes) syncWorkerManager.enqueueOneTime(account, syncDataType, fromPush = true) } @@ -81,7 +82,7 @@ class PushMessageHandler @Inject constructor( val service = instance.toLongOrNull()?.let { serviceRepository.getBlocking(it) } if (service != null) { logger.warning("Got push message without topic and service, syncing all accounts") - val account = accountRepository.fromName(service.accountName) + val account = accountRepository.fromName(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) syncWorkerManager.enqueueOneTimeAllAuthorities(account, fromPush = true) } else { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/push/PushRegistrationManager.kt b/app/src/main/kotlin/at/bitfire/davdroid/push/PushRegistrationManager.kt index ad4e882d4e7b8ab0a817b01a58a9b049bdec2961..0d912facc0bc2e66c58c0227f68deed4fb1d22a5 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/push/PushRegistrationManager.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/push/PushRegistrationManager.kt @@ -34,6 +34,7 @@ import at.bitfire.davdroid.repository.DavServiceRepository import at.bitfire.davdroid.sync.account.InvalidAccountException import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runInterruptible @@ -179,7 +180,7 @@ class PushRegistrationManager @Inject constructor( if (subscribeTo.isEmpty()) return - val account = accountRepository.get().fromName(service.accountName) + val account = accountRepository.get().fromName(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) httpClientBuilder.get() .fromAccountAsync(account) .build() @@ -293,7 +294,7 @@ class PushRegistrationManager @Inject constructor( if (from.isEmpty()) return - val account = accountRepository.get().fromName(service.accountName) + val account = accountRepository.get().fromName(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) httpClientBuilder.get() .fromAccountAsync(account) .build() diff --git a/app/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt b/app/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt index f83a3e583f31e7e5f1e27d2a845571c556c28bb3..49e8a87a3621e3c1fff658e601efc13d48332d3e 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt @@ -8,7 +8,6 @@ import android.accounts.Account import android.accounts.AccountManager import android.accounts.OnAccountsUpdateListener import android.content.Context -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.HomeSet import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.db.ServiceType @@ -28,6 +27,8 @@ import at.bitfire.davdroid.sync.worker.SyncWorkerManager import at.bitfire.vcard4android.GroupMethod import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes +import foundation.e.accountmanager.utils.AccountHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow @@ -56,7 +57,6 @@ class AccountRepository @Inject constructor( private val tasksAppManager: Lazy ) { - private val accountType = context.getString(R.string.account_type) private val accountManager = AccountManager.get(context) /** @@ -70,8 +70,8 @@ class AccountRepository @Inject constructor( * * @return account if account creation was successful; null otherwise (for instance because an account with this name already exists) */ - fun createBlocking(accountName: String, credentials: Credentials?, config: DavResourceFinder.Configuration, groupMethod: GroupMethod): Account? { - val account = fromName(accountName) + fun createBlocking(accountName: String, accountType: String, credentials: Credentials?, config: DavResourceFinder.Configuration, groupMethod: GroupMethod): Account? { + val account = fromName(accountName, accountType) // create Android account val userData = AccountSettings.initialUserData(credentials) @@ -113,8 +113,8 @@ class AccountRepository @Inject constructor( return account } - suspend fun delete(accountName: String): Boolean { - val account = fromName(accountName) + suspend fun delete(accountName: String, accountType: String): Boolean { + val account = fromName(accountName, accountType) // remove account directly (bypassing the authenticator, which is our own) return try { accountManager.removeAccountExplicitly(account) @@ -136,7 +136,7 @@ class AccountRepository @Inject constructor( } } - fun exists(accountName: String): Boolean = + fun exists(accountName: String, accountType: String): Boolean = if (accountName.isEmpty()) false else @@ -144,14 +144,16 @@ class AccountRepository @Inject constructor( .getAccountsByType(accountType) .any { it.name == accountName } - fun fromName(accountName: String) = + fun fromName(accountName: String, accountType: String) = Account(accountName, accountType) - fun getAll(): Array = accountManager.getAccountsByType(accountType) + fun getAll(): Array { + return AccountHelper.getAllAccounts(accountManager) + } fun getAllFlow() = callbackFlow> { val listener = OnAccountsUpdateListener { accounts -> - trySend(accounts.filter { it.type == accountType }.toSet()) + trySend(accounts.filter { it.type in AccountTypes.getAccountTypes() }.toSet()) } withContext(Dispatchers.Default) { // causes disk I/O accountManager.addOnAccountsUpdatedListener(listener, null, true) @@ -175,12 +177,12 @@ class AccountRepository @Inject constructor( * @throws IllegalArgumentException if the new account name already exists * @throws Exception (or sub-classes) on other errors */ - suspend fun rename(oldName: String, newName: String) { - val oldAccount = fromName(oldName) - val newAccount = fromName(newName) + suspend fun rename(accountType: String, oldName: String, newName: String) { + val oldAccount = fromName(accountType, oldName) + val newAccount = fromName(accountType, newName) // check whether new account name already exists - if (accountManager.getAccountsByType(context.getString(R.string.account_type)).contains(newAccount)) + if (accountManager.getAccountsByType(accountType).contains(newAccount)) throw IllegalArgumentException("Account with name \"$newName\" already exists") // rename account @@ -215,16 +217,18 @@ class AccountRepository @Inject constructor( // update account name references in database serviceRepository.renameAccount(oldName, newName) + val addressBookAccountType = AccountTypes.getAddressBookForAccountType(accountType) + try { // update address books - localAddressBookStore.get().updateAccount(oldAccount, newAccount) + localAddressBookStore.get().updateAccount(oldAccount, newAccount, addressBookAccountType) } catch (e: Exception) { logger.log(Level.WARNING, "Couldn't change address books to renamed account", e) } try { // update calendar events - localCalendarStore.get().updateAccount(oldAccount, newAccount) + localCalendarStore.get().updateAccount(oldAccount, newAccount, accountType) } catch (e: Exception) { logger.log(Level.WARNING, "Couldn't change calendars to renamed account", e) } @@ -232,7 +236,7 @@ class AccountRepository @Inject constructor( try { // update account_name of local tasks val dataStore = tasksAppManager.get().getDataStore() - dataStore?.updateAccount(oldAccount, newAccount) + dataStore?.updateAccount(oldAccount, newAccount, accountType) } catch (e: Exception) { logger.log(Level.WARNING, "Couldn't change task lists to renamed account", e) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/repository/DavCollectionRepository.kt b/app/src/main/kotlin/at/bitfire/davdroid/repository/DavCollectionRepository.kt index ddb4a70a3a32f3ca8bcd51d540755fceeea48466..5271d5441d60e7eea9feaac7420f246158244f10 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/repository/DavCollectionRepository.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/repository/DavCollectionRepository.kt @@ -24,7 +24,6 @@ import at.bitfire.dav4jvm.property.webdav.DisplayName import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV import at.bitfire.dav4jvm.property.webdav.ResourceType import at.bitfire.davdroid.Constants -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.CollectionType @@ -34,6 +33,7 @@ import at.bitfire.davdroid.network.HttpClient import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker import at.bitfire.davdroid.util.DavUtils import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.runInterruptible import net.fortuna.ical4j.model.Calendar @@ -170,7 +170,7 @@ class DavCollectionRepository @Inject constructor( /** Deletes the given collection from the server and the database. */ suspend fun deleteRemote(collection: Collection) { val service = serviceRepository.getBlocking(collection.serviceId) ?: throw IllegalArgumentException("Service not found") - val account = Account(service.accountName, context.getString(R.string.account_type)) + val account = Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) httpClientBuilder.get().fromAccount(account).build().use { httpClient -> runInterruptible(ioDispatcher) { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBook.kt b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBook.kt index 1037010fd0d5e1234ecf68d1946361de8bd73d22..e38a85c1d1c66dcfd175bc85ca9991340eb9290b 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBook.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBook.kt @@ -36,6 +36,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import java.util.LinkedList import java.util.Optional import java.util.logging.Level @@ -94,7 +95,7 @@ open class LocalAddressBook @AssistedInject constructor( val account = accountManager.getUserData(addressBookAccount, USER_DATA_COLLECTION_ID)?.toLongOrNull()?.let { collectionId -> collectionRepository.get(collectionId)?.let { collection -> serviceRepository.getBlocking(collection.serviceId)?.let { service -> - Account(service.accountName, context.getString(R.string.account_type)) + Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) } } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStore.kt b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStore.kt index 6fbb821e9c86a7eb7c808fb5a640fe33af0b7aa3..4c480385a905f00141029eac1e9739cf1ed66921 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStore.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalAddressBookStore.kt @@ -16,6 +16,7 @@ import androidx.core.content.contentValuesOf import androidx.core.os.bundleOf import at.bitfire.davdroid.R import at.bitfire.davdroid.db.Collection +import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.repository.DavServiceRepository import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager @@ -24,6 +25,8 @@ import at.bitfire.davdroid.sync.account.setAndVerifyUserData import at.bitfire.davdroid.util.DavUtils.lastSegment import com.google.common.base.CharMatcher import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes +import foundation.e.accountmanager.utils.AccountHelper import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -89,10 +92,11 @@ class LocalAddressBookStore @Inject constructor( override fun create(provider: ContentProviderClient, fromCollection: Collection): LocalAddressBook? { val service = serviceRepository.getBlocking(fromCollection.serviceId) ?: throw IllegalArgumentException("Couldn't fetch DB service from collection") - val account = Account(service.accountName, context.getString(R.string.account_type)) + val account = Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) val name = accountName(fromCollection) val addressBookAccount = createAddressBookAccount( + service = service, account = account, name = name, id = fromCollection.id @@ -109,9 +113,9 @@ class LocalAddressBookStore @Inject constructor( } @OpenForTesting - internal fun createAddressBookAccount(account: Account, name: String, id: Long): Account? { + internal fun createAddressBookAccount(service: Service, account: Account, name: String, id: Long): Account? { // create address book account with reference to account, collection ID and URL - val addressBookAccount = Account(name, context.getString(R.string.account_type_address_book)) + val addressBookAccount = Account(name, AccountTypes.getAddressBookTypeFromPrincipal(service.principal)) val userData = bundleOf( LocalAddressBook.USER_DATA_ACCOUNT_NAME to account.name, LocalAddressBook.USER_DATA_ACCOUNT_TYPE to account.type, @@ -168,9 +172,9 @@ class LocalAddressBookStore @Inject constructor( * @param oldAccount The old account * @param newAccount The new account */ - override fun updateAccount(oldAccount: Account, newAccount: Account) { + override fun updateAccount(oldAccount: Account, newAccount: Account, accountType: String) { val accountManager = AccountManager.get(context) - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) + accountManager.getAccountsByType(AccountTypes.getAddressBookForAccountType(accountType)) .filter { addressBookAccount -> accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_NAME) == oldAccount.name && accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_TYPE) == oldAccount.type @@ -193,7 +197,7 @@ class LocalAddressBookStore @Inject constructor( */ fun deleteByCollectionId(id: Long) { val accountManager = AccountManager.get(context) - val addressBookAccount = accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)).firstOrNull { account -> + val addressBookAccount = AccountHelper.getAllAddressBooks(accountManager).firstOrNull { account -> accountManager.getUserData(account, LocalAddressBook.USER_DATA_COLLECTION_ID)?.toLongOrNull() == id } if (addressBookAccount != null) @@ -208,7 +212,7 @@ class LocalAddressBookStore @Inject constructor( */ fun getAddressBookAccounts(account: Account): List = AccountManager.get(context).let { accountManager -> - accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) + AccountHelper.getAllAddressBooks(accountManager) .filter { addressBookAccount -> account.name == accountManager.getUserData( addressBookAccount, diff --git a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalCalendarStore.kt b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalCalendarStore.kt index 21eb8edcc3720f9505861146b6734c23f0ff1b69..3d32c843ce048ae24d925178fea91a01b9455390 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalCalendarStore.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalCalendarStore.kt @@ -24,6 +24,7 @@ import at.bitfire.ical4android.util.DateUtils import at.bitfire.ical4android.util.MiscUtils.asSyncAdapter import at.bitfire.synctools.storage.calendar.AndroidCalendarProvider import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import java.util.logging.Level import java.util.logging.Logger import javax.inject.Inject @@ -50,7 +51,7 @@ class LocalCalendarStore @Inject constructor( override fun create(client: ContentProviderClient, fromCollection: Collection): LocalCalendar? { val service = serviceRepository.getBlocking(fromCollection.serviceId) ?: throw IllegalArgumentException("Couldn't fetch DB service from collection") - val account = Account(service.accountName, context.getString(R.string.account_type)) + val account = Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) // If the collection doesn't have a color, use a default color. val collectionWithColor = @@ -138,7 +139,7 @@ class LocalCalendarStore @Inject constructor( return values } - override fun updateAccount(oldAccount: Account, newAccount: Account) { + override fun updateAccount(oldAccount: Account, newAccount: Account, accountType: String) { val values = contentValuesOf(Calendars.ACCOUNT_NAME to newAccount.name) val uri = Calendars.CONTENT_URI.asSyncAdapter(oldAccount) context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.use { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalDataStore.kt b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalDataStore.kt index 8e8a7e4c4f2de78a0f810ee9a4ad9fbea405e87f..5edac05f237d68a272df712b838cc5c774a766ea 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalDataStore.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalDataStore.kt @@ -77,6 +77,6 @@ interface LocalDataStore> { * @param oldAccount The old account. * @param newAccount The new account. */ - fun updateAccount(oldAccount: Account, newAccount: Account) + fun updateAccount(oldAccount: Account, newAccount: Account, accountType: String) } \ No newline at end of file diff --git a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalJtxCollectionStore.kt b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalJtxCollectionStore.kt index 856af2de748ae21904365b7aaaed752ae1afd5a1..e802efbb83afd26d0b28c4836436a5f8f39a0695 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalJtxCollectionStore.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalJtxCollectionStore.kt @@ -11,7 +11,6 @@ import android.content.ContentValues import android.content.Context import androidx.core.content.contentValuesOf import at.bitfire.davdroid.Constants -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.repository.PrincipalRepository @@ -22,6 +21,7 @@ import at.bitfire.ical4android.TaskProvider import at.techbee.jtx.JtxContract import at.techbee.jtx.JtxContract.asSyncAdapter import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import java.util.logging.Logger import javax.inject.Inject @@ -48,7 +48,7 @@ class LocalJtxCollectionStore @Inject constructor( override fun create(provider: ContentProviderClient, fromCollection: Collection): LocalJtxCollection? { val service = serviceDao.get(fromCollection.serviceId) ?: throw IllegalArgumentException("Couldn't fetch DB service from collection") - val account = Account(service.accountName, context.getString(R.string.account_type)) + val account = Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) // If the collection doesn't have a color, use a default color. val collectionWithColor = @@ -103,7 +103,7 @@ class LocalJtxCollectionStore @Inject constructor( localCollection.update(values) } - override fun updateAccount(oldAccount: Account, newAccount: Account) { + override fun updateAccount(oldAccount: Account, newAccount: Account, accountType: String) { TaskProvider.acquire(context, TaskProvider.ProviderName.JtxBoard)?.use { provider -> val values = contentValuesOf(JtxContract.JtxCollection.ACCOUNT_NAME to newAccount.name) val uri = JtxContract.JtxCollection.CONTENT_URI.asSyncAdapter(oldAccount) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalTaskListStore.kt b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalTaskListStore.kt index 80bd8e12c14afcbd520284cfcf54ea143b048ec8..e061c1efcf9932f1469fc76b9be820a6d8671d45 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalTaskListStore.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/resource/LocalTaskListStore.kt @@ -23,6 +23,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import org.dmfs.tasks.contract.TaskContract.TaskListColumns import org.dmfs.tasks.contract.TaskContract.TaskLists import org.dmfs.tasks.contract.TaskContract.Tasks @@ -58,7 +59,7 @@ class LocalTaskListStore @AssistedInject constructor( override fun create(provider: ContentProviderClient, fromCollection: Collection): LocalTaskList? { val service = serviceDao.get(fromCollection.serviceId) ?: throw IllegalArgumentException("Couldn't fetch DB service from collection") - val account = Account(service.accountName, context.getString(R.string.account_type)) + val account = Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) logger.log(Level.INFO, "Adding local task list", fromCollection) val uri = create(account, provider, providerName, fromCollection) @@ -109,7 +110,7 @@ class LocalTaskListStore @AssistedInject constructor( localCollection.update(valuesFromCollectionInfo(fromCollection, withColor = accountSettings.getManageCalendarColors())) } - override fun updateAccount(oldAccount: Account, newAccount: Account) { + override fun updateAccount(oldAccount: Account, newAccount: Account, accountType: String) { TaskProvider.acquire(context, providerName)?.use { provider -> val values = contentValuesOf(Tasks.ACCOUNT_NAME to newAccount.name) val uri = Tasks.getContentUri(providerName.authority) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt b/app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt index 552f334f25a07789ac59906b4d20a6e4b3892449..5d48e1da45d09dc0804e24408cfc99866ce21d97 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/servicedetection/RefreshCollectionsWorker.kt @@ -34,6 +34,7 @@ import at.bitfire.davdroid.ui.NotificationRegistry import at.bitfire.davdroid.ui.account.AccountSettingsActivity import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.flow.map import kotlinx.coroutines.runInterruptible import java.util.logging.Level @@ -136,7 +137,7 @@ class RefreshCollectionsWorker @AssistedInject constructor( val serviceId: Long = inputData.getLong(ARG_SERVICE_ID, -1) val service = serviceRepository.getBlocking(serviceId) val account = service?.let { service -> - Account(service.accountName, applicationContext.getString(R.string.account_type)) + Account(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) } override suspend fun doWork(): Result { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt index d3ee0a82bc217daffc50d0b7e7bf56acf5d34bbf..9021818aa45953ee86355f3ef2f55e58fcbd2c0b 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt @@ -10,7 +10,6 @@ import android.os.Bundle import android.os.Looper import androidx.annotation.WorkerThread import androidx.core.os.bundleOf -import at.bitfire.davdroid.R import at.bitfire.davdroid.settings.AccountSettings.Companion.CREDENTIALS_LOCK import at.bitfire.davdroid.settings.AccountSettings.Companion.CREDENTIALS_LOCK_AT_LOGIN_AND_SETTINGS import at.bitfire.davdroid.settings.migration.AccountSettingsMigration @@ -24,6 +23,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.AccountTypes import net.openid.appauth.AuthState import java.util.Collections import java.util.logging.Level @@ -69,7 +69,7 @@ class AccountSettings @AssistedInject constructor( val accountManager: AccountManager = AccountManager.get(context) init { val allowedAccountTypes = arrayOf( - context.getString(R.string.account_type), + *AccountTypes.getAccountTypes().toTypedArray(), "at.bitfire.davdroid.test" // R.strings.account_type_test in androidTest ) if (!allowedAccountTypes.contains(account.type)) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration17.kt b/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration17.kt index 7c255ddadaadfa81a076a77dc718e7a216b961cf..48c6b3b882c1941869c27bd9cc35c62968c6a243 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration17.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration17.kt @@ -8,7 +8,6 @@ import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.provider.ContactsContract -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.repository.DavCollectionRepository import at.bitfire.davdroid.repository.DavServiceRepository @@ -22,6 +21,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntKey import dagger.multibindings.IntoMap +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.runBlocking import java.util.logging.Level import java.util.logging.Logger @@ -41,7 +41,11 @@ class AccountSettingsMigration17 @Inject constructor( ): AccountSettingsMigration { override fun migrate(account: Account) { - val addressBookAccountType = context.getString(R.string.account_type_address_book) + val addressBookAccountType = if (account.type in AccountTypes.getAddressBookTypes()) { + account.type + } else { + AccountTypes.getAddressBookForAccountType(account.type) + } try { context.contentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY) } catch (e: SecurityException) { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration18.kt b/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration18.kt index a855460cbd65de3ba8b4f8843812c09f4040bab6..b47de45441cfede9ec262784f1690fe2779d6802 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration18.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/settings/migration/AccountSettingsMigration18.kt @@ -7,7 +7,6 @@ package at.bitfire.davdroid.settings.migration import android.accounts.Account import android.accounts.AccountManager import android.content.Context -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.resource.LocalAddressBook @@ -19,6 +18,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntKey import dagger.multibindings.IntoMap +import foundation.e.accountmanager.utils.AccountHelper import kotlinx.coroutines.runBlocking import javax.inject.Inject @@ -44,8 +44,7 @@ class AccountSettingsMigration18 @Inject constructor( db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV)?.let { service -> db.collectionDao().getByService(service.id).forEach { collection -> // Find associated address book account by collection ID (if it exists) - val addressBookAccount = accountManager - .getAccountsByType(context.getString(R.string.account_type_address_book)) + val addressBookAccount = AccountHelper.getAllAddressBooks(accountManager) .firstOrNull { accountManager.getUserData( it, diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/account/AccountsCleanupWorker.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/account/AccountsCleanupWorker.kt index 5278237e513cca86b63fb5dc138bb161d062bca0..dab70f5c6b684050a0dc33db23d33bc164cb95ac 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/account/AccountsCleanupWorker.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/account/AccountsCleanupWorker.kt @@ -21,6 +21,7 @@ import at.bitfire.davdroid.resource.LocalAddressBook import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import foundation.e.accountmanager.utils.AccountHelper import java.time.Duration import java.util.concurrent.Semaphore import java.util.logging.Level @@ -77,7 +78,7 @@ class AccountsCleanupWorker @AssistedInject constructor( @VisibleForTesting internal fun cleanUpAddressBooks() { val accounts = accountRepository.getAll() - for (addressBookAccount in accountManager.getAccountsByType(context.getString(R.string.account_type_address_book))) { + for (addressBookAccount in AccountHelper.getAllAddressBooks(accountManager)) { val accountName = accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_NAME) val accountType = accountManager.getUserData(addressBookAccount, LocalAddressBook.USER_DATA_ACCOUNT_TYPE) if (!accounts.any { it.name == accountName && it.type == accountType }) { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/sync/adapter/SyncAdapterImpl.kt b/app/src/main/kotlin/at/bitfire/davdroid/sync/adapter/SyncAdapterImpl.kt index 9a5666029e16c6012bfcf608f93988382261616f..2b956065dd1321b397e8ab2e93a687de6a487bba 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/sync/adapter/SyncAdapterImpl.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/sync/adapter/SyncAdapterImpl.kt @@ -31,6 +31,7 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -80,14 +81,14 @@ class SyncAdapterImpl @Inject constructor( logger.info("Sync request via sync framework for $accountOrAddressBookAccount $authority (upload=$upload)") // If we should sync an address book account - find the account storing the settings - val account = if (accountOrAddressBookAccount.type == context.getString(R.string.account_type_address_book)) + val account = if (accountOrAddressBookAccount.type in AccountTypes.getAddressBookTypes()) AccountManager.get(context) .getUserData(accountOrAddressBookAccount, LocalAddressBook.USER_DATA_COLLECTION_ID) ?.toLongOrNull() ?.let { collectionId -> collectionRepository.get(collectionId)?.let { collection -> serviceRepository.getBlocking(collection.serviceId)?.let { service -> - Account(service.accountName, context.getString(R.string.account_type)) + Account(service.accountName, AccountTypes.getAccountTypeForAddressBook(accountOrAddressBookAccount.type)) } } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/CollectionSelectedUseCase.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/CollectionSelectedUseCase.kt index 631ba4f525a4bf35a7378ef39aeec306c8f3c093..7452b3bc8ddd34781440e18126431e1dd72ef649 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/CollectionSelectedUseCase.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/CollectionSelectedUseCase.kt @@ -13,6 +13,7 @@ import at.bitfire.davdroid.repository.DavCollectionRepository import at.bitfire.davdroid.repository.DavServiceRepository import at.bitfire.davdroid.sync.worker.SyncWorkerManager import at.bitfire.davdroid.ui.CollectionSelectedUseCase.Companion.DELAY_MS +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -49,7 +50,7 @@ class CollectionSelectedUseCase @Inject constructor( suspend fun handleWithDelay(collectionId: Long) { val collection = collectionRepository.getAsync(collectionId) ?: return val service = serviceRepository.get(collection.serviceId) ?: return - val account = accountRepository.fromName(service.accountName) + val account = accountRepository.fromName(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) // Atomically cancel, launch and remember delay coroutine of given account delayJobs.compute(account) { _, previousJob -> diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoGenerator.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoGenerator.kt index e3df6c3e90119164cc66d1653ed4ce8ccb8936cb..a0313f050470c3295a978620371d5a6030440bb2 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoGenerator.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoGenerator.kt @@ -33,7 +33,6 @@ import androidx.work.WorkManager import androidx.work.WorkQuery import at.bitfire.dav4jvm.exception.DavException import at.bitfire.davdroid.BuildConfig -import at.bitfire.davdroid.R import at.bitfire.davdroid.TextTable import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.repository.AccountRepository @@ -47,6 +46,7 @@ import at.bitfire.davdroid.sync.worker.BaseSyncWorker import at.bitfire.ical4android.TaskProvider import at.techbee.jtx.JtxContract import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.accountmanager.utils.AccountHelper import org.dmfs.tasks.contract.TaskContract import java.io.PrintWriter import java.io.Writer @@ -314,7 +314,7 @@ class DebugInfoGenerator @Inject constructor( for (account in accounts) dumpAccount(account, writer) - val addressBookAccounts = accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)).toMutableList() + val addressBookAccounts = AccountHelper.getAllAddressBooks(accountManager).toMutableList() if (addressBookAccounts.isNotEmpty()) { writer.append("ADDRESS BOOK ACCOUNTS\n\n") for (account in addressBookAccounts) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt index 2ba465926e942ee9ecdaa5628c0e49c435b58b9e..d60f8bd5cb58568877c040a65c523e7b5cad29be 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt @@ -6,14 +6,15 @@ package at.bitfire.davdroid.ui.account import AccountScreen import android.accounts.Account +import android.accounts.AccountManager import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.core.content.IntentCompat -import at.bitfire.davdroid.R import at.bitfire.davdroid.ui.AccountsActivity import dagger.hilt.android.AndroidEntryPoint +import foundation.e.accountmanager.AccountTypes import java.util.logging.Logger import javax.inject.Inject @@ -28,7 +29,9 @@ class AccountActivity : AppCompatActivity() { val account = IntentCompat.getParcelableExtra(intent, EXTRA_ACCOUNT, Account::class.java) ?: - intent.getStringExtra(EXTRA_ACCOUNT)?.let { Account(it, getString(R.string.account_type)) } + intent.getStringExtra(EXTRA_ACCOUNT)?.let { + Account(it, AccountTypes.Default.accountType) + } // If account is not passed, log warning and redirect to accounts overview if (account == null) { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountScreenModel.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountScreenModel.kt index 9e1607ababded47fec7fcfc5e1836e72611d1662..ac697c7190f76c8676e6499ce65f0553fb6e2216 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountScreenModel.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountScreenModel.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import at.bitfire.davdroid.R import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.repository.AccountRepository import at.bitfire.davdroid.repository.DavCollectionRepository @@ -162,7 +161,7 @@ class AccountScreenModel @AssistedInject constructor( /** Deletes the account from the system (won't touch collections on the server). */ fun deleteAccount() { viewModelScope.launch { - accountRepository.delete(account.name) + accountRepository.delete(account.name, account.type) } } @@ -183,10 +182,10 @@ class AccountScreenModel @AssistedInject constructor( fun renameAccount(newName: String) { viewModelScope.launch { try { - accountRepository.rename(account.name, newName) + accountRepository.rename(account.type, account.name, newName) // synchronize again - val newAccount = Account(newName, context.getString(R.string.account_type)) + val newAccount = Account(newName, account.type) syncWorkerManager.enqueueOneTimeAllAuthorities(newAccount, manual = true) } catch (e: Exception) { logger.log(Level.SEVERE, "Couldn't rename account", e) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionSelectedUseCase.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionSelectedUseCase.kt index 5ee54276c852209353a6cc51b00000ebc09e5f8a..247f173cb9b8692d7b6e29306c541aa62faefe40 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionSelectedUseCase.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionSelectedUseCase.kt @@ -12,6 +12,7 @@ import at.bitfire.davdroid.repository.DavCollectionRepository import at.bitfire.davdroid.repository.DavServiceRepository import at.bitfire.davdroid.sync.worker.SyncWorkerManager import at.bitfire.davdroid.ui.account.CollectionSelectedUseCase.Companion.DELAY_MS +import foundation.e.accountmanager.AccountTypes import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -53,7 +54,7 @@ class CollectionSelectedUseCase @Inject constructor( suspend fun handleWithDelay(collectionId: Long) { val collection = collectionRepository.getAsync(collectionId) ?: return val service = serviceRepository.get(collection.serviceId) ?: return - val account = accountRepository.fromName(service.accountName) + val account = accountRepository.fromName(service.accountName, AccountTypes.getAccountTypeFromPrincipal(service.principal)) // Atomically cancel, launch and remember delay coroutine of given account delayJobs.compute(account) { _, previousJob -> diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt index 3260b1ef92d38017b45ab57cfd6131f0682ca54e..bb2c319a1ad5a8c8901322fa3baba5a20940a7e3 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt @@ -68,7 +68,7 @@ fun AccountDetailsPage( accountName = uiState.accountName, suggestedAccountNames = uiState.suggestedAccountNames, accountNameAlreadyExists = uiState.accountNameExists, - onUpdateAccountName = { model.updateAccountName(it) }, + onUpdateAccountName = { model.updateAccountName(it, uiState.accountType) }, showApostropheWarning = uiState.showApostropheWarning, groupMethod = uiState.groupMethod, groupMethodReadOnly = uiState.groupMethodReadOnly, diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt index 49ab78c6a96b769a69a604d32b58bd62662979d9..fc3a2690a5f9a46d7508e8a9d54afb3d2f79f8cf 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt @@ -103,7 +103,8 @@ class LoginScreenModel @AssistedInject constructor( ?: loginInfo.credentials?.username ?: loginInfo.baseUri?.host ?: "" - updateAccountNameAndEmails(initialAccountName, emails) + val accountType = loginDetailsUiState.loginType.accountType + updateAccountNameAndEmails(initialAccountName, accountType, emails) updateGroupMethod(loginInfo.suggestedGroupMethod) page = Page.AccountDetails } @@ -223,6 +224,7 @@ class LoginScreenModel @AssistedInject constructor( data class AccountDetailsUiState( val accountName: String = "", + val accountType: String = "", val suggestedAccountNames: Set = emptySet(), val accountNameExists: Boolean = false, val groupMethod: GroupMethod = GroupMethod.GROUP_VCARDS, @@ -262,20 +264,22 @@ class LoginScreenModel @AssistedInject constructor( combinedState }.stateIn(viewModelScope, SharingStarted.Lazily, _accountDetailsUiState.value) - fun updateAccountName(accountName: String) { + fun updateAccountName(accountName: String, accountType: String) { _accountDetailsUiState.update { currentState -> currentState.copy( accountName = accountName, - accountNameExists = accountRepository.exists(accountName) + accountType = accountType, + accountNameExists = accountRepository.exists(accountName, accountType) ) } } - fun updateAccountNameAndEmails(accountName: String, emails: Set) { + fun updateAccountNameAndEmails(accountName: String, accountType: String, emails: Set) { _accountDetailsUiState.update { currentState -> currentState.copy( accountName = accountName, - accountNameExists = accountRepository.exists(accountName), + accountType = accountType, + accountNameExists = accountRepository.exists(accountName, accountType), suggestedAccountNames = emails ) } @@ -302,6 +306,7 @@ class LoginScreenModel @AssistedInject constructor( val account = withContext(Dispatchers.Default) { accountRepository.createBlocking( accountDetailsUiState.value.accountName, + accountDetailsUiState.value.accountType, loginInfo.credentials, foundConfig!!, accountDetailsUiState.value.groupMethod diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginType.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginType.kt index 88de99615ec22cfb2feaf6d94435c660c0d28519..174df33aa62814775a4e6d19be61d0ce47449904 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginType.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginType.kt @@ -7,6 +7,7 @@ package at.bitfire.davdroid.ui.setup import android.net.Uri import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import foundation.e.accountmanager.AccountTypes interface LoginType { @@ -15,6 +16,9 @@ interface LoginType { /** Optional URL to a provider-specific help page. */ val helpUrl: Uri? + val accountType: String + get() = AccountTypes.Default.accountType + @Composable fun LoginScreen( snackbarHostState: SnackbarHostState, diff --git a/app/src/main/kotlin/foundation/e/accountmanager/AccountTypes.kt b/app/src/main/kotlin/foundation/e/accountmanager/AccountTypes.kt new file mode 100644 index 0000000000000000000000000000000000000000..c75787ca4192dc859f4f2c65b81ec6d602cc55e3 --- /dev/null +++ b/app/src/main/kotlin/foundation/e/accountmanager/AccountTypes.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 eFoundation + * + * 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 foundation.e.accountmanager + +import okhttp3.HttpUrl + +enum class AccountTypes( + val accountType: String, + val addressBookType: String, + val whitelistedDomains: List = emptyList() +) { + // Duplicated in res/values/strings.xml because we can’t access Context in enums. + // Update the values in res/values/strings.xml as well. + Default( + accountType = "bitfire.at.davdroid", // R.string.account_type + addressBookType = "at.bitfire.davdroid.address_book", // R.string.account_type_address_book + ); + + companion object { + fun getAccountTypes(): Set = + entries.map { it.accountType }.toSet() + + fun getAddressBookTypes(): Set = + entries.map { it.addressBookType }.toSet() + + fun getAccountTypeForAddressBook(addressBookType: String): String = + entries.find { + it.addressBookType == addressBookType + }?.accountType ?: Default.accountType + + fun getAddressBookForAccountType(accountType: String): String = + entries.find { + it.accountType == accountType + }?.addressBookType ?: Default.addressBookType + + fun getAccountTypeFromPrincipal(principal: HttpUrl?): String { + if (principal == null) { + return Default.accountType + } + + return AccountTypes.entries.firstOrNull { + it.whitelistedDomains.contains(principal.host) + }?.accountType ?: Default.accountType + } + + fun getAddressBookTypeFromPrincipal(principal: HttpUrl?): String { + val accountType = getAccountTypeFromPrincipal(principal) + return AccountTypes.getAddressBookForAccountType(accountType) + } + } +} diff --git a/app/src/main/kotlin/foundation/e/accountmanager/utils/AccountHelper.kt b/app/src/main/kotlin/foundation/e/accountmanager/utils/AccountHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..ba108be59e9f05baedb292dad79fc25ff425916b --- /dev/null +++ b/app/src/main/kotlin/foundation/e/accountmanager/utils/AccountHelper.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025 eFoundation + * + * 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 foundation.e.accountmanager.utils + +import android.accounts.Account +import android.accounts.AccountManager +import foundation.e.accountmanager.AccountTypes + +object AccountHelper { + fun getAllAccounts(accountManager: AccountManager): Array { + val allAccounts = mutableListOf() + for (type in AccountTypes.getAccountTypes()) { + allAccounts += accountManager.getAccountsByType(type).asList() + } + return allAccounts.toTypedArray() + } + + fun getAllAddressBooks(accountManager: AccountManager): Array { + val allAccounts = mutableListOf() + for (type in AccountTypes.getAddressBookTypes()) { + allAccounts += accountManager.getAccountsByType(type).asList() + } + return allAccounts.toTypedArray() + } +} diff --git a/app/src/main/kotlin/foundation/e/accountmanager/utils/SystemUtils.kt b/app/src/main/kotlin/foundation/e/accountmanager/utils/SystemUtils.kt index cf1b0a154f91baf97ece6f594e5023221fb3bf35..89e31b10c5fd351b700e1adf965954a88ae131aa 100644 --- a/app/src/main/kotlin/foundation/e/accountmanager/utils/SystemUtils.kt +++ b/app/src/main/kotlin/foundation/e/accountmanager/utils/SystemUtils.kt @@ -1,7 +1,20 @@ /* - * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. + * Copyright (C) 2025 eFoundation + * + * 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 foundation.e.accountmanager.utils import android.content.Context diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35efdfd2596f45ff9b9906c0976789e43ff9c6fa..977295c25ce10f369bf9fc812518aba39567d69b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,6 +47,12 @@ commons-codec = { strictly = "1.17.1" } #noinspection NewerVersionAvailable commons-lang = { strictly = "3.15.0" } +# --- e-Specific dependencies --- +elib = "0.0.1-alpha11" +ezVcard = "0.12.1" +ical4j = "3.2.19" +synctools = "58bc6752" + [libraries] android-desugaring = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "android-desugaring" } androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } @@ -108,6 +114,12 @@ room-testing = { module = "androidx.room:room-testing", version.ref = "room" } unifiedpush = { module = "org.unifiedpush.android:connector", version.ref = "unifiedpush" } unifiedpush-fcm = { module = "org.unifiedpush.android:embedded-fcm-distributor", version.ref = "unifiedpush-fcm" } +# --- e-Specific dependencies --- +elib = { module = "foundation.e:elib", version.ref = "elib" } +ez-vcard = { module = "com.googlecode.ez-vcard:ez-vcard", version.ref = "ezVcard" } +ical4j = { module = "org.mnode.ical4j:ical4j", version.ref = "ical4j" } +synctools = { module = "foundation.e:synctools", version.ref = "synctools" } + [plugins] android-application = { id = "com.android.application", version.ref = "android-agp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }