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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
    c)+=A>!pcE;fRU z4aq+LtLj(nUhv?K`hrHzz4<3LZm#z%`W3mf^61b8B{qt>=_qPRui6T5!TA zBjg0jsnoW;Yv12oBGma|TD;wjEy+!8*7*}H=4RBr{?XXFJyynJSMrwo$vsnif4#~& zuW|SCe)|QRR{6v!37uu`>3GVJut@T!;rmF?xV zrgKes$sKZn)#%`k|Nnn|Th%UI7j;HZ-D&4r#$!p2sgIfEpRJ#-`||v;&wr13%-q*x zRLfdA?^jG^rPI{lQz3z&YU_Qxrl^M;>ta+oFj?f+!b2bJJsDNiZA$eelYRa>-sey4 z+Og=Fr+wYOhi&zm_vTv^DwY4LzW%p(^)kQJb#9;brBsVC97yRr^6=Gt@i4QR)%)Tm z-e{HUbT3))^z`)UGb?x3sU-iq_DzSOAZ%jIw4)M|Cx8E2)h_)im)}Ti;=koP|LpGG zm%Kz?BgtEmq2Y$d$rt%ye}f+zZ)2BS?ZMulQv30C_cT?Zs<2B-x1}>OOqnt_Zbfyt z-K`rL)e}B;{3+WmE|+`f!OA~wp6BkSOEL&}FI=Kt6gt)1_3H!uqYt~>pj28BAC%;*Up_We8L%=`Yvyr(s;&+Hf$Z%j@;{%>iyU!|6K*t`i_Jc8!F zo!OAo5xSE3;+D&k_if#<>gB)re;(XWynR^2IcI+OyIu8pSEt;X^;_k$ujln!^Vk^# zY>!mZaw^iS}DHnIX-O0dkO~R91HTP>uoO#@%uUGp` z-fgkHlA^JaLE>EewexFFFI&CdEcF@Jd6hJYnVFN8RT-IXOJ`;f>0j=0O2g+=aFAC? uOX7{FdFyZQ{nS3&7$nC)V{kz7KR@T|7a5z^Oaq;p$>8bg=d#Wzp$Pz>GfeOR 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 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F7Fi*Ar-gYMrQj&r%KFQ zbZqAlmyhSq#3*&1-o$mx!1N!3S7Pw=1-m_ZZr@>D{Cne()`rdrGdaX-IwE%=ta^~u@JLFc~Pp0B=l zyLeydYPlG_8rF37-7B@H^jlnRIGFYzYwNGX8LKTWso6aHFELU2!MbZ~>-taK|24;$ zr<$2r-YoD#R_+Dii>#}-16fz?%UGLrVkR5UM5(K+36oSden?K)SI?#F5XkyZFL{34 zH}B*NazZK_Cg>#2Je97*J%#yB^G_}3DQ>Ar<2xyNSlIb+-Vzk5I4%Raj?s%n0! z&AaxXxN1eM`}HaF8cx2vmoD*)<45ni&Wg&(mSJ&BzlGxN3M>rd+pW|XyHH?cx; zJa4VJ=n&hiTk`Z-VNiVa!C%+~7l=jF6({_t2;(VS7I zZG+&aJr`4!v{he^$l1U3&CV&S4x1R=)c06a{emxiuZ$N1r?%KZ`Ny+czHV?O-k{L4`r`f*}1$AWX-Q%||YuFQyJ z*}-{D`pC5v?{;`y)H$D`-k_!$8Ypnw`|RLPm3 zax5g1o1^53=3y4ew6A8uE8bo?;mu&!GciJ|@%Hc6E5DpG|B_p(F;|aw*^fu%5&v@1 zlMgwrYrbBZdDF^jU2^x@&@%JbI3@w*8P9*3&7L9Mn5ZER(S>N~SZvl9HLy;(G8 z+2t?iGujmz*R)ye{N2zctXva!{;%#tJN=r&ZcmpbDId?fx2Y>{PK=yVy>j!UCJDp) zYdTBgrg&DHzTf%j@0xf1(Q7&l=1iV%B;3H$GHH{-h1jh2@8Y^UA35u>oE5fT{&LFW zWq~IgKBu*;>s*$6KH4Lt_2In-y4>wA+M*&K<(JDaW<>bR@t(ZnN!hJO+0!pwj1m=p z?0rx=?aP$7>N6j^cvMA;{|6~vzIWe2yxvmD$}d^&(UqBXmFfHHzE^weKJf76`FY$t zO1Z7t?bgaowv*SHsa@dTIzP}YmM1fM(#oIfjb_b}d#pR#wEkJZnzGZUx~e7|*pn=j z;WeXug7OK;g~wVqG(N99xc*4FqR+|+jVtbM>yz||IPz!HhRB!t#S9EfSyfpLnbvoE zx?QQ9;?C2vV4YO;r{LA=n-eRG zdD&fd>^9D+d*5NB+N!d5(&-Irw>|4Qllynsa#6)}^Xx-LHEoCWfA_Vf|LiOJK6T4a z+rxT4dE|ZW#B9iW9Lynap6#y{Zd`ru@M+rv*{?S{#?^H%E7&meed}IZo4d@7u9tp$ zduoVu9ltR@dxq`HlxSw1GTA-Kk#>Gx>#odHo3VjkPkiS;X5l6Kf9+c5s>#5>z~JfX K=d#Wzp$Pz*z_9@U literal 3410 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 Jvd$@?2>`x)s=)vN 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 zcmeAS@N?(olHy`uVBq!ia0y~yU|0mg9Bd2>42M36Ni#68)qA=)hE&{oJJ&ZO)>Y*A z?^)9(Em7fJRwx>GUrXsy*E*HeK`$2ZPPyc39+`68bz)218WE9A6BWbu(+&3)C+&&;j-`S-#8%01=9&(7L^-#62; zIPJU~0~vsOPBFX7-$iX19#=11=lj6xm~ptYtIhTCBFVtuEjqUftT!)wpZO~N*jK5! zHH$BF{d~2E^WvP0yRFHw>tC*qeLlPGzft`53?CEyTP5FeUfusxzj?OYfd=M9o)^1T z_lfo5x=oPK(G+O~zwa%aoE(yndg-|X}CN%|qXi%r?~HM4)c zcv$1JL*ilK&#f<}Wki*F$8Y{8xuHYybA?RwJx=-VIAPgJFCOES=v+;~>ymrhScAXRRDEs{+kf_-z+4`I z`<{EJ9nBAYCMj(AaKXcruS**W4>Bz%o$`rCJHe|VSePkb+>$G*udcA=m< zulq7(wvT-h$E>@QR~aVuCyJeQ?iO5mPgGti?d=4outq_3-uHT!+Y0@qkJ)w_Kblo= zz+&asro=bmJ(IIfe#x7>d*SmfiEozoCO=;%akAyE;I|ob40`^%wAdQx{C72Y6w#tH zeRJE#`Mm=FCHyv;KdNcz+mj{n|0>7F^N&TMg|EfVsXe~AEw^Lhn%Dc4%|1$(J#tyX za^=3uUbc_lUu{h`XiR?7Vu2ck_MU;P$3GU`m~g$hi*QKOr;N__)%K zDE`(fsB1W#^0Ji@O(Xzf*R1?su2@9s2}oUq5={ zYgD`0TWyQ{%AIkddmlyJFn_bHnE$_}-P_=ca?z+A^u7vS)MFv}6d}U?u zmj=~+HN6=#zkJwim)7r^bN=+A@2>kNe3>v^Q1{Y-x(mDG<6GZWxqWH-XFP#zTBD$| z+Oc1|n&fvhNZw$)^?l6?W$|t2lsh-F$%KS z{gJORY;SAg+~1@oe4diXKJ}*GD{hg!69S|Ar^=kzF!#!a;#aC_UHWM^?sc=?KE?K% zV{Y)0CnpxmgiDmTt~{YUYm%~{)p8HPWzv${zq>Vl3u@7HIxeE=U2gmMTU`FewNsXM z->_UO_Ucb;iOJzIxvMv#8=d9~TCebU`DndMyHVC-#onD)XEJ(;tJj5psZqN%-|_ah z>@|J66MxMO{ZhgG|M7n1hh?QAG7Ad!xVFv>`*L9ZY|VR;U;QdJC(O3^u>4S;b}`GW z|1tSaxi58ZiuF0H-oboYhHK}PLm5%-$6a4PDbPr9O4BL!S$96@dAw|~0AleFgL*7qzP4Rd`f zzwEz#_Qmx7Voa&#_7{$)**e6O?hRYv8_ccqcxnDIyNgK=a#`!r?^`eWY`6SkX6zB! zcNMX}=6(J(^LOLr|Kh9fBr~s_U3l>3!)(R6^~!gvSE<&X-Q@R(N$t#ML)phumPefV zWO1&V!C3UiMTyzExy;Oy&1R;@oW0Pp`i$_l`I7A3ItxP&=7#y)y53{zJ0-&JM#E_- z+!eYUSUrmelF{r5}E*^IxMvS literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7ac653ef931da6b73605e7dae066f6f490954 GIT binary patch literal 3615 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 TOm+~I0x9rx^>bP0l+XkKMNGaZ 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 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F|^jp7cNa$WRT zNK$f6lJ(q-ODfG*muQ(@RTK@pFi!>$yJ8C7T5J3 zfBwt=z@h4zV|%BaYFP7o)sAfm?>2v|oTd8l=c^+>VoEojTd=OvJVWYHv8iv)#qV#7 zf64A{i|ekgD{(IP5~P3pX%^E4dL|7;7P2KpU+k3a?)9>yl%+0+$ zOKVwwzWU`E6gAtoXqBDH{5k*BZ$|8BxZzXvMsZG?S@c}DP>F}L1SdUN=)QY$u8N>m zeD>$RC)WipT(apMM`p%a<-4}AOSW&n{w(W#mSXAl{YtIk_KNZ~AH#*_n@^uF-?Vv= z@zmWe%5%JO1Rs~Oh0eJ7S>~zcw8^=TeT}-(P*Y z!ZT!7eoKgXyM_GpwKFsQBK&Tw(XQxhJDgVZ{hsftj?92l-K%#W_sEjc@>H^Qjk7q| zX)?t}aK2{R?X|*}b}SE@?k#l9tKj2PlS`R<%vFv)eSL1P^p%eK0>4G^?JG)LSwr74 z?pznPaPiqdUE}sXp4R-Yp6waE3?p3225zw6K57%BFdL;CkT^NI5BViskHq)eUT z@omljngfsS%@e#nJL`nI>%p*6CcoIZ>sG|A`CfPAm559CBI7BILhqwF91ndvzUS@P zXsf)*{@!6XkNh&N)SvJu>$iHwBEKC4Z&_MX>n+P)T}@l*e3Q35zh!0n8~er2pEH=Z?+| zB3t7(hOFqT{lwL+^pt1PoKJV%~O5N|NrJXMr;3d2{+#N?CE;{$iJ5g<_jS+vxASJh z)7iCkuH3G+=NUG5O}F$57hbumYR<#DSNrd)xoZ|OHx`?f9BG|3IYulw^}s9UuC|{K zC)=`FTnJm=8P)ts?>s}mqlZg)*6Addt6K*A?Ayx7KGiorW4W)Hw7?vLzmm~x4d?Hh zGiU6Owb?x}`@o^lVB4jZA4+nj?P^SCm?ao)#`?!}tNDbsX;z>9&h373YOR(R|EiN~ zw`KRZmCWwZJL$FhLbRkqXY6BdZFOH0>4uh$vrp6wtu)ikn9u&$^i9Cie)GIZI}$d% z`!HWcBuVe4!)}9;xo<2``Me@(;4=#)og#JtroT8T;Q`G<;OzUFP~^mPMvr6h{#h(h9{d6wmKN zx_Wc&OTBw&~`$2(3GkRZ0|CTcs@P7E~WTso~4||%M%&V zGnc>Ywq2sM{`h~shsKu{trfYwSzYblBh9XjSNBX4x+8O$xl)7iGh=UG#EPevHf$4) zo2Gr=b@ls+>&v4oTyr!JPfEYu^IhWg!Q3~j-E~r15_j=!mQd;0V}9TT^JV6^qc-(x zJKih^xf$qj@O9*Kp@{dVz3+Su?_OaXw=L%t>-+u7s`GS9o}6n}5wn%sc76M$YZ0uA z`F;2+He64BrFQ+@zv(i+)A&~}*uB3;!BeE|`vrs3HrqQt{bo89CY}4J?&{U#FH7Q| zZkwbnls0XF%og*?;EW!-oXP+%W`Wad=axsvHWqGM z|2AdbbnW$L17|nb1ZHwAF1pyl@Iq|U>4VGy2Rk-)f4O|0`Pri%6_+i0>-Qga~CH)7Y*pD_;1Ga8K+tXh0%kw@f%JvuoX(m(9q^JyjD{x&_c$M+60_XU>t^4xkcl}#jJ zPP>|D!A-e9XXy?JwJ=fmhu-HuDlaa?7qWj8%4-pn>o~lk zBz3w$+MD8(j?ewm&vY3_PW&wz%e}aMc7tvEl0B{#mAh;^EWSkkxFUVJyefRtD!v{`7vjb=DmAabmK_>0? z^eO&vMUFJFJdaJ9{w%{NU2HS5eX8F%%zet&qfIO~J#x`SJHwBNIiobXX#^2+kv z`uk@#81{2?DSTYk^Ka?y>BqNhJauxj{O99`s%zTbw`3pV{J!J=3;S;m=5kC>QaJqC zQPKa%<%93#>(th?x1PUK$ZMt9rgvE2`G4lk`p*vTDso!Oz`(%Z>FVdQ&MBb@0Jsh4 AQ~&?~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 2aa7185580e3d47e30abd92c4694d9877e21e66d..7f7b37920902820d13dd6a6ef777349a9a12ab25 100644 GIT binary patch delta 2741 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 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 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 AEC2ui 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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 OFnGH9xvX4A0#j?ONqIWY|>EJuXW%&*9rgf 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 literal 6521 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 DoHa)X diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..495a066b0598b59f0ceafa6f2774e352dbc56be5 GIT binary patch literal 4070 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index f50fed52275f4387fb1b7ac33354aee4e813482b..387ff43694164b6b5f43348873a49772b5491069 100644 GIT binary patch literal 5950 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 literal 8597 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 a^PfGwh5dMb;YJ1q1_n=8KbLh*2~7ZP6bHuu 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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 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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 KxvX + + #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