From b5f2ca64e15db982bc109c3a1a105dcc72e806c4 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Tue, 5 Jul 2022 13:51:33 +0530 Subject: [PATCH 01/10] Added new columns to the Service table, fixed some authentication issues --- .../java/at/bitfire/davdroid/DavService.kt | 346 ++++++--------- .../at/bitfire/davdroid/db/AppDatabase.kt | 419 ++++++++---------- .../java/at/bitfire/davdroid/db/Service.kt | 3 + .../davdroid/settings/AccountSettings.kt | 5 +- .../ui/setup/AccountDetailsFragment.kt | 151 +++---- 5 files changed, 396 insertions(+), 528 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/DavService.kt b/app/src/main/java/at/bitfire/davdroid/DavService.kt index 29cf4d82f..ea33ba300 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavService.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavService.kt @@ -1,118 +1,84 @@ -/*************************************************************************************************** - * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. - **************************************************************************************************/ - -package at.bitfire.davdroid - import android.accounts.Account -import android.app.IntentService import android.app.PendingIntent import android.content.ContentResolver -import android.content.Context import android.content.Intent import android.os.Binder import android.os.Bundle -import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.room.Transaction +import at.bitfire.davdroid.ui.NotificationUtils import foundation.e.dav4jvm.DavResource import foundation.e.dav4jvm.Response import foundation.e.dav4jvm.UrlUtils import foundation.e.dav4jvm.exception.HttpException import foundation.e.dav4jvm.property.* -import at.bitfire.davdroid.db.* -import at.bitfire.davdroid.db.Collection -import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.settings.AccountSettings -import at.bitfire.davdroid.settings.Settings -import at.bitfire.davdroid.settings.SettingsManager +import foundation.e.accountmanager.log.Logger +import foundation.e.accountmanager.model.* + +import at.bitfire.davdroid.ui.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity -import at.bitfire.davdroid.ui.NotificationUtils -import dagger.hilt.android.AndroidEntryPoint + import okhttp3.HttpUrl import okhttp3.OkHttpClient import java.lang.ref.WeakReference import java.util.* import java.util.logging.Level -import javax.inject.Inject -import kotlin.collections.* +import kotlin.concurrent.thread -@Suppress("DEPRECATION") -@AndroidEntryPoint -class DavService: IntentService("DavService") { +class DavService: android.app.Service() { companion object { - const val ACTION_REFRESH_COLLECTIONS = "refreshCollections" const val EXTRA_DAV_SERVICE_ID = "davServiceID" /** Initialize a forced synchronization. Expects intent data - to be an URI of this format: - contents://// + to be an URI of this format: + contents://// **/ const val ACTION_FORCE_SYNC = "forceSync" val DAV_COLLECTION_PROPERTIES = arrayOf( - ResourceType.NAME, - CurrentUserPrivilegeSet.NAME, - DisplayName.NAME, - Owner.NAME, - AddressbookDescription.NAME, SupportedAddressData.NAME, - CalendarDescription.NAME, CalendarColor.NAME, SupportedCalendarComponentSet.NAME, - Source.NAME + ResourceType.NAME, + CurrentUserPrivilegeSet.NAME, + DisplayName.NAME, + AddressbookDescription.NAME, SupportedAddressData.NAME, + CalendarDescription.NAME, CalendarColor.NAME, SupportedCalendarComponentSet.NAME, + Source.NAME ) - fun refreshCollections(context: Context, serviceId: Long) { - val intent = Intent(context, DavService::class.java) - intent.action = DavService.ACTION_REFRESH_COLLECTIONS - intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, serviceId) - context.startService(intent) - } - } - @Inject lateinit var db: AppDatabase - @Inject lateinit var settings: SettingsManager + private val runningRefresh = HashSet() + private val refreshingStatusListeners = LinkedList>() - /** - * List of [Service] IDs for which the collections are currently refreshed - */ - private val runningRefresh = Collections.synchronizedSet(HashSet()) - - /** - * Currently registered [RefreshingStatusListener]s, which will be notified - * when a collection refresh status changes - */ - private val refreshingStatusListeners = Collections.synchronizedList(LinkedList>()) - @WorkerThread - override fun onHandleIntent(intent: Intent?) { - if (intent == null) - return + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + intent?.let { + val id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1) - val id = intent.getLongExtra(EXTRA_DAV_SERVICE_ID, -1) - - when (intent.action) { - ACTION_REFRESH_COLLECTIONS -> - if (runningRefresh.add(id)) { - refreshingStatusListeners.forEach { listener -> - listener.get()?.onDavRefreshStatusChanged(id, true) + when (intent.action) { + ACTION_REFRESH_COLLECTIONS -> + if (runningRefresh.add(id)) { + refreshingStatusListeners.forEach { listener -> + listener.get()?.onDavRefreshStatusChanged(id, true) + } + thread { refreshCollections(id) } } - refreshCollections(id) - } - - ACTION_FORCE_SYNC -> { - val uri = intent.data!! - val authority = uri.authority!! - val account = Account( + ACTION_FORCE_SYNC -> { + val uri = intent.data!! + val authority = uri.authority!! + val account = Account( uri.pathSegments[1], uri.pathSegments[0] - ) - forceSync(authority, account) + ) + forceSync(authority, account) + } } - } + + return START_NOT_STICKY } @@ -132,17 +98,14 @@ class DavService: IntentService("DavService") { fun addRefreshingStatusListener(listener: RefreshingStatusListener, callImmediateIfRunning: Boolean) { refreshingStatusListeners += WeakReference(listener) if (callImmediateIfRunning) - synchronized(runningRefresh) { - for (id in runningRefresh) - listener.onDavRefreshStatusChanged(id, true) - } + runningRefresh.forEach { id -> listener.onDavRefreshStatusChanged(id, true) } } fun removeRefreshingStatusListener(listener: RefreshingStatusListener) { val iter = refreshingStatusListeners.iterator() while (iter.hasNext()) { val item = iter.next().get() - if (item == listener || item == null) + if (listener == item) iter.remove() } } @@ -165,8 +128,7 @@ class DavService: IntentService("DavService") { } private fun refreshCollections(serviceId: Long) { - val syncAllCollections = settings.getBoolean(Settings.SYNC_ALL_COLLECTIONS) - + val db = AppDatabase.getInstance(this) val homeSetDao = db.homeSetDao() val collectionDao = db.collectionDao() @@ -179,18 +141,11 @@ class DavService: IntentService("DavService") { /** * Checks if the given URL defines home sets and adds them to the home set list. * - * @param personal Whether this is the "outer" call of the recursion. - * - * *true* = found home sets belong to the current-user-principal; recurse if - * calendar proxies or group memberships are found - * - * *false* = found home sets don't directly belong to the current-user-principal; don't recurse - * * @throws java.io.IOException * @throws HttpException - * @throws at.bitfire.dav4jvm.exception.DavException + * @throws foundation.e.dav4jvm.exception.DavException */ - fun queryHomeSets(client: OkHttpClient, url: HttpUrl, personal: Boolean = true) { + fun queryHomeSets(client: OkHttpClient, url: HttpUrl, recurse: Boolean = true) { val related = mutableSetOf() fun findRelated(root: HttpUrl, dav: Response) { @@ -232,11 +187,11 @@ class DavService: IntentService("DavService") { for (href in homeSet.hrefs) dav.location.resolve(href)?.let { val foundUrl = UrlUtils.withTrailingSlash(it) - homeSets[foundUrl] = HomeSet(0, service.id, personal, foundUrl) + homeSets[foundUrl] = HomeSet(0, service.id, foundUrl) } } - if (personal) + if (recurse) findRelated(dav.location, response) } } catch (e: HttpException) { @@ -252,11 +207,11 @@ class DavService: IntentService("DavService") { for (href in homeSet.hrefs) dav.location.resolve(href)?.let { val foundUrl = UrlUtils.withTrailingSlash(it) - homeSets[foundUrl] = HomeSet(0, service.id, personal, foundUrl) + homeSets[foundUrl] = HomeSet(0, service.id, foundUrl) } } - if (personal) + if (recurse) findRelated(dav.location, response) } } catch (e: HttpException) { @@ -268,157 +223,140 @@ class DavService: IntentService("DavService") { } } - // query related homesets (those that do not belong to the current-user-principal) for (resource in related) queryHomeSets(client, resource, false) } + @Transaction fun saveHomesets() { - // syncAll sets the ID of the new homeset to the ID of the old one when the URLs are matching DaoTools(homeSetDao).syncAll( - homeSetDao.getByService(serviceId), - homeSets, - { it.url }) + homeSetDao.getByService(serviceId), + homeSets, + { it.url }) } + @Transaction fun saveCollections() { - // syncAll sets the ID of the new collection to the ID of the old one when the URLs are matching DaoTools(collectionDao).syncAll( - collectionDao.getByService(serviceId), - collections, { it.url }) { new, old -> - // use old settings of "force read only" and "sync", regardless of detection results + collectionDao.getByService(serviceId), + collections, { it.url }) { new, old -> new.forceReadOnly = old.forceReadOnly new.sync = old.sync } } + fun saveResults() { + saveHomesets() + saveCollections() + } + try { Logger.log.info("Refreshing ${service.type} collections of service #$service") // cancel previous notification NotificationManagerCompat.from(this) - .cancel(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS) + .cancel(service.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS) // create authenticating OkHttpClient (credentials taken from account settings) HttpClient.Builder(this, AccountSettings(this, account)) - .setForeground(true) - .build().use { client -> - val httpClient = client.okHttpClient - - // refresh home set list (from principal) - service.principal?.let { principalUrl -> - Logger.log.fine("Querying principal $principalUrl for home sets") - queryHomeSets(httpClient, principalUrl) - } + .setForeground(true) + .build().use { client -> + val httpClient = client.okHttpClient + + // refresh home set list (from principal) + service.accessToken?.let { accessToken -> + service.principal?.let { principalUrl -> + Logger.log.fine("Querying principal $principalUrl for home sets") + queryHomeSets(httpClient, principalUrl) + } - // now refresh homesets and their member collections - val itHomeSets = homeSets.iterator() - while (itHomeSets.hasNext()) { - val (homeSetUrl, homeSet) = itHomeSets.next() - Logger.log.fine("Listing home set $homeSetUrl") + // now refresh homesets and their member collections + val itHomeSets = homeSets.iterator() + while (itHomeSets.hasNext()) { + val homeSet = itHomeSets.next() + Logger.log.fine("Listing home set ${homeSet.key}") + + try { + DavResource(httpClient, homeSet.key, accessToken).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation -> + if (!response.isSuccess()) + return@propfind + + if (relation == Response.HrefRelation.SELF) { + // this response is about the homeset itself + homeSet.value.displayName = response[DisplayName::class.java]?.displayName + homeSet.value.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true + } - try { - DavResource(httpClient, homeSetUrl).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation -> - if (!response.isSuccess()) - return@propfind - - if (relation == Response.HrefRelation.SELF) { - // this response is about the homeset itself - homeSet.displayName = response[DisplayName::class.java]?.displayName - homeSet.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true + // in any case, check whether the response is about a useable collection + val info = Collection.fromDavResponse(response) ?: return@propfind + info.serviceId = serviceId + info.confirmed = true + Logger.log.log(Level.FINE, "Found collection", info) + + // remember usable collections + if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type))) + collections[response.href] = info + } + } catch(e: HttpException) { + if (e.code in arrayOf(403, 404, 410)) + // delete home set only if it was not accessible (40x) + itHomeSets.remove() } - - // in any case, check whether the response is about a useable collection - val info = Collection.fromDavResponse(response) ?: return@propfind - info.serviceId = serviceId - info.refHomeSet = homeSet - info.confirmed = true - - // whether new collections are selected for synchronization by default (controlled by managed setting) - info.sync = syncAllCollections - - info.owner = response[Owner::class.java]?.href?.let { response.href.resolve(it) } - Logger.log.log(Level.FINE, "Found collection", info) - - // remember usable collections - if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) || - (service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type))) - collections[response.href] = info } - } catch(e: HttpException) { - if (e.code in arrayOf(403, 404, 410)) - // delete home set only if it was not accessible (40x) - itHomeSets.remove() - } - } - // check/refresh unconfirmed collections - val collectionsIter = collections.entries.iterator() - while (collectionsIter.hasNext()) { - val currentCollection = collectionsIter.next() - val (url, info) = currentCollection - if (!info.confirmed) - try { - // this collection doesn't belong to a homeset anymore, otherwise it would have been confirmed - info.homeSetId = null - - DavResource(httpClient, url).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ -> - if (!response.isSuccess()) - return@propfind - - val collection = Collection.fromDavResponse(response) ?: return@propfind - collection.serviceId = info.serviceId // use same service ID as previous entry - collection.confirmed = true - - // remove unusable collections - if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || - (service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) || - (collection.type == Collection.TYPE_WEBCAL && collection.source == null)) - collectionsIter.remove() - else - // update this collection in list - currentCollection.setValue(collection) - } - } catch(e: HttpException) { - if (e.code in arrayOf(403, 404, 410)) - // delete collection only if it was not accessible (40x) - collectionsIter.remove() - else - throw e + // check/refresh unconfirmed collections + val itCollections = collections.entries.iterator() + while (itCollections.hasNext()) { + val (url, info) = itCollections.next() + if (!info.confirmed) + try { + DavResource(httpClient, url, accessToken).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ -> + if (!response.isSuccess()) + return@propfind + + val collection = Collection.fromDavResponse(response) ?: return@propfind + collection.confirmed = true + + // remove unusable collections + if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) || + (collection.type == Collection.TYPE_WEBCAL && collection.source == null)) + itCollections.remove() + } + } catch(e: HttpException) { + if (e.code in arrayOf(403, 404, 410)) + // delete collection only if it was not accessible (40x) + itCollections.remove() + else + throw e + } } + } } - } - db.runInTransaction { - saveHomesets() - // use refHomeSet (if available) to determine homeset ID - for (collection in collections.values) - collection.refHomeSet?.let { homeSet -> - collection.homeSetId = homeSet.id - } - saveCollections() - } + saveResults() } catch(e: InvalidAccountException) { Logger.log.log(Level.SEVERE, "Invalid account", e) } catch(e: Exception) { Logger.log.log(Level.SEVERE, "Couldn't refresh collection list", e) - val debugIntent = DebugInfoActivity.IntentBuilder(this) - .withCause(e) - .withAccount(account) - .build() + val debugIntent = Intent(this, DebugInfoActivity::class.java) + debugIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e) + debugIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account) + val notify = NotificationUtils.newBuilder(this, NotificationUtils.CHANNEL_GENERAL) - .setSmallIcon(R.drawable.ic_sync_problem_notify) - .setContentTitle(getString(R.string.dav_service_refresh_failed)) - .setContentText(getString(R.string.dav_service_refresh_couldnt_refresh)) - .setContentIntent(PendingIntent.getActivity(this, 0, debugIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)) - .setSubText(account.name) - .setCategory(NotificationCompat.CATEGORY_ERROR) - .build() + .setSmallIcon(R.drawable.ic_sync_problem_notify) + .setContentTitle(getString(R.string.dav_service_refresh_failed)) + .setContentText(getString(R.string.dav_service_refresh_couldnt_refresh)) + .setContentIntent(PendingIntent.getActivity(this, 0, debugIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setSubText(account.name) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .build() NotificationManagerCompat.from(this) - .notify(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS, notify) + .notify(serviceId.toString(), NotificationUtils.NOTIFY_REFRESH_COLLECTIONS, notify) } finally { runningRefresh.remove(serviceId) refreshingStatusListeners.mapNotNull { it.get() }.forEach { @@ -428,4 +366,4 @@ class DavService: IntentService("DavService") { } -} \ No newline at end of file +} diff --git a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt index fc56d4c69..0274306a5 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt @@ -3,272 +3,227 @@ **************************************************************************************************/ package at.bitfire.davdroid.db - -import android.accounts.AccountManager -import android.app.PendingIntent import android.content.Context -import android.content.Intent +import android.database.sqlite.SQLiteException import android.database.sqlite.SQLiteQueryBuilder -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.core.database.getStringOrNull -import androidx.room.* +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import at.bitfire.davdroid.R -import at.bitfire.davdroid.TextTable -import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.ui.AccountsActivity -import at.bitfire.davdroid.ui.NotificationUtils -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import java.io.Writer -import javax.inject.Singleton +import foundation.e.accountmanager.log.Logger @Suppress("ClassName") @Database(entities = [ Service::class, HomeSet::class, - Collection::class, - SyncStats::class, - WebDavDocument::class, - WebDavMount::class -], exportSchema = true, version = 11, autoMigrations = [ - AutoMigration(from = 9, to = 10), - AutoMigration(from = 10, to = 11) -]) + Collection::class +], version = 7) @TypeConverters(Converters::class) abstract class AppDatabase: RoomDatabase() { - @Module - @InstallIn(SingletonComponent::class) - object AppDatabaseModule { - @Provides - @Singleton - fun appDatabase(@ApplicationContext context: Context): AppDatabase = - Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "services.db") - .addMigrations(*migrations) - .fallbackToDestructiveMigration() // as a last fallback, recreate database instead of crashing - .addCallback(object: Callback() { - override fun onDestructiveMigration(db: SupportSQLiteDatabase) { - val nm = NotificationManagerCompat.from(context) - val launcherIntent = Intent(context, AccountsActivity::class.java) - val notify = NotificationUtils.newBuilder(context, NotificationUtils.CHANNEL_GENERAL) - .setSmallIcon(R.drawable.ic_warning_notify) - .setContentTitle(context.getString(R.string.database_destructive_migration_title)) - .setContentText(context.getString(R.string.database_destructive_migration_text)) - .setCategory(NotificationCompat.CATEGORY_ERROR) - .setContentIntent(PendingIntent.getActivity(context, 0, launcherIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)) - .setAutoCancel(true) - .build() - nm.notify(NotificationUtils.NOTIFY_DATABASE_CORRUPTED, notify) - - // 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))) - am.removeAccount(account, null, null) - } - }) - .build() - } + abstract fun serviceDao(): ServiceDao + abstract fun homeSetDao(): HomeSetDao + abstract fun collectionDao(): CollectionDao companion object { - // migrations - - val migrations: Array = arrayOf( - object : Migration(8, 9) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE syncstats (" + - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + - "collectionId INTEGER NOT NULL REFERENCES collection(id) ON DELETE CASCADE," + - "authority TEXT NOT NULL," + - "lastSync INTEGER NOT NULL)") - db.execSQL("CREATE UNIQUE INDEX index_syncstats_collectionId_authority ON syncstats(collectionId, authority)") - - db.execSQL("CREATE INDEX index_collection_url ON collection(url)") - } - }, - - object : Migration(7, 8) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE homeset ADD COLUMN personal INTEGER NOT NULL DEFAULT 1") - db.execSQL("ALTER TABLE collection ADD COLUMN homeSetId INTEGER DEFAULT NULL REFERENCES homeset(id) ON DELETE SET NULL") - db.execSQL("ALTER TABLE collection ADD COLUMN owner TEXT DEFAULT NULL") - db.execSQL("CREATE INDEX index_collection_homeSetId_type ON collection(homeSetId, type)") - } - }, - - object : Migration(6, 7) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE homeset ADD COLUMN privBind INTEGER NOT NULL DEFAULT 1") - db.execSQL("ALTER TABLE homeset ADD COLUMN displayName TEXT DEFAULT NULL") - } - }, - - object : Migration(5, 6) { - override fun migrate(db: SupportSQLiteDatabase) { - val sql = arrayOf( - // migrate "services" to "service": rename columns, make id NOT NULL - "CREATE TABLE service(" + - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + - "accountName TEXT NOT NULL," + - "type TEXT NOT NULL," + - "principal TEXT DEFAULT NULL" + - ")", - "CREATE UNIQUE INDEX index_service_accountName_type ON service(accountName, type)", - "INSERT INTO service(id, accountName, type, principal) SELECT _id, accountName, service, principal FROM services", - "DROP TABLE services", - - // migrate "homesets" to "homeset": rename columns, make id NOT NULL - "CREATE TABLE homeset(" + - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + - "serviceId INTEGER NOT NULL," + - "url TEXT NOT NULL," + - "FOREIGN KEY (serviceId) REFERENCES service(id) ON DELETE CASCADE" + - ")", - "CREATE UNIQUE INDEX index_homeset_serviceId_url ON homeset(serviceId, url)", - "INSERT INTO homeset(id, serviceId, url) SELECT _id, serviceID, url FROM homesets", - "DROP TABLE homesets", - - // migrate "collections" to "collection": rename columns, make id NOT NULL - "CREATE TABLE collection(" + - "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + - "serviceId INTEGER NOT NULL," + - "type TEXT NOT NULL," + - "url TEXT NOT NULL," + - "privWriteContent INTEGER NOT NULL DEFAULT 1," + - "privUnbind INTEGER NOT NULL DEFAULT 1," + - "forceReadOnly INTEGER NOT NULL DEFAULT 0," + - "displayName TEXT DEFAULT NULL," + - "description TEXT DEFAULT NULL," + - "color INTEGER DEFAULT NULL," + - "timezone TEXT DEFAULT NULL," + - "supportsVEVENT INTEGER DEFAULT NULL," + - "supportsVTODO INTEGER DEFAULT NULL," + - "supportsVJOURNAL INTEGER DEFAULT NULL," + - "source TEXT DEFAULT NULL," + - "sync INTEGER NOT NULL DEFAULT 0," + - "FOREIGN KEY (serviceId) REFERENCES service(id) ON DELETE CASCADE" + - ")", - "CREATE INDEX index_collection_serviceId_type ON collection(serviceId,type)", - "INSERT INTO collection(id, serviceId, type, url, privWriteContent, privUnbind, forceReadOnly, displayName, description, color, timezone, supportsVEVENT, supportsVTODO, source, sync) " + - "SELECT _id, serviceID, type, url, privWriteContent, privUnbind, forceReadOnly, displayName, description, color, timezone, supportsVEVENT, supportsVTODO, source, sync FROM collections", - "DROP TABLE collections" - ) - sql.forEach { db.execSQL(it) } - } - }, - - object : Migration(4, 5) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE collections ADD COLUMN privWriteContent INTEGER DEFAULT 0 NOT NULL") - db.execSQL("UPDATE collections SET privWriteContent=NOT readOnly") - - db.execSQL("ALTER TABLE collections ADD COLUMN privUnbind INTEGER DEFAULT 0 NOT NULL") - db.execSQL("UPDATE collections SET privUnbind=NOT readOnly") - - // there's no DROP COLUMN in SQLite, so just keep the "readOnly" column - } - }, + private var INSTANCE: AppDatabase? = null + + @Synchronized + fun getInstance(context: Context): AppDatabase { + INSTANCE?.let { return it } + + val db = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "services.db") + .addMigrations( + Migration1_2, + Migration2_3, + Migration3_4, + Migration4_5, + Migration5_6, + Migration6_7 + ) + .fallbackToDestructiveMigration() // as a last fallback, recreate database instead of crashing + .build() + INSTANCE = db - object : Migration(3, 4) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE collections ADD COLUMN forceReadOnly INTEGER DEFAULT 0 NOT NULL") - } - }, + return db + } - object : Migration(2, 3) { - override fun migrate(db: SupportSQLiteDatabase) { - // We don't have access to the context in a Room migration now, so - // we will just drop those settings from old DAVx5 versions. - Logger.log.warning("Dropping settings distrustSystemCerts and overrideProxy*") + } - /*val edit = PreferenceManager.getDefaultSharedPreferences(context).edit() - try { - db.query("settings", arrayOf("setting", "value"), null, null, null, null, null).use { cursor -> - while (cursor.moveToNext()) { - when (cursor.getString(0)) { - "distrustSystemCerts" -> edit.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, cursor.getInt(1) != 0) - "overrideProxy" -> edit.putBoolean(App.OVERRIDE_PROXY, cursor.getInt(1) != 0) - "overrideProxyHost" -> edit.putString(App.OVERRIDE_PROXY_HOST, cursor.getString(1)) - "overrideProxyPort" -> edit.putInt(App.OVERRIDE_PROXY_PORT, cursor.getInt(1)) + fun dump(sb: StringBuilder) { + val db = openHelper.readableDatabase + db.beginTransactionNonExclusive() - StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED -> - edit.putBoolean(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, cursor.getInt(1) != 0) - StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED -> - edit.putBoolean(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED, cursor.getInt(1) != 0) - } + // iterate through all tables + db.query(SQLiteQueryBuilder.buildQueryString(false, "sqlite_master", arrayOf("name"), "type='table'", null, null, null, null)).use { cursorTables -> + while (cursorTables.moveToNext()) { + val table = cursorTables.getString(0) + sb.append(table).append("\n") + db.query("SELECT * FROM $table").use { cursor -> + // print columns + val cols = cursor.columnCount + sb.append("\t| ") + for (i in 0 until cols) + sb .append(" ") + .append(cursor.getColumnName(i)) + .append(" |") + sb.append("\n") + + // print rows + while (cursor.moveToNext()) { + sb.append("\t| ") + for (i in 0 until cols) { + sb.append(" ") + try { + val value = cursor.getString(i) + if (value != null) + sb.append(value + .replace("\r", "") + .replace("\n", "")) + else + sb.append("") + + } catch (e: SQLiteException) { + sb.append("") } + sb.append(" |") } - db.execSQL("DROP TABLE settings") - } finally { - edit.apply() - }*/ - } - }, - - object : Migration(1, 2) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE collections ADD COLUMN type TEXT NOT NULL DEFAULT ''") - db.execSQL("ALTER TABLE collections ADD COLUMN source TEXT DEFAULT NULL") - db.execSQL("UPDATE collections SET type=(" + - "SELECT CASE service WHEN ? THEN ? ELSE ? END " + - "FROM services WHERE _id=collections.serviceID" + - ")", - arrayOf("caldav", "CALENDAR", "ADDRESS_BOOK")) + sb.append("\n") + } + sb.append("----------\n") } } - ) - + db.endTransaction() + } } - // DAOs + // migrations - abstract fun serviceDao(): ServiceDao - abstract fun homeSetDao(): HomeSetDao - abstract fun collectionDao(): CollectionDao - abstract fun syncStatsDao(): SyncStatsDao - abstract fun webDavDocumentDao(): WebDavDocumentDao - abstract fun webDavMountDao(): WebDavMountDao + object Migration6_7: Migration(6, 7) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE homeset ADD COLUMN privBind INTEGER NOT NULL DEFAULT 1") + db.execSQL("ALTER TABLE homeset ADD COLUMN displayName TEXT DEFAULT NULL") + } + } + object Migration5_6: Migration(5, 6) { + override fun migrate(db: SupportSQLiteDatabase) { + val sql = arrayOf( + // migrate "services" to "service": rename columns, make id NOT NULL + "CREATE TABLE service(" + + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + + "accountName TEXT NOT NULL," + + "accessToken TEXT NOT NULL," + + "refreshToken TEXT NOT NULL," + + "type TEXT NOT NULL," + + "principal TEXT DEFAULT NULL" + + ")", + "CREATE UNIQUE INDEX index_service_accountName_type ON service(accountName, type)", + "INSERT INTO service(id, accountName, accessToken, refreshToken, type, principal) SELECT _id, accountName, accessToken, refreshToken, service, principal FROM services", + "DROP TABLE services", + + // migrate "homesets" to "homeset": rename columns, make id NOT NULL + "CREATE TABLE homeset(" + + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + + "serviceId INTEGER NOT NULL," + + "url TEXT NOT NULL," + + "FOREIGN KEY (serviceId) REFERENCES service(id) ON DELETE CASCADE" + + ")", + "CREATE UNIQUE INDEX index_homeset_serviceId_url ON homeset(serviceId, url)", + "INSERT INTO homeset(id, serviceId, url) SELECT _id, serviceID, url FROM homesets", + "DROP TABLE homesets", + + // migrate "collections" to "collection": rename columns, make id NOT NULL + "CREATE TABLE collection(" + + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + + "serviceId INTEGER NOT NULL," + + "type TEXT NOT NULL," + + "url TEXT NOT NULL," + + "privWriteContent INTEGER NOT NULL DEFAULT 1," + + "privUnbind INTEGER NOT NULL DEFAULT 1," + + "forceReadOnly INTEGER NOT NULL DEFAULT 0," + + "displayName TEXT DEFAULT NULL," + + "description TEXT DEFAULT NULL," + + "color INTEGER DEFAULT NULL," + + "timezone TEXT DEFAULT NULL," + + "supportsVEVENT INTEGER DEFAULT NULL," + + "supportsVTODO INTEGER DEFAULT NULL," + + "supportsVJOURNAL INTEGER DEFAULT NULL," + + "source TEXT DEFAULT NULL," + + "sync INTEGER NOT NULL DEFAULT 0," + + "FOREIGN KEY (serviceId) REFERENCES service(id) ON DELETE CASCADE" + + ")", + "CREATE INDEX index_collection_serviceId_type ON collection(serviceId,type)", + "INSERT INTO collection(id, serviceId, type, url, privWriteContent, privUnbind, forceReadOnly, displayName, description, color, timezone, supportsVEVENT, supportsVTODO, source, sync) " + + "SELECT _id, serviceID, type, url, privWriteContent, privUnbind, forceReadOnly, displayName, description, color, timezone, supportsVEVENT, supportsVTODO, source, sync FROM collections", + "DROP TABLE collections" + ) + sql.forEach { db.execSQL(it) } + } + } - // helpers + object Migration4_5: Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE collections ADD COLUMN privWriteContent INTEGER DEFAULT 0 NOT NULL") + db.execSQL("UPDATE collections SET privWriteContent=NOT readOnly") - fun dump(writer: Writer, ignoreTables: Array) { - val db = openHelper.readableDatabase - db.beginTransactionNonExclusive() + db.execSQL("ALTER TABLE collections ADD COLUMN privUnbind INTEGER DEFAULT 0 NOT NULL") + db.execSQL("UPDATE collections SET privUnbind=NOT readOnly") - // iterate through all tables - db.query(SQLiteQueryBuilder.buildQueryString(false, "sqlite_master", arrayOf("name"), "type='table'", null, null, null, null)).use { cursorTables -> - while (cursorTables.moveToNext()) { - val tableName = cursorTables.getString(0) - if (ignoreTables.contains(tableName)) { - writer.append("$tableName: ") - db.query("SELECT COUNT(*) FROM $tableName").use { cursor -> - if (cursor.moveToNext()) - writer.append("${cursor.getInt(0)} row(s), data not listed here\n\n") - } - } else { - writer.append("$tableName\n") - db.query("SELECT * FROM $tableName").use { cursor -> - val table = TextTable(*cursor.columnNames) - val cols = cursor.columnCount - // print rows - while (cursor.moveToNext()) { - val values = Array(cols) { idx -> cursor.getStringOrNull(idx) } - table.addLine(*values) + // there's no DROP COLUMN in SQLite, so just keep the "readOnly" column + } + } + + object Migration3_4: Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE collections ADD COLUMN forceReadOnly INTEGER DEFAULT 0 NOT NULL") + } + } + + object Migration2_3: Migration(2, 3) { + override fun migrate(db: SupportSQLiteDatabase) { + // We don't have access to the context in a Room migration now, so + // we will just drop those settings from old DAVx5 versions. + Logger.log.warning("Dropping settings distrustSystemCerts and overrideProxy*") + + /*val edit = PreferenceManager.getDefaultSharedPreferences(context).edit() + try { + db.query("settings", arrayOf("setting", "value"), null, null, null, null, null).use { cursor -> + while (cursor.moveToNext()) { + when (cursor.getString(0)) { + "distrustSystemCerts" -> edit.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, cursor.getInt(1) != 0) + "overrideProxy" -> edit.putBoolean(App.OVERRIDE_PROXY, cursor.getInt(1) != 0) + "overrideProxyHost" -> edit.putString(App.OVERRIDE_PROXY_HOST, cursor.getString(1)) + "overrideProxyPort" -> edit.putInt(App.OVERRIDE_PROXY_PORT, cursor.getInt(1)) + + StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED -> + edit.putBoolean(StartupDialogFragment.HINT_GOOGLE_PLAY_ACCOUNTS_REMOVED, cursor.getInt(1) != 0) + StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED -> + edit.putBoolean(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED, cursor.getInt(1) != 0) } - writer.append(table.toString()) } } - } - db.endTransaction() + db.execSQL("DROP TABLE settings") + } finally { + edit.apply() + }*/ + } + } + + object Migration1_2: Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE collections ADD COLUMN type TEXT NOT NULL DEFAULT ''") + db.execSQL("ALTER TABLE collections ADD COLUMN source TEXT DEFAULT NULL") + db.execSQL("UPDATE collections SET type=(" + + "SELECT CASE service WHEN ? THEN ? ELSE ? END " + + "FROM services WHERE _id=collections.serviceID" + + ")", + arrayOf("caldav", "CALENDAR", "ADDRESS_BOOK")) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/at/bitfire/davdroid/db/Service.kt b/app/src/main/java/at/bitfire/davdroid/db/Service.kt index 0287f6c21..a5500ff58 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/Service.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/Service.kt @@ -20,6 +20,9 @@ data class Service( var accountName: String, var type: String, + var accessToken: String, + var refreshToken: String, + var principal: HttpUrl? ): IdEntity { diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index 539779f5b..d961278c3 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -100,8 +100,11 @@ class AccountSettings( when (credentials.type) { Credentials.Type.UsernamePassword -> bundle.putString(KEY_USERNAME, credentials.userName) - Credentials.Type.OAuth -> + Credentials.Type.OAuth -> { bundle.putString(KEY_USERNAME, credentials.userName) + bundle.putString(KEY_ACCESS_TOKEN, credentials.accessToken) + bundle.putString(KEY_REFRESH_TOKEN, credentials.refreshToken) + } Credentials.Type.ClientCertificate -> bundle.putString(KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) } 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 2e37a4e52..a776c8c4a 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 @@ -4,58 +4,48 @@ package at.bitfire.davdroid.ui.setup +import DavService import android.accounts.Account import android.accounts.AccountManager +import android.app.Application import android.content.ContentResolver -import android.content.Context import android.content.Intent import android.os.Bundle import android.provider.CalendarContract -import android.text.Editable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels import androidx.lifecycle.* -import at.bitfire.davdroid.DavService +import at.bitfire.davdroid.Constants + import at.bitfire.davdroid.InvalidAccountException -import at.bitfire.davdroid.R -import at.bitfire.davdroid.databinding.LoginAccountDetailsBinding import at.bitfire.davdroid.db.AppDatabase -import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.db.HomeSet import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.resource.TaskUtils +import foundation.e.accountmanager.R +import foundation.e.accountmanager.databinding.LoginAccountDetailsBinding +import foundation.e.accountmanager.model.Credentials +import at.bitfire.davdroid.resource.LocalTaskList import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.Settings -import at.bitfire.davdroid.settings.SettingsManager -import at.bitfire.davdroid.syncadapter.AccountUtils -import at.bitfire.davdroid.ui.account.AccountActivity import foundation.e.ical4android.TaskProvider import foundation.e.vcard4android.GroupMethod - import com.google.android.material.snackbar.Snackbar -import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.launch import java.util.logging.Level -import javax.inject.Inject +import kotlin.concurrent.thread -@AndroidEntryPoint -class AccountDetailsFragment : Fragment() { +class AccountDetailsFragment: Fragment() { - @Inject lateinit var settings: SettingsManager - - val loginModel by activityViewModels() - val model by viewModels() + private lateinit var loginModel: LoginModel + private lateinit var model: AccountDetailsModel + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + loginModel = ViewModelProviders.of(requireActivity()).get(LoginModel::class.java) + model = ViewModelProviders.of(this).get(AccountDetailsModel::class.java) + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val v = LoginAccountDetailsBinding.inflate(inflater, container, false) @@ -64,24 +54,16 @@ class AccountDetailsFragment : Fragment() { val config = loginModel.configuration ?: throw IllegalStateException() - // default account name - model.name.value = - config.calDAV?.emails?.firstOrNull() - ?: loginModel.credentials?.userName - ?: loginModel.credentials?.certificateAlias - ?: loginModel.baseURI?.host + model.name.value = config.calDAV?.email ?: + loginModel.credentials?.userName ?: + loginModel.credentials?.certificateAlias // CardDAV-specific + val settings = Settings.getInstance(requireActivity()) v.carddav.visibility = if (config.cardDAV != null) View.VISIBLE else View.GONE - if (settings.containsKey(AccountSettings.KEY_CONTACT_GROUP_METHOD)) + if (settings.has(AccountSettings.KEY_CONTACT_GROUP_METHOD)) v.contactGroupMethod.isEnabled = false - // CalDAV-specific - config.calDAV?.let { - val accountNameAdapter = ArrayAdapter(requireActivity(), android.R.layout.simple_list_item_1, it.emails) - v.accountName.setAdapter(accountNameAdapter) - } - v.createAccount.setOnClickListener { val name = model.name.value if (name.isNullOrBlank()) @@ -101,20 +83,14 @@ class AccountDetailsFragment : Fragment() { v.createAccount.visibility = View.GONE model.createAccount( - name, - loginModel.credentials, - config, - GroupMethod.valueOf(groupMethodName) - ).observe(viewLifecycleOwner, Observer { success -> - if (success) { - // close Create account activity + name, + loginModel.credentials!!, + config, + GroupMethod.valueOf(groupMethodName) + ).observe(this, Observer { success -> + if (success) requireActivity().finish() - // open Account activity for created account - val intent = Intent(requireActivity(), AccountActivity::class.java) - val account = Account(name, getString(R.string.account_type)) - intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account) - startActivity(intent) - } else { + else { Snackbar.make(requireActivity().findViewById(android.R.id.content), R.string.login_account_not_created, Snackbar.LENGTH_LONG).show() v.createAccountProgress.visibility = View.GONE @@ -140,49 +116,42 @@ class AccountDetailsFragment : Fragment() { } - @HiltViewModel - class AccountDetailsModel @Inject constructor( - @ApplicationContext val context: Context, - val db: AppDatabase, - val settingsManager: SettingsManager - ) : ViewModel() { + class AccountDetailsModel( + application: Application + ): AndroidViewModel(application) { val name = MutableLiveData() val nameError = MutableLiveData() - val showApostropheWarning = MutableLiveData(false) - fun validateAccountName(s: Editable) { - showApostropheWarning.value = s.toString().contains('\'') - nameError.value = null - } - - fun createAccount(name: String, credentials: Credentials?, config: DavResourceFinder.Configuration, groupMethod: GroupMethod): LiveData { + fun createAccount(name: String, credentials: Credentials, config: DavResourceFinder.Configuration, groupMethod: GroupMethod): LiveData { val result = MutableLiveData() - viewModelScope.launch(Dispatchers.Default + NonCancellable) { + val context = getApplication() + thread { val account = Account(name, context.getString(R.string.account_type)) // create Android account val userData = AccountSettings.initialUserData(credentials) Logger.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, userData)) - if (!AccountUtils.createAccount(context, account, userData, credentials?.password)) { + val accountManager = AccountManager.get(context) + if (!accountManager.addAccountExplicitly(account, credentials.password, userData)) { result.postValue(false) - return@launch + return@thread } // add entries for account to service DB Logger.log.log(Level.INFO, "Writing account configuration to database", config) + val db = AppDatabase.getInstance(context) try { val accountSettings = AccountSettings(context, account) - val defaultSyncInterval = settingsManager.getLong(Settings.DEFAULT_SYNC_INTERVAL) val refreshIntent = Intent(context, DavService::class.java) refreshIntent.action = DavService.ACTION_REFRESH_COLLECTIONS - val addrBookAuthority = context.getString(R.string.address_books_authority) if (config.cardDAV != null) { // insert CardDAV service - val id = insertService(name, Service.TYPE_CARDDAV, config.cardDAV) + + val id = insertService(db, name, credentials.accessToken!!, credentials.refreshToken!!, Service.TYPE_CARDDAV, config.cardDAV) // initial CardDAV account settings accountSettings.setGroupMethod(groupMethod) @@ -191,52 +160,52 @@ class AccountDetailsFragment : Fragment() { refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id) context.startService(refreshIntent) - // set default sync interval and enable sync regardless of permissions - ContentResolver.setIsSyncable(account, addrBookAuthority, 1) - accountSettings.setSyncInterval(addrBookAuthority, defaultSyncInterval) + // contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_address_books.xml + accountSettings.setSyncInterval(context.getString(R.string.address_books_authority), Constants.DEFAULT_SYNC_INTERVAL) } else - ContentResolver.setIsSyncable(account, addrBookAuthority, 0) + ContentResolver.setIsSyncable(account, context.getString(R.string.address_books_authority), 0) if (config.calDAV != null) { // insert CalDAV service - val id = insertService(name, Service.TYPE_CALDAV, config.calDAV) + val id = insertService(db, name, credentials.accessToken!!, credentials.refreshToken!!, Service.TYPE_CALDAV, config.calDAV) // start CalDAV service detection (refresh collections) refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id) context.startService(refreshIntent) - // set default sync interval and enable sync regardless of permissions - ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1) - accountSettings.setSyncInterval(CalendarContract.AUTHORITY, defaultSyncInterval) + // calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_calendars.xml + accountSettings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL) - val taskProvider = TaskUtils.currentProvider(context) - if (taskProvider != null) { - ContentResolver.setIsSyncable(account, taskProvider.authority, 1) - accountSettings.setSyncInterval(taskProvider.authority, defaultSyncInterval) - // further changes will be handled by TasksWatcher on app start or when tasks app is (un)installed + // enable task sync if OpenTasks is installed + // further changes will be handled by PackageChangedReceiver + if (LocalTaskList.tasksProviderAvailable(context)) { + ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1) + accountSettings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL) } - } else + } else { ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0) + ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0) + } } catch(e: InvalidAccountException) { Logger.log.log(Level.SEVERE, "Couldn't access account settings", e) result.postValue(false) - return@launch + return@thread } result.postValue(true) } return result } - private fun insertService(accountName: String, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long { + private fun insertService(db: AppDatabase, accountName: String, accessToken: String, refreshToken: String, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long { // insert service - val service = Service(0, accountName, type, info.principal) + val service = Service(0, accountName, accessToken, refreshToken, type, info.principal) val serviceId = db.serviceDao().insertOrReplace(service) // insert home sets val homeSetDao = db.homeSetDao() for (homeSet in info.homeSets) { - homeSetDao.insertOrReplace(HomeSet(0, serviceId, true, homeSet)) + homeSetDao.insertOrReplace(HomeSet(0, serviceId, homeSet)) } // insert collections @@ -251,4 +220,4 @@ class AccountDetailsFragment : Fragment() { } -} \ No newline at end of file +} -- GitLab From dbb3b9e3c0d4f423d67cac59cfb8855231a2583f Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 08:50:47 +0530 Subject: [PATCH 02/10] Fixed some issues where the access token wasn't being passed to the Google API. --- .../java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt | 2 +- .../java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt | 2 +- .../java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt index 6564f3adb..4f9374767 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt @@ -57,7 +57,7 @@ class CalendarSyncManager( override fun prepare(): Boolean { collectionURL = (localCollection.name ?: return false).toHttpUrlOrNull() ?: return false - davCollection = DavCalendar(httpClient.okHttpClient, collectionURL) + davCollection = DavCalendar(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) // if there are dirty exceptions for events, mark their master events as dirty, too localCollection.processDirtyExceptions() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index cd64d1c7f..4afdcf8f8 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -125,7 +125,7 @@ class ContactsSyncManager( } collectionURL = localCollection.url.toHttpUrlOrNull() ?: return false - davCollection = DavAddressBook(httpClient.okHttpClient, collectionURL) + davCollection = DavAddressBook(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) resourceDownloader = ResourceDownloader(davCollection.location) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt index 74c403ba6..7fd4cf34f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt @@ -57,7 +57,7 @@ class TasksSyncManager( override fun prepare(): Boolean { collectionURL = (localCollection.syncId ?: return false).toHttpUrlOrNull() ?: return false - davCollection = DavCalendar(httpClient.okHttpClient, collectionURL) + davCollection = DavCalendar(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) return true } -- GitLab From 2806605afe011fb95f8e9e7a3f6b835a117982aa Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:00:47 +0530 Subject: [PATCH 03/10] Implemented OAuth token support. Refreshing of tokens not working yet. --- .../syncadapter/CalendarSyncManager.kt | 104 +++--- .../syncadapter/ContactsSyncManager.kt | 321 +++++++++++------- .../ui/setup/GoogleAuthenticatorFragment.kt | 2 - .../layout/fragment_google_authenticator.xml | 8 +- 4 files changed, 255 insertions(+), 180 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt index 4f9374767..b01e83a8a 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt @@ -49,58 +49,54 @@ class CalendarSyncManager( account: Account, accountSettings: AccountSettings, extras: Bundle, - httpClient: HttpClient, authority: String, syncResult: SyncResult, localCalendar: LocalCalendar -): SyncManager(context, account, accountSettings, httpClient, extras, authority, syncResult, localCalendar) { +): SyncManager(context, account, accountSettings, extras, authority, syncResult, localCalendar) { override fun prepare(): Boolean { - collectionURL = (localCollection.name ?: return false).toHttpUrlOrNull() ?: return false + collectionURL = HttpUrl.parse(localCollection.name ?: return false) ?: return false davCollection = DavCalendar(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) // if there are dirty exceptions for events, mark their master events as dirty, too localCollection.processDirtyExceptions() - // now find dirty events that have no instances and set them to deleted - localCollection.deleteDirtyEventsWithoutInstances() - return true } override fun queryCapabilities(): SyncState? = - remoteExceptionContext { - var syncState: SyncState? = null - it.propfind(0, MaxICalendarSize.NAME, SupportedReportSet.NAME, GetCTag.NAME, SyncToken.NAME) { response, relation -> - if (relation == Response.HrefRelation.SELF) { - response[MaxICalendarSize::class.java]?.maxSize?.let { maxSize -> - Logger.log.info("Calendar accepts events up to ${FileUtils.byteCountToDisplaySize(maxSize)}") - } - - response[SupportedReportSet::class.java]?.let { supported -> - hasCollectionSync = supported.reports.contains(SupportedReportSet.SYNC_COLLECTION) - } - syncState = syncState(response) + useRemoteCollection { + var syncState: SyncState? = null + it.propfind(0, SupportedReportSet.NAME, GetCTag.NAME, SyncToken.NAME) { response, relation -> + if (relation == Response.HrefRelation.SELF) { + response[SupportedReportSet::class.java]?.let { supported -> + hasCollectionSync = supported.reports.contains(SupportedReportSet.SYNC_COLLECTION) } - } - Logger.log.info("Calendar supports Collection Sync: $hasCollectionSync") - syncState + syncState = syncState(response) + } } + Logger.log.info("Server supports Collection Sync: $hasCollectionSync") + syncState + } + override fun syncAlgorithm() = if (accountSettings.getTimeRangePastDays() != null || !hasCollectionSync) - SyncAlgorithm.PROPFIND_REPORT - else - SyncAlgorithm.COLLECTION_SYNC + SyncAlgorithm.PROPFIND_REPORT + else + SyncAlgorithm.COLLECTION_SYNC - override fun generateUpload(resource: LocalEvent): RequestBody = localExceptionContext(resource) { + override fun prepareUpload(resource: LocalEvent): RequestBody = useLocal(resource) { val event = requireNotNull(resource.event) Logger.log.log(Level.FINE, "Preparing upload of event ${resource.fileName}", event) val os = ByteArrayOutputStream() event.write(os) - os.toByteArray().toRequestBody(DavCalendar.MIME_ICALENDAR_UTF8) + RequestBody.create( + DavCalendar.MIME_ICALENDAR_UTF8, + os.toByteArray() + ) } override fun listAllRemote(callback: DavResponseCallback) { @@ -112,34 +108,49 @@ class CalendarSyncManager( limitStart = calendar.time } - return remoteExceptionContext { remote -> + return useRemoteCollection { remote -> Logger.log.info("Querying events since $limitStart") - remote.calendarQuery(Component.VEVENT, limitStart, null, callback) + remote.calendarQuery("VEVENT", limitStart, null, callback) } } override fun downloadRemote(bunch: List) { Logger.log.info("Downloading ${bunch.size} iCalendars: $bunch") - remoteExceptionContext { - it.multiget(bunch) { response, _ -> - responseExceptionContext(response) { - if (!response.isSuccess()) { - Logger.log.warning("Received non-successful multiget response for ${response.href}") - return@responseExceptionContext + if (bunch.size == 1) { + val remote = bunch.first() + // only one contact, use GET + useRemote(DavResource(httpClient.okHttpClient, remote, accountSettings.credentials().accessToken)) { resource -> + resource.get(DavCalendar.MIME_ICALENDAR.toString()) { response -> + // CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4] + val eTag = response.header("ETag")?.let { GetETag(it).eTag } + ?: throw DavException("Received CalDAV GET response without ETag") + + response.body()!!.use { + processVEvent(resource.fileName(), eTag, it.charStream()) } + } + } + } else + // multiple iCalendars, use calendar-multi-get + useRemoteCollection { + it.multiget(bunch) { response, _ -> + useRemote(response) { + if (!response.isSuccess()) { + Logger.log.warning("Received non-successful multiget response for ${response.href}") + return@useRemote + } - val eTag = response[GetETag::class.java]?.eTag + val eTag = response[GetETag::class.java]?.eTag ?: throw DavException("Received multi-get response without ETag") - val scheduleTag = response[ScheduleTag::class.java]?.scheduleTag - val calendarData = response[CalendarData::class.java] - val iCal = calendarData?.iCalendar + val calendarData = response[CalendarData::class.java] + val iCal = calendarData?.iCalendar ?: throw DavException("Received multi-get response without address data") - processVEvent(DavUtils.lastSegmentOfUrl(response.href), eTag, scheduleTag, StringReader(iCal)) + processVEvent(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(iCal)) + } } } - } } override fun postProcess() { @@ -148,7 +159,7 @@ class CalendarSyncManager( // helpers - private fun processVEvent(fileName: String, eTag: String, scheduleTag: String?, reader: Reader) { + private fun processVEvent(fileName: String, eTag: String, reader: Reader) { val events: List try { events = Event.eventsFromReader(reader) @@ -163,23 +174,22 @@ class CalendarSyncManager( // set default reminder for non-full-day events, if requested val defaultAlarmMinBefore = accountSettings.getDefaultAlarm() - if (defaultAlarmMinBefore != null && DateUtils.isDateTime(event.dtStart) && event.alarms.isEmpty()) { - val alarm = VAlarm(Duration.ofMinutes(-defaultAlarmMinBefore.toLong())) + if (defaultAlarmMinBefore != null && !event.isAllDay() && event.alarms.isEmpty()) { + val alarm = VAlarm(Dur(0, 0, -defaultAlarmMinBefore, 0)) Logger.log.log(Level.FINE, "${event.uid}: Adding default alarm", alarm) event.alarms += alarm } // update local event, if it exists - localExceptionContext(localCollection.findByName(fileName)) { local -> + useLocal(localCollection.findByName(fileName)) { local -> if (local != null) { Logger.log.log(Level.INFO, "Updating $fileName in local calendar", event) local.eTag = eTag - local.scheduleTag = scheduleTag local.update(event) syncResult.stats.numUpdates++ } else { Logger.log.log(Level.INFO, "Adding $fileName to local calendar", event) - localExceptionContext(LocalEvent(localCollection, event, fileName, eTag, scheduleTag, LocalResource.FLAG_REMOTELY_PRESENT)) { + useLocal(LocalEvent(localCollection, event, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)) { it.add() } syncResult.stats.numInserts++ @@ -190,6 +200,6 @@ class CalendarSyncManager( } override fun notifyInvalidResourceTitle(): String = - context.getString(R.string.sync_invalid_event) + context.getString(R.string.sync_invalid_event) } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index 4afdcf8f8..77818946a 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -46,7 +46,6 @@ import java.io.IOException import java.io.Reader import java.io.StringReader import java.util.logging.Level - /** * Synchronization manager for CardDAV collections; handles contacts and groups. * @@ -86,26 +85,22 @@ class ContactsSyncManager( context: Context, account: Account, accountSettings: AccountSettings, - httpClient: HttpClient, extras: Bundle, authority: String, syncResult: SyncResult, val provider: ContentProviderClient, localAddressBook: LocalAddressBook -): SyncManager(context, account, accountSettings, httpClient, extras, authority, syncResult, localAddressBook) { +): SyncManager(context, account, accountSettings, extras, authority, syncResult, localAddressBook) { companion object { infix fun Set.disjunct(other: Set) = (this - other) union (other - this) } private val readOnly = localAddressBook.readOnly + private val accessToken: String? = accountSettings.credentials().accessToken private var hasVCard4 = false - private var hasJCard = false - private val groupStrategy = when (accountSettings.getGroupMethod()) { - GroupMethod.GROUP_VCARDS -> VCard4Strategy(localAddressBook) - GroupMethod.CATEGORIES -> CategoriesStrategy(localAddressBook) - } + private val groupMethod = accountSettings.getGroupMethod() /** * Used to download images which are referenced by URL @@ -124,178 +119,227 @@ class ContactsSyncManager( } } - collectionURL = localCollection.url.toHttpUrlOrNull() ?: return false + collectionURL = HttpUrl.parse(localCollection.url) ?: return false davCollection = DavAddressBook(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) resourceDownloader = ResourceDownloader(davCollection.location) - Logger.log.info("Contact group strategy: ${groupStrategy::class.java.simpleName}") return true } override fun queryCapabilities(): SyncState? { - return remoteExceptionContext { + Logger.log.info("Contact group method: $groupMethod") + // in case of GROUP_VCARDs, treat groups as contacts in the local address book + localCollection.includeGroups = groupMethod == GroupMethod.GROUP_VCARDS + + return useRemoteCollection { var syncState: SyncState? = null - it.propfind(0, MaxVCardSize.NAME, SupportedAddressData.NAME, SupportedReportSet.NAME, GetCTag.NAME, SyncToken.NAME) { response, relation -> + it.propfind(0, SupportedAddressData.NAME, SupportedReportSet.NAME, GetCTag.NAME, SyncToken.NAME) { response, relation -> if (relation == Response.HrefRelation.SELF) { - response[MaxVCardSize::class.java]?.maxSize?.let { maxSize -> - Logger.log.info("Address book accepts vCards up to ${FileUtils.byteCountToDisplaySize(maxSize)}") - } - response[SupportedAddressData::class.java]?.let { supported -> hasVCard4 = supported.hasVCard4() - - // temporarily disable jCard because of https://github.com/nextcloud/server/issues/29693 - // hasJCard = supported.hasJCard() } + response[SupportedReportSet::class.java]?.let { supported -> hasCollectionSync = supported.reports.contains(SupportedReportSet.SYNC_COLLECTION) } + syncState = syncState(response) } } - // Logger.log.info("Server supports jCard: $hasJCard") - Logger.log.info("Address book supports vCard4: $hasVCard4") - Logger.log.info("Address book supports Collection Sync: $hasCollectionSync") + Logger.log.info("Server supports vCard/4: $hasVCard4") + Logger.log.info("Server supports Collection Sync: $hasCollectionSync") syncState } } override fun syncAlgorithm() = if (hasCollectionSync) - SyncAlgorithm.COLLECTION_SYNC - else - SyncAlgorithm.PROPFIND_REPORT + SyncAlgorithm.COLLECTION_SYNC + else + SyncAlgorithm.PROPFIND_REPORT override fun processLocallyDeleted() = - if (readOnly) { - for (group in localCollection.findDeletedGroups()) { - Logger.log.warning("Restoring locally deleted group (read-only address book!)") - localExceptionContext(group) { it.resetDeleted() } - } + if (readOnly) { + for (group in localCollection.findDeletedGroups()) { + Logger.log.warning("Restoring locally deleted group (read-only address book!)") + useLocal(group) { it.resetDeleted() } + } - for (contact in localCollection.findDeletedContacts()) { - Logger.log.warning("Restoring locally deleted contact (read-only address book!)") - localExceptionContext(contact) { it.resetDeleted() } - } + for (contact in localCollection.findDeletedContacts()) { + Logger.log.warning("Restoring locally deleted contact (read-only address book!)") + useLocal(contact) { it.resetDeleted() } + } - false - } else - // mirror deletions to remote collection (DELETE) - super.processLocallyDeleted() + false + } else + // mirror deletions to remote collection (DELETE) + super.processLocallyDeleted() override fun uploadDirty(): Boolean { if (readOnly) { for (group in localCollection.findDirtyGroups()) { Logger.log.warning("Resetting locally modified group to ETag=null (read-only address book!)") - localExceptionContext(group) { it.clearDirty(null, null) } + useLocal(group) { it.clearDirty(null) } } for (contact in localCollection.findDirtyContacts()) { Logger.log.warning("Resetting locally modified contact to ETag=null (read-only address book!)") - localExceptionContext(contact) { it.clearDirty(null, null) } + useLocal(contact) { it.clearDirty(null) } } - } else - // we only need to handle changes in groups when the address book is read/write - groupStrategy.beforeUploadDirty() + } else { + if (groupMethod == GroupMethod.CATEGORIES) { + /* groups memberships are represented as contact CATEGORIES */ + + // groups with DELETED=1: set all members to dirty, then remove group + for (group in localCollection.findDeletedGroups()) { + Logger.log.fine("Finally removing group $group") + // useless because Android deletes group memberships as soon as a group is set to DELETED: + // group.markMembersDirty() + useLocal(group) { it.delete() } + } + + // groups with DIRTY=1: mark all memberships as dirty, then clean DIRTY flag of group + for (group in localCollection.findDirtyGroups()) { + Logger.log.fine("Marking members of modified group $group as dirty") + useLocal(group) { + it.markMembersDirty() + it.clearDirty(null) + } + } + } else { + /* groups as separate VCards: there are group contacts and individual contacts */ + + // mark groups with changed members as dirty + val batch = BatchOperation(localCollection.provider!!) + for (contact in localCollection.findDirtyContacts()) + try { + Logger.log.fine("Looking for changed group memberships of contact ${contact.fileName}") + val cachedGroups = contact.getCachedGroupMemberships() + val currentGroups = contact.getGroupMemberships() + for (groupID in cachedGroups disjunct currentGroups) { + Logger.log.fine("Marking group as dirty: $groupID") + batch.enqueue(BatchOperation.Operation( + ContentProviderOperation.newUpdate(localCollection.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, groupID))) + .withValue(Groups.DIRTY, 1) + .withYieldAllowed(true) + )) + } + } catch(e: FileNotFoundException) { + } + batch.commit() + } + } // generate UID/file name for newly created contacts return super.uploadDirty() } - override fun generateUpload(resource: LocalAddress): RequestBody = - localExceptionContext(resource) { - val contact: Contact = when (resource) { - is LocalContact -> resource.getContact() - is LocalGroup -> resource.getContact() - else -> throw IllegalArgumentException("resource must be LocalContact or LocalGroup") + override fun prepareUpload(resource: LocalAddress): RequestBody = useLocal(resource) { + val contact: Contact + if (resource is LocalContact) { + contact = resource.contact!! + + if (groupMethod == GroupMethod.CATEGORIES) { + // add groups as CATEGORIES + for (groupID in resource.getGroupMemberships()) { + provider.query( + localCollection.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, groupID)), + arrayOf(Groups.TITLE), null, null, null + )?.use { cursor -> + if (cursor.moveToNext()) { + val title = cursor.getString(0) + if (!title.isNullOrEmpty()) + contact.categories.add(title) + } + } + } } + } else if (resource is LocalGroup) + contact = resource.contact!! + else + throw IllegalArgumentException("resource must be LocalContact or LocalGroup") - Logger.log.log(Level.FINE, "Preparing upload of vCard ${resource.fileName}", contact) + Logger.log.log(Level.FINE, "Preparing upload of VCard ${resource.fileName}", contact) - val os = ByteArrayOutputStream() - val mimeType: MediaType - when { - hasJCard -> { - mimeType = DavAddressBook.MIME_JCARD - contact.writeJCard(os) - } - hasVCard4 -> { - mimeType = DavAddressBook.MIME_VCARD4 - contact.writeVCard(VCardVersion.V4_0, os) - } - else -> { - mimeType = DavAddressBook.MIME_VCARD3_UTF8 - contact.writeVCard(VCardVersion.V3_0, os) - } - } + val os = ByteArrayOutputStream() + contact.write(if (hasVCard4) VCardVersion.V4_0 else VCardVersion.V3_0, groupMethod, os) - return@localExceptionContext(os.toByteArray().toRequestBody(mimeType)) - } + RequestBody.create( + if (hasVCard4) DavAddressBook.MIME_VCARD4 else DavAddressBook.MIME_VCARD3_UTF8, + os.toByteArray() + ) + } override fun listAllRemote(callback: DavResponseCallback) = - remoteExceptionContext { - it.propfind(1, ResourceType.NAME, GetETag.NAME, callback = callback) - } + useRemoteCollection { + it.propfind(1, ResourceType.NAME, GetETag.NAME, callback = callback) + } override fun downloadRemote(bunch: List) { - Logger.log.info("Downloading ${bunch.size} vCard(s): $bunch") - remoteExceptionContext { - val contentType: String? - val version: String? - when { - hasJCard -> { - contentType = DavUtils.MEDIA_TYPE_JCARD.toString() - version = VCardVersion.V4_0.version - } - hasVCard4 -> { - contentType = DavUtils.MEDIA_TYPE_VCARD.toString() - version = VCardVersion.V4_0.version - } - else -> { - contentType = DavUtils.MEDIA_TYPE_VCARD.toString() - version = null // 3.0 is the default version; don't request 3.0 explicitly because maybe some vCard3-only servers don't understand it + Logger.log.info("Downloading ${bunch.size} vCards: $bunch") + if (bunch.size == 1) { + val remote = bunch.first() + // only one contact, use GET + useRemote(DavResource(httpClient.okHttpClient, remote)) { resource -> + resource.get("text/vcard;version=4.0, text/vcard;charset=utf-8;q=0.8, text/vcard;q=0.5") { response -> + // CardDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc6352#section-6.3.2.3] + val eTag = response.header("ETag")?.let { GetETag(it).eTag } + ?: throw DavException("Received CardDAV GET response without ETag") + + response.body()!!.use { + processVCard(resource.fileName(), eTag, it.charStream(), resourceDownloader) + } } } - it.multiget(bunch, contentType, version) { response, _ -> - responseExceptionContext(response) { - if (!response.isSuccess()) { - Logger.log.warning("Received non-successful multiget response for ${response.href}") - return@responseExceptionContext - } - - val eTag = response[GetETag::class.java]?.eTag + } else + // multiple vCards, use addressbook-multi-get + useRemoteCollection { + it.multiget(bunch, hasVCard4) { response, _ -> + useRemote(response) { + if (!response.isSuccess()) { + Logger.log.warning("Received non-successful multiget response for ${response.href}") + return@useRemote + } + + val eTag = response[GetETag::class.java]?.eTag ?: throw DavException("Received multi-get response without ETag") - var isJCard = hasJCard // assume that server has sent what we have requested (we ask for jCard only when the server advertises it) - response[GetContentType::class.java]?.type?.let { type -> - isJCard = type.sameTypeAs(DavUtils.MEDIA_TYPE_JCARD) - } - - val addressData = response[AddressData::class.java] - val card = addressData?.card + val addressData = response[AddressData::class.java] + val vCard = addressData?.vCard ?: throw DavException("Received multi-get response without address data") - processCard(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(card), isJCard, resourceDownloader) + processVCard(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(vCard), resourceDownloader) + } } } - } } override fun postProcess() { - groupStrategy.postProcess() + if (groupMethod == GroupMethod.CATEGORIES) { + /* VCard3 group handling: groups memberships are represented as contact CATEGORIES */ + + // remove empty groups + Logger.log.info("Removing empty groups") + localCollection.removeEmptyGroups() + + } else { + /* VCard4 group handling: there are group contacts and individual contacts */ + Logger.log.info("Assigning memberships of downloaded contact groups") + LocalGroup.applyPendingMemberships(localCollection) + } } // helpers - private fun processCard(fileName: String, eTag: String, reader: Reader, jCard: Boolean, downloader: Contact.Downloader) { + private fun processVCard(fileName: String, eTag: String, reader: Reader, downloader: Contact.Downloader) { Logger.log.info("Processing CardDAV resource $fileName") val contacts = try { - Contact.fromReader(reader, jCard, downloader) + Contact.fromReader(reader, downloader) } catch (e: CannotParseException) { Logger.log.log(Level.SEVERE, "Received invalid vCard, ignoring", e) notifyInvalidResource(e, fileName) @@ -309,10 +353,14 @@ class ContactsSyncManager( Logger.log.warning("Received multiple vCards, using first one") val newData = contacts.first() - groupStrategy.verifyContactBeforeSaving(newData) + + if (groupMethod == GroupMethod.CATEGORIES && newData.group) { + Logger.log.warning("Received group vCard although group method is CATEGORIES. Saving as regular contact") + newData.group = false + } // update local contact, if it exists - localExceptionContext(localCollection.findByName(fileName)) { + useLocal(localCollection.findByName(fileName)) { var local = it if (local != null) { Logger.log.log(Level.INFO, "Updating $fileName in local address book", newData) @@ -332,7 +380,7 @@ class ContactsSyncManager( syncResult.stats.numUpdates++ } else { - // group has become an individual contact or vice versa, delete and create with new type + // group has become an individual contact or vice versa local.delete() local = null } @@ -341,13 +389,13 @@ class ContactsSyncManager( if (local == null) { if (newData.group) { Logger.log.log(Level.INFO, "Creating local group", newData) - localExceptionContext(LocalGroup(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)) { group -> + useLocal(LocalGroup(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)) { group -> group.add() local = group } } else { Logger.log.log(Level.INFO, "Creating local contact", newData) - localExceptionContext(LocalContact(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)) { contact -> + useLocal(LocalContact(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT)) { contact -> contact.add() local = contact } @@ -355,8 +403,24 @@ class ContactsSyncManager( syncResult.stats.numInserts++ } + if (groupMethod == GroupMethod.CATEGORIES) + (local as? LocalContact)?.let { localContact -> + // VCard3: update group memberships from CATEGORIES + val batch = BatchOperation(provider) + Logger.log.log(Level.FINE, "Removing contact group memberships") + localContact.removeGroupMemberships(batch) + + for (category in localContact.contact!!.categories) { + val groupID = localCollection.findOrCreateGroup(category) + Logger.log.log(Level.FINE, "Adding membership in group $category ($groupID)") + localContact.addToGroup(batch, groupID) + } + + batch.commit() + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) - // workaround for Android 7 which sets DIRTY flag when only meta-data is changed + // workaround for Android 7 which sets DIRTY flag when only meta-data is changed (local as? LocalContact)?.updateHashCode(null) } } @@ -365,29 +429,38 @@ class ContactsSyncManager( // downloader helper class private inner class ResourceDownloader( - val baseUrl: HttpUrl + val baseUrl: HttpUrl ): Contact.Downloader { override fun download(url: String, accepts: String): ByteArray? { - val httpUrl = url.toHttpUrlOrNull() + val httpUrl = HttpUrl.parse(url) if (httpUrl == null) { Logger.log.log(Level.SEVERE, "Invalid external resource URL", url) return null } // authenticate only against a certain host, and only upon request - val client = HttpClient.Builder(context, baseUrl.host, accountSettings.credentials()) - .followRedirects(true) // allow redirects - .build() + val builder = HttpClient.Builder(context, baseUrl.host(), accountSettings.credentials()) + + // allow redirects + builder.followRedirects(true) + val client = builder.build() try { - val response = client.okHttpClient.newCall(Request.Builder() - .get() - .url(httpUrl) - .build()).execute() + val requestBuilder = Request.Builder() + .get() + .url(httpUrl) + + if (accessToken!!.isNotEmpty()) { + requestBuilder.header("Authorization", "Bearer $accessToken") + } + + val response = client.okHttpClient.newCall(requestBuilder + .build()) + .execute() if (response.isSuccessful) - return response.body?.bytes() + return response.body()?.bytes() else Logger.log.warning("Couldn't download external resource") } catch(e: IOException) { @@ -400,6 +473,6 @@ class ContactsSyncManager( } override fun notifyInvalidResourceTitle(): String = - context.getString(R.string.sync_invalid_contact) + context.getString(R.string.sync_invalid_contact) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index a082f3fad..aa76d9f83 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -169,8 +169,6 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon override fun onTokenRequestCompleted(response: TokenResponse?, ex: AuthorizationException?) { authState?.update(response, ex) - progress_bar.visibility = View.GONE - auth_token_success_text_view.visibility = View.VISIBLE getAccountInfo() } diff --git a/app/src/main/res/layout/fragment_google_authenticator.xml b/app/src/main/res/layout/fragment_google_authenticator.xml index 317a6952a..d46d68f40 100644 --- a/app/src/main/res/layout/fragment_google_authenticator.xml +++ b/app/src/main/res/layout/fragment_google_authenticator.xml @@ -19,13 +19,7 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" /> - + -- GitLab From d1a55b94478ef1a2ca9ef3544e958fcf07090785 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:03:38 +0530 Subject: [PATCH 04/10] Don't show the login activity on the Android recent apps screen. --- app/src/main/AndroidManifest.xml | 1 + .../bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt | 5 ++++- dav4jvm | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 282c54b2c..e7a8de14b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -103,6 +103,7 @@ diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index aa76d9f83..db648d89a 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -1,5 +1,6 @@ package at.bitfire.davdroid.ui.setup +import android.app.Activity import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -122,7 +123,9 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon authorizationService?.createCustomTabsIntentBuilder()!! .build()) - activity?.finish() + requireActivity().setResult(Activity.RESULT_OK) + requireActivity().finish() + } private fun createPostAuthorizationIntent( diff --git a/dav4jvm b/dav4jvm index 3e2a8eec8..1d8e59972 160000 --- a/dav4jvm +++ b/dav4jvm @@ -1 +1 @@ -Subproject commit 3e2a8eec827030ee17e81d58bcb97198d13edfff +Subproject commit 1d8e599727224bdcaeefd20cd0cc8cdf53f65df2 -- GitLab From aeeacfe155ad23cafeb32674c168ee2ad5aef344 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:26:15 +0530 Subject: [PATCH 05/10] Changed app icon and renamed app --- app/src/main/ic_launcher-web.png | Bin 23286 -> 19896 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 +++-- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 +++++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3410 -> 1440 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 1720 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3615 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 945 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2568 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4391 -> 2748 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 2442 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5640 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6521 -> 3760 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 4070 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8379 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 8597 -> 5950 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 5732 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12741 bytes .../res/values/ic_launcher_background.xml | 4 ++++ app/src/main/res/values/strings.xml | 6 ++++-- .../main/res/xml/account_authenticator.xml | 4 ++-- .../account_authenticator_address_book.xml | 2 +- 21 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/ic_launcher_background.xml diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png index 4cc81409080c7754124235d4d31c11ce7a8434f0..cafde5df9d1e89e1bdc0c9800cacbdabf7bd084a 100644 GIT binary patch literal 19896 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 literal 23286 zcmc$`g;$i{-v#;%-QC?Gl1fN-h;&LLV9>}&$AH8Tk_t!&f&v1P0!sIQNJ)bm{wV8 z^liv2sYywr*kJ;gJgFSZk{k`e(|gaF6FItbH~&@V;6MkpazUw!Nx?6Y626|E?lMrg z@sO_L3rf#^ZsFJXO%t>RwMS8INT-{N)&3Ivkb82tJt=*G0)COuT`bB6`3 z8b)x`4-4!S;)kNqhA;b?`(KB_Eaf6Xsh*KL$6?)eY zLYOGtN7h8983syPO*h7qOBE}=K{Wz$XmmUx###1b}=`-CM{?Ud7a#Fp> zUrCsWwjM~_7`5-BMVcsGiUyC(@2k59FJD+WAR(dPr84Fe&W2A8%#RcG<}5+s?wqmQ|JZ6&a(h_(<7%R6#!EiFaDA!48v5?Qhh|tEe4pSh|v8 zkgorMTjlu2+>dEK7Whp2Aw(=S&K-N}r(x zxEWE55dpp9k*q4n&26QddN<07zla3%AwV69h$rV+g_elvPPQ`Ql8M=FXkdCB36_!`xkrA z1z*0fc%PlNoJZop=;Gu}H&jFyzofja@ZV|MwWvf#IT7dd8&CcX-1rvEHY|4kIbHV- zah)?Pt@&VxX@}}9^iT?>qTm}SOlMYfX--2*sz+a^!oezAaKz=azl2VlHj6}B3O|BP7lvQE|djUIHjz@QEvTF=;IPNFdFC(?~ZGaO-F zxm_lB@vL!FdVZ1^Hkvx#leyW$J;Msh>-Zfs#A3?Mi_O(hEzal9zZ`o%04tMjDE=;Y zcFaq>nj_0ywx~8q0IcBzUs0#5k zc2d1caz$&AqGw`c=Swh~%QiE|w8&gcMIs6McN|rhUg%Wt%1?x1j+WX(0zL&ie^}FLMvFub%gjno>p@q0J@Vk0F@B$b%) zy8pmY=W22>g$w5{HsS*sBJ?!*2K)ZR={DE~G|k4ZXkV@2T1irRaG5$>2=6Tok9r|R7q=_>olYVd~IYgZgiVQa1@e9=)nxp_di7HcpO93Yj>at zi#NHN;GX{bLS=%U^P18XP6?SY5I~^&31`V4&J

=`J@7GtVO-rNfjD<+^5U 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 V`--=zXLU1yrm7z5y|QKa{{WyfOz;2z diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 476888c8a..fb9ddeec5 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,7 @@ - - + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..80b730f36 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index c402e3feb9efa131b521651be72c2a40e2b73c80..0abd7b9aef1eeb349120f5ea7cc09164fec65018 100644 GIT binary patch literal 1440 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 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..3cacca24f6bbd89db3d0ca96d6171d22e498780c GIT binary patch literal 1720 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..0bec04edce7b671e73026860a203a9530a93fd89 GIT binary patch literal 945 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p|}=FjsiGIEGZ*dOOEATR2dp?Kayj zCryT^cU8dg~VinCofj?c(4FTNohDs(8V*OIzHr z%lfXySLUVD>Uz>2i1>J1vi^R~J^fkb162W|V!h|7M&IZCHZ%ZQi509o_Ib~u48IW3 zwOy|ks(Z%@R;P=Jq|1YDPJ?EA*tx(FUczEf;Z?O|a z6=`R??J6!lx?evpXSbxO>a8!2j`o|oeruk+RpHk&)n@r}M)5N}QX*Gfmd9|*{o@mG4xqWU|qP3)PPP=-Kx=#4}4bB(SFREV@zZicnar*M&%hwQoXJr@rfC!R@bSy%62%@;&RXS4_7>so`tkRbRh-PJGR|HGl5U4c}U3oUxF1 z{cC<>dF|vj;Wk|WyPbi7k|r29Z9Q?aR1cw=D;2CyW+i3$E2>l{?2?Pg*|Qh zrhp5!=i*(n7wum3k^7zfy8Y3YUpF0)doJf$YU%nQ;=XgVRqiJr`|V$TC!hXZ)YWis z@-a(U&9@x|LVjGk|I9I+dZ~+J`Oc*qzMZJ-)HLlE_6*LEOaI4&HO)70|7T#6*|%Y` RgP;^hfv2mV%Q~loCICfDz9;|y literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..2717d05c911ea085137abb8cced628e0af447acc GIT binary patch literal 2568 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 v?oK`{ literal 4391 zcmV+?5!mjDP)&K~#90?VWje6ji#1->UAUlgZ$1Of&E3A-#oK@nwC zWL(gT3(kxe^vYGoU4y>e|2}AI_InNtFwJ)Y3QSmKKkgRk3Rai7!h0z_^(6%wa`xz9tJs9 zkmX=+8ueR6MzA3WRe~UIDVPwrGF3>mcXdyf0&F<^Xe2f<6Bwb1l@|*#W*NtwcTZWf zQ7xFN0PDUj2CNPfk|b$r>xF}WUS}XEAe2DAU_h1;rMvE)^x}KU zcc}2rfk(3?S)NeaTt<_r1{qm%lP^>P{DK(NZ#aHBgI#V7j<{z|YC~t?M5PGUK+(VKbir4xw*~jRWFZHf&r$0f2`b7~I7)@wE1b(7FemVmH z7JC~uhm{6X6%Cehn(P%gu=?2IpP`bKiUPQs{euk&R9jD~7B_eB7lMclnMzzlGV#&L zMD)AF<$EPr;%x0H3Mz6atj(sZEe8i(x@CNVatV;#lOQ&HBD2C5^VzYL9*ZC#5F30M z6Nk-b?9j{5>-;>5M-)VcMvP`?#AxQSm^Mo@IYr-+U7Ajn^|(iIdJB~*$bFJxQ-G)4 z7D0zUW5Taw&e+?D=r=@5lK?OpLYR?oJu?!nr>G*2Z}LB&q&2HojP6xRfM*dV#ipPW zQ`wmJtV_jysBtojCO^o)&}h#Rbd$KKal}PE$LZ?he4g_^)L2jTNV-ZWcSF1JawHC$ z!J@HGAqtA)4+4cSZX3Ufd*?piW10VRhDRo_V%}?9HT*tw_YE>FCfWiwtU%x{=aY)}+H3nxTpC1HBh5|+<< zkx*l}1}SJmRQPZnxbk&kgQoR(+M0DkmFQ@gHy>Tim2t~R8FQoCaWuEpbF%yp1!adR zuPdgev5Xc|0~VVJov6c4Z=ior1cSn2h#oM4QA3g$5j7rzU!cor`0EWUz2fhDnz@D} z_1oRHQ4O`cRB42@bHpXZrVu;meYf6vo88KR6Y1>B|B4f5vym0-aY%Nob_=cMCd$qg zl3nmE0RH*_lHyXBk+6Wo*ck{eOn-}l$l{rg;lJNVcFkul+u9v8?S`u1YUW$*W_DzM z$j+RPXl|?bI6bS~OxDRAWS!hWba*TarryJ}OXs^J#EsML$F#ed^$^QmxC}-5Lr4Wm2i2K*X#i=jRV}Fxvie(Ke?Ch z@;3KKdUl7Ek1}55?d{Ltkkki?dYvD4Uy+KX-h1N&pH@K*$<9AFE~lhAzsJ-5K7Tte zZT&M=yID0{c*r1@%=|0$dMccaJ&N67Qw_Ih#&Z1SP!C1$z!lMxenMZ1 zaI|DU8+NWzZJ3RQ5U!nk7nU}MrX|6rRnX7zwFsHV)A;(}`>J7Pk6VP%5TdCFDkY%j zedWnXH^sMUW-WL@@ZZXT+|YPiSbV2B5P3v}Sg7h9@Fu>x%eM1fAaH zt!FOLA%Z&u{5TnNuDOERrgFZ`-Q?2S-(B2o6O>DUB!lvt`mYGo^f**Kw8#%08$bcGG){}iYkw= zC3CIYC847P+?w(r0FABZSoP68)HPSS{!kv`PD#+L4e%2UB*aWT@3&cFZ(wr#TwdMw zI5IK<0#_#AO8?+UL_wtNTp>l3M`$rOFlOjf280ZD3NUr_)g;7BA@6jiGd_1j`0p+M zNl0KA&8B+(bj?b_f+LtS{w6lf5jyy6Z0%laF6J3SRymPj6PIpgDI0T2^8 zitv!Z94hz*NtQ^Cn+?F>qCEf%3L8qlpn-^@j<~^N0mv=hb6(u8R!3%Q8$X=d#fh`o zl+_jhoY#Hq34W@Ml-oqj7iIxiZD#hL_==Rog^U_9flFg1bGT@?GsBOT>?g8+3^5U- z7&2f4W#-14j}cokUGz83sQiRSl(7)t>>{OViSMPdU?gXV>4^o%cBjF;jTt zrk~#G*S~lIo5RYAg=;9TJW8vnk+H+utGwf-S)M;kUNA!9C=M1>Cr zprNIPu+RvGMvewx#lkfN_y+-y95kZ*6~?{rTy=QUp;HdF#Rbo#wTs z?r8r29pTYN1QDzz2mXP|LupUk3&SEWW#*Xc05Dlv*q@*7yta7u!x#-A9>wcRG@p?1 zmIwj@w_WjPgdcC%euWR{hy@B^81+By$oGTD5fY{* z8?p?#aFL4IGo5w?U3D)^89k5q=w#>j-AA@k*IdQUoR7I?(w#uN|7hQ_G)|T0AWIY(tf0{*;91;%6#_|QLG@V_0Y3sw}7P}#U%9u*C{Q)IShnx<# z&X_g!2Da{dQ*k+9S0enPYi}m?S&+Bx`k-4|mz6*Rb^Jzxh;zcXzGU zK4LdfX@ouZht}E(*_!owm*Hk4Ea3j@U%+2)Pz~n|5&8#3aMgr=(>78MP=2g#IY6$Ia%En_eS4MBVSFr3fRVlX&v>b;Lx(YZGBt_+o9SjLeo}&vT~cM3-Sl zM2+K_CGRn1)I8O2-4#Kv^W(Z{f8_C-*Af;S>3KvN;#HTGO*ZlFfhU|3>uLlAgtBbG zDjvD%Rp(4vXLu^Y_}E!Id&h^|IDHwS=*EI44NzNU*u~Ps?yqn&%T27?_ZZ7&zwFXm zmk=|BXYP2Pyy8rDWPd>Z*@M5@CGV;O&tGp~;>bB%J#mS1KDxKzm1QZvq*bcr&5Vcm z!>kuvI(ke%V0`RM#>dX2p|ytWf^Wz#JxD?MVVc_N+RalCA+moA@k5ds6El_M5h>0U za;|c`G>g4Qx3P5I(>>xl^7>&N9kj?*tj$=#-P8ZU&;6zRBk|YOPzkidotaqH%T~1*JYt#PfbrE`ghm66>rkvHR z-?M=p0tE*P-IPVdEL?_M?XQ=6dODZD+_4OQdkA^^0Q8_CGqOh(>jL{Z1^ z$OMKBN?=g`7zTw6CDa&>(GZNkJ`hQEV6mAnTU$BTTuF7qSt{#GD5}Wo(k<=@IfWT6 zox0^JURxdQE`}gI;n9KASD4OAT9O z$FEnT+zl1*$#l^LDT1KBeta1apf69P$>W>xy%8$M#gVCC(ZY)?90yF6spWje0F6$UH{xC*l{N!`#w4{|n}q zNDdiOlLLoME^KOS`S*eYb)9Y(QguZH`ZYe;Y7L!MUEDIWzT8A`xIczK9iqmEakzuQ zsHd&b+6(@Cq!VNTNtUpiWo#A)K(izXw|w<-f#SKCUFPKVsgsQU7TZe#@;&sjv!;7D zLXJb=&dsZHRfq5{zsidqPfQR6;TBnx69k!oUEgt0Vgia0g!FNSyzQx}n*0w&AAR)E hM<0Fk(Z>bI{{ukb2)dv9#*zR4002ovPDHLkV1ghWf#CoE diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..76499e569a47cb0879b32919382c62830629a7fa GIT binary patch literal 2442 zcmb_e`#%$k8}`-FvnbCqVOy=bF@%8zvEmB;s#Y$X$?Z)9FI zJu4WpUu%^~Hk^*8mG`k3SWX`sx0Gxe`0MdZWmb(n*=C$9g#+Mlgt@CP#Lm#o#CawA0#FbNF=WO7Afd1ay-3#3njkn?}O!RPdW4k($ zP7VL4eepwyRfpek@!|M}`&!`*G`c?LwD;pu`4IHco#6M>%i3}Xg}^ZVepcd25ufuw=?Ya zLm&Dh-udGLX6Du`JOScW2{?d4Gtb|DX*XH+t$STE;0qoRe7sFh8%VN5Sy~VjK~r^7 zhw2GOTa|5*1H1TX>3b-^@U&N&P~~!JC+qTW8j{YsgD5v<sK@$ANNbnDtZrBW47e^I9cEy z7ucjkcP*E{Vb79)qlrcB^9Ph7mt0c>V6R5MS{fZqv$0-Pij=k;6LXU<=sYE6n&k_N*R>ApIpvb76&uSBj+3pqj%?_KopdS*u&6d z2T`oD6g96(?9C-U&f;X2wv&J8FI~b=k7s((baOwIl@5QZ4<0r*({grP5>*Vl-ONXD7}~3Z@sRd4PmFlX?NK>8q}9xG0@gA)pKo79 z^AP%vZnlRw6&43kti_7LxuJ>^>DNMU?*xrE)ei4gmPazZei52i?(!9oc?l_6`h=2% ze@s0Y9b5Z8R4V6`MW~PJ#2t+!u!b4{m(u>s6l_Pn2JcJUwq+)y;8qIx?&Lw;wa$*D z!zgr0nQhJq-k~2TQM4>kR?5^o6QOHhQBXx+iY-iFqHWA$(eb7{$!A3_qiaZ zc~qByz`F_7B6=-!%J3_*G_JE8fjWR;47Z3pt0OwTrS$rhvVEsTD0Y zT@JN2kzIIj7+USQ8COHp71M)6+|>09>YNq)k!}Lw`mmBfrSTJXQ+MO2SjR-_zIQ@) zE6KQ?qMOt-Bv=5*Zk^9FYD=KKqmEbS6YrXU?wXlE zW^|mN)r4<;&kd}Qx944AAAy~O==XK2i5UdQ*D)hxE;i>{;=X^c6cs07JGzTzR49ix zU7mb7JXK1@B#lgIOsGVpE|O1DzFw#4u0CYaNnd&PQI|^)K{8g`{9b-3K}sxX-nmw< z+3yPZ%GF0AsudR+2bVH5IPunm{yW;M)^CdPtH?pw88~{a)FTFZp=ae@#m0U^;R|F) zwd%`fIkVqUBsOW`y0^SoE_847YhtZKGV3+HW5k$FtgvD|~@a5j` z_w4iVfklncFK3*BePrdnXo|{|0fxckD>VdN{_nghk0^hR0e586-&)v)^ z(kxjc`YG(-OA0yr8l^}NsX#es%% z8)1Ny^vGXtnSl2C2VEas_h}@h*$Sk_y$30DGzY_rEqlu0rSa~I)cvmvLDQP1<=1fq z7C2PWkEyS=b&FlC)C^l?sG++GZT%JYmMTI2t UI4vygv-!0T2K92OIUbVwe>beMEC2ui literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..526a405c5764aa12d527092c21affa398a3572ff GIT binary patch literal 5640 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 MZ>m?T^C0Sf0ByPuKmY&$ literal 6521 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4baae2652887ca25f7e51d45b774a72f207ec193 GIT binary patch literal 8379 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 Y4_CG0ob=-4)m$9VR7I#%DceN+4^iR-0ssI2 literal 8597 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c2d12ad0a079346f39e2459278c5a7e21a992092 GIT binary patch literal 5732 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 Gv;PK+kNOk< literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..0c826f57c67d457b14b57d77886401695a586001 GIT binary patch literal 12741 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 LNkvXXu0mjfgDV6? literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..73fab6619 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #7CB342 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 76bd7dd90..0a123b281 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,9 @@ - DAVx⁵ + Account Manager + WebDAV + https://www.davx5.com/ davx5app Google @@ -12,7 +14,7 @@ Account does not exist (anymore) bitfire.at.davdroid at.bitfire.davdroid.address_book - DAVx⁵ Address book + WebDav Address book at.bitfire.davdroid.addressbooks Address books This field is required diff --git a/app/src/main/res/xml/account_authenticator.xml b/app/src/main/res/xml/account_authenticator.xml index 85eaa2033..f32fccaba 100644 --- a/app/src/main/res/xml/account_authenticator.xml +++ b/app/src/main/res/xml/account_authenticator.xml @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/app/src/main/res/xml/account_authenticator_address_book.xml b/app/src/main/res/xml/account_authenticator_address_book.xml index 6305f31e7..e23f971ea 100644 --- a/app/src/main/res/xml/account_authenticator_address_book.xml +++ b/app/src/main/res/xml/account_authenticator_address_book.xml @@ -1,6 +1,6 @@ \ No newline at end of file -- GitLab From 2d9577f7f058594ebd92074676ff27566829b5dc Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:27:48 +0530 Subject: [PATCH 06/10] Enable syncing of account automatically. --- app/src/main/java/at/bitfire/davdroid/db/Collection.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/at/bitfire/davdroid/db/Collection.kt b/app/src/main/java/at/bitfire/davdroid/db/Collection.kt index 414c4d23e..a14ff470f 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/Collection.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/Collection.kt @@ -62,7 +62,7 @@ data class Collection( var source: HttpUrl? = null, /** whether this collection has been selected for synchronization */ - var sync: Boolean = false + var sync: Boolean = true ): IdEntity { -- GitLab From 22bebd76d2f9710d9ae8207f832b97d7b6189567 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:35:56 +0530 Subject: [PATCH 07/10] Use OAuth access token even if the account has only one contact and fixed issues with caldav --- app/src/main/java/at/bitfire/davdroid/DavService.kt | 8 ++++---- .../bitfire/davdroid/syncadapter/ContactsSyncManager.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/DavService.kt b/app/src/main/java/at/bitfire/davdroid/DavService.kt index ea33ba300..91e84b143 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavService.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavService.kt @@ -145,7 +145,7 @@ class DavService: android.app.Service() { * @throws HttpException * @throws foundation.e.dav4jvm.exception.DavException */ - fun queryHomeSets(client: OkHttpClient, url: HttpUrl, recurse: Boolean = true) { + fun queryHomeSets(client: OkHttpClient, url: HttpUrl, accessToken: String, recurse: Boolean = true) { val related = mutableSetOf() fun findRelated(root: HttpUrl, dav: Response) { @@ -178,7 +178,7 @@ class DavService: android.app.Service() { } } - val dav = DavResource(client, url) + val dav = DavResource(client, url, accessToken) when (service.type) { Service.TYPE_CARDDAV -> try { @@ -224,7 +224,7 @@ class DavService: android.app.Service() { } for (resource in related) - queryHomeSets(client, resource, false) + queryHomeSets(client, resource, accessToken, false) } @Transaction @@ -267,7 +267,7 @@ class DavService: android.app.Service() { service.accessToken?.let { accessToken -> service.principal?.let { principalUrl -> Logger.log.fine("Querying principal $principalUrl for home sets") - queryHomeSets(httpClient, principalUrl) + queryHomeSets(httpClient, principalUrl, accessToken) } // now refresh homesets and their member collections diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index 77818946a..9fead6301 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -283,7 +283,7 @@ class ContactsSyncManager( if (bunch.size == 1) { val remote = bunch.first() // only one contact, use GET - useRemote(DavResource(httpClient.okHttpClient, remote)) { resource -> + useRemote(DavResource(httpClient.okHttpClient, remote, accountSettings.credentials().accessToken)) { resource -> resource.get("text/vcard;version=4.0, text/vcard;charset=utf-8;q=0.8, text/vcard;q=0.5") { response -> // CardDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc6352#section-6.3.2.3] val eTag = response.header("ETag")?.let { GetETag(it).eTag } -- GitLab From d8f5e70439354f363acc0fc72e278c3e8d05c362 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:39:50 +0530 Subject: [PATCH 08/10] Use Google CalDAV API V2 instead of V1. --- .../at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index db648d89a..7e0f2b42b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -321,7 +321,7 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon fun validateUrl() { model.baseUrlError.value = null try { - val uri = URI("https://www.google.com/calendar/dav/$emailAddress/events") + val uri = URI("https://apidata.googleusercontent.com/caldav/v2/$emailAddress/events") if (uri.scheme.equals("http", true) || uri.scheme.equals("https", true)) { valid = true loginModel.baseURI = uri -- GitLab From 1e2e00b5d2a260af6abbabd0b77bba8c13d131f1 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:42:11 +0530 Subject: [PATCH 09/10] Change "Create account" to "Add account", fix non-OAuth --- .../java/at/bitfire/davdroid/DavService.kt | 114 +++++++++--------- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/DavService.kt b/app/src/main/java/at/bitfire/davdroid/DavService.kt index 91e84b143..fd6c3b68a 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavService.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavService.kt @@ -263,75 +263,75 @@ class DavService: android.app.Service() { .build().use { client -> val httpClient = client.okHttpClient + val accessToken = service.accessToken + // refresh home set list (from principal) - service.accessToken?.let { accessToken -> - service.principal?.let { principalUrl -> - Logger.log.fine("Querying principal $principalUrl for home sets") - queryHomeSets(httpClient, principalUrl, accessToken) - } + service.principal?.let { principalUrl -> + Logger.log.fine("Querying principal $principalUrl for home sets") + queryHomeSets(httpClient, principalUrl, accessToken) + } + + // now refresh homesets and their member collections + val itHomeSets = homeSets.iterator() + while (itHomeSets.hasNext()) { + val homeSet = itHomeSets.next() + Logger.log.fine("Listing home set ${homeSet.key}") + + try { + DavResource(httpClient, homeSet.key, accessToken).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation -> + if (!response.isSuccess()) + return@propfind + + if (relation == Response.HrefRelation.SELF) { + // this response is about the homeset itself + homeSet.value.displayName = response[DisplayName::class.java]?.displayName + homeSet.value.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true + } - // now refresh homesets and their member collections - val itHomeSets = homeSets.iterator() - while (itHomeSets.hasNext()) { - val homeSet = itHomeSets.next() - Logger.log.fine("Listing home set ${homeSet.key}") + // in any case, check whether the response is about a useable collection + val info = Collection.fromDavResponse(response) ?: return@propfind + info.serviceId = serviceId + info.confirmed = true + Logger.log.log(Level.FINE, "Found collection", info) + + // remember usable collections + if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type))) + collections[response.href] = info + } + } catch(e: HttpException) { + if (e.code in arrayOf(403, 404, 410)) + // delete home set only if it was not accessible (40x) + itHomeSets.remove() + } + } + // check/refresh unconfirmed collections + val itCollections = collections.entries.iterator() + while (itCollections.hasNext()) { + val (url, info) = itCollections.next() + if (!info.confirmed) try { - DavResource(httpClient, homeSet.key, accessToken).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation -> + DavResource(httpClient, url, accessToken).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ -> if (!response.isSuccess()) return@propfind - if (relation == Response.HrefRelation.SELF) { - // this response is about the homeset itself - homeSet.value.displayName = response[DisplayName::class.java]?.displayName - homeSet.value.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true - } - - // in any case, check whether the response is about a useable collection - val info = Collection.fromDavResponse(response) ?: return@propfind - info.serviceId = serviceId - info.confirmed = true - Logger.log.log(Level.FINE, "Found collection", info) + val collection = Collection.fromDavResponse(response) ?: return@propfind + collection.confirmed = true - // remember usable collections - if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) || - (service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type))) - collections[response.href] = info + // remove unusable collections + if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) || + (collection.type == Collection.TYPE_WEBCAL && collection.source == null)) + itCollections.remove() } } catch(e: HttpException) { if (e.code in arrayOf(403, 404, 410)) - // delete home set only if it was not accessible (40x) - itHomeSets.remove() + // delete collection only if it was not accessible (40x) + itCollections.remove() + else + throw e } - } - - // check/refresh unconfirmed collections - val itCollections = collections.entries.iterator() - while (itCollections.hasNext()) { - val (url, info) = itCollections.next() - if (!info.confirmed) - try { - DavResource(httpClient, url, accessToken).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ -> - if (!response.isSuccess()) - return@propfind - - val collection = Collection.fromDavResponse(response) ?: return@propfind - collection.confirmed = true - - // remove unusable collections - if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || - (service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) || - (collection.type == Collection.TYPE_WEBCAL && collection.source == null)) - itCollections.remove() - } - } catch(e: HttpException) { - if (e.code in arrayOf(403, 404, 410)) - // delete collection only if it was not accessible (40x) - itCollections.remove() - else - throw e - } - } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a123b281..b65c718f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -272,7 +272,7 @@ Login with URL and client certificate Select certificate Login - Create account + Add account Account name Use of apostrophes (\'), have been reported to cause problems on some devices. Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can\'t have two accounts with the same name. -- GitLab From 41438c421ae7e8edf2e8bb61964d6191d83b3a85 Mon Sep 17 00:00:00 2001 From: Narinder Rana Date: Wed, 6 Jul 2022 09:48:11 +0530 Subject: [PATCH 10/10] Change "Create account" to "Add account", fix non-OAuth : fix data base --- app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt index 0274306a5..683b624b4 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt @@ -117,8 +117,8 @@ abstract class AppDatabase: RoomDatabase() { "CREATE TABLE service(" + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + "accountName TEXT NOT NULL," + - "accessToken TEXT NOT NULL," + - "refreshToken TEXT NOT NULL," + + "accessToken TEXT ," + + "refreshToken TEXT ," + "type TEXT NOT NULL," + "principal TEXT DEFAULT NULL" + ")", -- GitLab