From ca0286ba0329d37e4c606669bfb8593ad9aef966 Mon Sep 17 00:00:00 2001 From: jat Date: Wed, 28 Aug 2024 16:37:30 +0200 Subject: [PATCH] Fix synchronized contacts not appearing in some contact apps Certain contact apps require the account to be syncable. In order to do this, we implement a stubbed sync service and adapter which does nothing and ask Android not to call it. Preexisting installations of the app will need to turn contact synchronization off and on again to receive this fix. Closes #6568. Co-authored-by: paw --- app-android/app/src/main/AndroidManifest.xml | 13 + .../tutanota/AndroidMobileContactsFacade.kt | 633 ++++++++++++++---- .../java/de/tutao/tutanota/StubSyncService.kt | 53 ++ .../app/src/main/res/xml/syncadapter.xml | 11 + 4 files changed, 589 insertions(+), 121 deletions(-) create mode 100644 app-android/app/src/main/java/de/tutao/tutanota/StubSyncService.kt create mode 100644 app-android/app/src/main/res/xml/syncadapter.xml diff --git a/app-android/app/src/main/AndroidManifest.xml b/app-android/app/src/main/AndroidManifest.xml index c942762659c2..17d32e6a3089 100644 --- a/app-android/app/src/main/AndroidManifest.xml +++ b/app-android/app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ + @@ -150,6 +151,18 @@ android:resource="@xml/authenticator"/> + + + + + + + cursor?.forEachRow { @@ -82,11 +90,13 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo override suspend fun getContactBooks(): List { checkContactPermissions() - return resolver.query(RawContacts.CONTENT_URI, - arrayOf( - RawContacts.ACCOUNT_TYPE, - RawContacts.ACCOUNT_NAME, - ), null, null, null).use { cursor -> + return resolver.query( + RawContacts.CONTENT_URI, + arrayOf( + RawContacts.ACCOUNT_TYPE, + RawContacts.ACCOUNT_NAME, + ), null, null, null + ).use { cursor -> val accounts = mutableMapOf() cursor!!.forEachRow { val accountType = cursor.getString(0) @@ -123,10 +133,12 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo if (accountName != null) queryValues.add(accountName) - return resolver.query(RawContacts.CONTENT_URI, arrayOf( + return resolver.query( + RawContacts.CONTENT_URI, arrayOf( RawContacts._ID, RawContacts.SOURCE_ID, - ), query, queryValues.toTypedArray(), null).use { cursor -> + ), query, queryValues.toTypedArray(), null + ).use { cursor -> cursor!!.mapTo(mutableListOf()) { val contactId = cursor.getLong(0) val sourceId = cursor.getString(1) @@ -138,6 +150,7 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo override suspend fun saveContacts(username: String, contacts: List) { checkContactPermissions() + checkSyncPermission() val matchResult = matchStoredContacts(username, contacts, false) for (contact in matchResult.newServerContacts) { createContact(username, contact) @@ -156,34 +169,41 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo } private fun resetDirtyState(rawId: Long) { - val updateDirtyStateOp = ContentProviderOperation.newUpdate(RAW_CONTACT_URI).withSelection("${RawContacts._ID} = ?", arrayOf(rawId.toString())).withValue(RawContacts.DIRTY, 0).build() + val updateDirtyStateOp = ContentProviderOperation.newUpdate(RAW_CONTACT_URI) + .withSelection("${RawContacts._ID} = ?", arrayOf(rawId.toString())).withValue(RawContacts.DIRTY, 0).build() resolver.applyBatch(ContactsContract.AUTHORITY, arrayListOf(updateDirtyStateOp)) } private fun updateSourceId(rawId: Long, sourceId: String) { - val updateSourceIdOp = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI).withSelection("${RawContacts._ID} = ?", arrayOf(rawId.toString())).withValue(RawContacts.SOURCE_ID, sourceId).build() + val updateSourceIdOp = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection("${RawContacts._ID} = ?", arrayOf(rawId.toString())) + .withValue(RawContacts.SOURCE_ID, sourceId).build() resolver.applyBatch(ContactsContract.AUTHORITY, arrayListOf(updateSourceIdOp)) } private data class MatchContactResult( - /** do not exist on the device yet but exists on the server */ - val newServerContacts: MutableList = mutableListOf(), - /** exist on the device and the server and are not marked as dirty */ - val existingServerContacts: MutableList> = mutableListOf(), - /** contacts that exist on the device and on the server but we did not map them via sourceId yet */ - val nativeContactWithoutSourceId: MutableList = mutableListOf(), - /** exists on native (and is not marked deleted or dirty) but doesn't exist on the server anymore */ - val deletedOnServer: MutableList = mutableListOf(), - /** exist in both but are marked as dirty */ - val editedOnDevice: MutableList = mutableListOf(), - /** exists on the device but not on the server (and marked as dirty) */ - val createdOnDevice: MutableList = mutableListOf(), - /** exists on the server but marked as deleted (and dirty) on the device; server IDs */ - val deletedOnDevice: MutableList = mutableListOf(), + /** do not exist on the device yet but exists on the server */ + val newServerContacts: MutableList = mutableListOf(), + /** exist on the device and the server and are not marked as dirty */ + val existingServerContacts: MutableList> = mutableListOf(), + /** contacts that exist on the device and on the server but we did not map them via sourceId yet */ + val nativeContactWithoutSourceId: MutableList = mutableListOf(), + /** exists on native (and is not marked deleted or dirty) but doesn't exist on the server anymore */ + val deletedOnServer: MutableList = mutableListOf(), + /** exist in both but are marked as dirty */ + val editedOnDevice: MutableList = mutableListOf(), + /** exists on the device but not on the server (and marked as dirty) */ + val createdOnDevice: MutableList = mutableListOf(), + /** exists on the server but marked as deleted (and dirty) on the device; server IDs */ + val deletedOnDevice: MutableList = mutableListOf(), ) - private fun matchStoredContacts(username: String, serverContacts: List, isSync: Boolean): MatchContactResult { + private fun matchStoredContacts( + username: String, + serverContacts: List, + isSync: Boolean + ): MatchContactResult { // Map from server contact id to the contact. // We remove from it as we match to find only those that are new val serverContactsById = serverContacts.groupBy { it.id }.mapValuesTo(mutableMapOf()) { it.value[0] } @@ -194,13 +214,19 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo val sourceId = cursor.getString(1) if (sourceId == null) { - val serverContactWithMatchingRawId = serverContacts.find { contact -> contact.rawId == rawContactId.toString() } + val serverContactWithMatchingRawId = + serverContacts.find { contact -> contact.rawId == rawContactId.toString() } if (serverContactWithMatchingRawId?.id != null) { serverContactsById.remove(serverContactWithMatchingRawId.id) // This will be called when we process the entity event // after creating the contact - result.nativeContactWithoutSourceId.add(readContact(rawContactId, serverContactWithMatchingRawId.id)) + result.nativeContactWithoutSourceId.add( + readContact( + rawContactId, + serverContactWithMatchingRawId.id + ) + ) } else { val newContact = readContact(rawContactId, null).toStructured() result.createdOnDevice.add(newContact) @@ -230,6 +256,7 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo override suspend fun syncContacts(username: String, contacts: List): ContactSyncResult { checkContactPermissions() + checkSyncPermission() val matchResult = matchStoredContacts(username, contacts, true) for (contact in matchResult.newServerContacts) { @@ -249,8 +276,13 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo activity.getPermission(Manifest.permission.WRITE_CONTACTS) } + private suspend fun checkSyncPermission() { + activity.getPermission(Manifest.permission.WRITE_SYNC_SETTINGS) + } + private fun deleteRawContact(rawId: Long): Int { - val uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawId).buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build() + val uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawId).buildUpon() + .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build() return resolver.delete(uri, null, null) } @@ -263,10 +295,17 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo createSystemAccount(username) } - val rawContactUri = RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(RawContacts.ACCOUNT_NAME, username).appendQueryParameter(RawContacts.ACCOUNT_TYPE, TUTA_ACCOUNT_TYPE).build() + val rawContactUri = RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(RawContacts.ACCOUNT_NAME, username) + .appendQueryParameter(RawContacts.ACCOUNT_TYPE, TUTA_ACCOUNT_TYPE).build() if (sourceId != null) { - return resolver.query(rawContactUri, arrayOf(RawContacts._ID, RawContacts.SOURCE_ID), "${RawContacts.SOURCE_ID} = ?", arrayOf(sourceId), null) + return resolver.query( + rawContactUri, + arrayOf(RawContacts._ID, RawContacts.SOURCE_ID), + "${RawContacts.SOURCE_ID} = ?", + arrayOf(sourceId), + null + ) } return resolver.query(rawContactUri, arrayOf(RawContacts._ID, RawContacts.SOURCE_ID), null, null, null) @@ -274,13 +313,21 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo private fun createSystemAccount(username: String) { val userAccount = Account(username, TUTA_ACCOUNT_TYPE) + + // Disable all usage of the stub sync adapter by the OS + ContentResolver.setSyncAutomatically(userAccount, ContactsContract.AUTHORITY, false) + val isAccountAdded = AccountManager.get(activity).addAccountExplicitly(userAccount, null, null) if (!isAccountAdded) { Log.w(TAG, "Failed to create new account?") } } - private fun checkContactDetails(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactDetails( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { if (storedContact.birthday != serverContact.birthday) { checkContactBirthday(storedContact, ops, serverContact) } @@ -290,47 +337,83 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo } if (storedContact.givenName != serverContact.firstName) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, serverContact.firstName).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, serverContact.firstName).build() ops += updateNameOp } if (storedContact.middleName != serverContact.middleName) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, serverContact.middleName).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, serverContact.middleName).build() ops += updateNameOp } if (storedContact.lastName != serverContact.lastName) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, serverContact.lastName).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, serverContact.lastName).build() ops += updateNameOp } if (storedContact.title != serverContact.title) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, serverContact.title).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, serverContact.title).build() ops += updateNameOp } if (storedContact.nameSuffix != serverContact.nameSuffix) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, serverContact.nameSuffix).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, serverContact.nameSuffix).build() ops += updateNameOp } if (storedContact.phoneticFirst != serverContact.phoneticFirst) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, serverContact.phoneticFirst).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue( + ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, + serverContact.phoneticFirst + ).build() ops += updateNameOp } if (storedContact.phoneticMiddle != serverContact.phoneticMiddle) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, serverContact.phoneticMiddle).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue( + ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, + serverContact.phoneticMiddle + ).build() ops += updateNameOp } if (storedContact.phoneticLast != serverContact.phoneticLast) { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, serverContact.phoneticLast).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue( + ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, + serverContact.phoneticLast + ).build() ops += updateNameOp } if (storedContact.nickname != serverContact.nickname) { - val updateNicknameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.Nickname.NAME, serverContact.nickname).build() + val updateNicknameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.Nickname.NAME, serverContact.nickname).build() ops += updateNicknameOp } @@ -339,197 +422,368 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo } } - private fun checkContactBirthday(storedContact: AndroidContact, ops: ArrayList, serverContact: StructuredContact) { + private fun checkContactBirthday( + storedContact: AndroidContact, + ops: ArrayList, + serverContact: StructuredContact + ) { // If the birthday wasn't added during contact creation, it's // necessary to add and not just update it if (storedContact.birthday == null) { - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Event.START_DATE, serverContact.birthday).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Event.START_DATE, serverContact.birthday) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue( + ContactsContract.CommonDataKinds.Event.TYPE, + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY + ).build() + ) } else { - ops.add(ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY).withValue(ContactsContract.CommonDataKinds.Event.START_DATE, serverContact.birthday).build()) + ops.add( + ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue( + ContactsContract.CommonDataKinds.Event.TYPE, + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY + ).withValue(ContactsContract.CommonDataKinds.Event.START_DATE, serverContact.birthday).build() + ) } } - private fun checkContactNote(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactNote( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { if (storedContact.notes != serverContact.notes) { if (storedContact.notes == "") { - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Note.NOTE, serverContact.notes).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Note.NOTE, serverContact.notes).build() + ) } else { - val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.Note.NOTE, serverContact.notes).build() + val updateNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.Note.NOTE, serverContact.notes).build() ops += updateNameOp } } } - private fun checkContactCompany(storedContact: AndroidContact, ops: ArrayList, serverContact: StructuredContact) { + private fun checkContactCompany( + storedContact: AndroidContact, + ops: ArrayList, + serverContact: StructuredContact + ) { // If the company wasn't added during contact creation, it's // necessary to add and not just update it if (storedContact.company == "") { - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, serverContact.company).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.TYPE, ContactsContract.CommonDataKinds.Organization.TYPE_WORK).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, serverContact.department).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.TITLE, serverContact.role).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE + ).withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, serverContact.company).withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE + ).withValue( + ContactsContract.CommonDataKinds.Organization.TYPE, + ContactsContract.CommonDataKinds.Organization.TYPE_WORK + ).withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE + ) + .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, serverContact.department) + .withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE + ).withValue(ContactsContract.CommonDataKinds.Organization.TITLE, serverContact.role).build() + ) } else { - ops.add(ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", arrayOf(storedContact.rawId.toString())).withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, serverContact.company).withValue(ContactsContract.CommonDataKinds.Organization.TYPE, ContactsContract.CommonDataKinds.Organization.TYPE_WORK).withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, serverContact.department).withValue(ContactsContract.CommonDataKinds.Organization.TITLE, serverContact.role).build()) + ops.add( + ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?", + arrayOf(storedContact.rawId.toString()) + ).withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, serverContact.company).withValue( + ContactsContract.CommonDataKinds.Organization.TYPE, + ContactsContract.CommonDataKinds.Organization.TYPE_WORK + ).withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, serverContact.department) + .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, serverContact.role).build() + ) } } - private fun checkContactMailAddresses(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactMailAddresses( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { for (serverMailAddress in serverContact.mailAddresses) { val storedAddress = storedContact.emailAddresses.find { it.address == serverMailAddress.address } if (storedAddress != null) { if (storedAddress.type != serverMailAddress.type.toAndroidType()) { - val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Email.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedAddress.address)).withValue(ContactsContract.CommonDataKinds.Email.TYPE, serverMailAddress.type.toAndroidType()).build() + val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Email.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedAddress.address) + ).withValue(ContactsContract.CommonDataKinds.Email.TYPE, serverMailAddress.type.toAndroidType()) + .build() ops += updateTypeOp } if (storedAddress.customTypeName != serverMailAddress.customTypeName) { - val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Email.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedAddress.address)).withValue(ContactsContract.CommonDataKinds.Email.LABEL, serverMailAddress.customTypeName).build() + val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Email.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedAddress.address) + ).withValue(ContactsContract.CommonDataKinds.Email.LABEL, serverMailAddress.customTypeName).build() ops += updateCustomTypeNameOp } } else { // it's a new mail address - val createEmailAddressOp = insertMailAddressOperation(serverMailAddress).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).build() + val createEmailAddressOp = insertMailAddressOperation(serverMailAddress).withValue( + ContactsContract.Data.RAW_CONTACT_ID, + storedContact.rawId + ).build() ops += createEmailAddressOp } } for (storedMailAddress in storedContact.emailAddresses) { if (serverContact.mailAddresses.none { it.address == storedMailAddress.address }) { - val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Email.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedMailAddress.address)).build() + val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Email.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedMailAddress.address) + ).build() ops += deleteOp } } } - private fun checkContactAddresses(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactAddresses( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { for (serverAddress in serverContact.addresses) { val storedAddress = storedContact.addresses.find { it.address == serverAddress.address } if (storedAddress != null) { if (storedAddress.type != serverAddress.type.toAndroidType()) { - val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.StructuredPostal.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedAddress.address)).withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, serverAddress.type.toAndroidType()).build() + val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.StructuredPostal.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedAddress.address) + ).withValue( + ContactsContract.CommonDataKinds.StructuredPostal.TYPE, + serverAddress.type.toAndroidType() + ).build() ops += updateTypeOp } if (storedAddress.customTypeName != serverAddress.customTypeName) { - val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.StructuredPostal.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedAddress.address)).withValue(ContactsContract.CommonDataKinds.StructuredPostal.LABEL, serverAddress.customTypeName).build() + val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.StructuredPostal.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedAddress.address) + ).withValue(ContactsContract.CommonDataKinds.StructuredPostal.LABEL, serverAddress.customTypeName) + .build() ops += updateCustomTypeNameOp } } else { // it's a new address - val createAddressOp = insertAddressOperation(serverAddress).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).build() + val createAddressOp = insertAddressOperation(serverAddress).withValue( + ContactsContract.Data.RAW_CONTACT_ID, + storedContact.rawId + ).build() ops += createAddressOp } } for (storedAddress in storedContact.addresses) { if (serverContact.addresses.none { it.address == storedAddress.address }) { - val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.StructuredPostal.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedAddress.address)).build() + val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.StructuredPostal.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedAddress.address) + ).build() ops += deleteOp } } } - private fun checkContactPhonesNumbers(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactPhonesNumbers( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { for (serverPhoneNumber in serverContact.phoneNumbers) { val storedNumber = storedContact.phoneNumbers.find { it.number == serverPhoneNumber.number } if (storedNumber != null) { if (storedNumber.type != serverPhoneNumber.type.toAndroidType()) { - val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Phone.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedNumber.number)).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, serverPhoneNumber.type.toAndroidType()).build() + val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Phone.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedNumber.number) + ).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, serverPhoneNumber.type.toAndroidType()) + .build() ops += updateTypeOp } if (storedNumber.customTypeName != serverPhoneNumber.customTypeName) { - val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Phone.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedNumber.number)).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, serverPhoneNumber.customTypeName).build() + val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Phone.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedNumber.number) + ).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, serverPhoneNumber.customTypeName).build() ops += updateCustomTypeNameOp } } else { // it's a new phone number - val createEmailAddressOp = insertPhoneNumberOperations(serverPhoneNumber).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).build() + val createEmailAddressOp = insertPhoneNumberOperations(serverPhoneNumber).withValue( + ContactsContract.Data.RAW_CONTACT_ID, + storedContact.rawId + ).build() ops += createEmailAddressOp } } for (storedPhoneNumber in storedContact.phoneNumbers) { if (serverContact.phoneNumbers.none { it.number == storedPhoneNumber.number }) { - val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Phone.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedPhoneNumber.number)).build() + val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Phone.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedPhoneNumber.number) + ).build() ops += deleteOp } } } - private fun checkContactCustomDates(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactCustomDates( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { for (serverCustomDate in serverContact.customDate) { val storedDate = storedContact.customDate.find { it.dateIso == serverCustomDate.dateIso } if (storedDate != null) { if (storedDate.type != serverCustomDate.type.toAndroidType()) { - val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Event.DATA} = ?", arrayOf(storedContact.rawId.toString(), serverCustomDate.dateIso)).withValue(ContactsContract.CommonDataKinds.Event.TYPE, serverCustomDate.type.toAndroidType()).build() + val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Event.DATA} = ?", + arrayOf(storedContact.rawId.toString(), serverCustomDate.dateIso) + ).withValue(ContactsContract.CommonDataKinds.Event.TYPE, serverCustomDate.type.toAndroidType()) + .build() ops += updateTypeOp } if (storedDate.customTypeName != serverCustomDate.customTypeName) { - val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Event.DATA} = ?", arrayOf(storedContact.rawId.toString(), serverCustomDate.dateIso)).withValue(ContactsContract.CommonDataKinds.Event.LABEL, serverCustomDate.customTypeName).build() + val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Event.DATA} = ?", + arrayOf(storedContact.rawId.toString(), serverCustomDate.dateIso) + ).withValue(ContactsContract.CommonDataKinds.Event.LABEL, serverCustomDate.customTypeName).build() ops += updateCustomTypeNameOp } } else { // it's a new custom dte number - val createCustomDate = insertCustomDateOperation(serverCustomDate).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).build() + val createCustomDate = insertCustomDateOperation(serverCustomDate).withValue( + ContactsContract.Data.RAW_CONTACT_ID, + storedContact.rawId + ).build() ops += createCustomDate } } for (storedDate in storedContact.customDate) { if (serverContact.customDate.none { it.dateIso == storedDate.dateIso }) { - val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Event.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedDate.dateIso)).build() + val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Event.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedDate.dateIso) + ).build() ops += deleteOp } } } - private fun checkContactWebsites(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactWebsites( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { for (serverWebsite in serverContact.websites) { val storedWebsite = storedContact.websites.find { it.url == serverWebsite.url } if (storedWebsite != null) { if (storedWebsite.type != serverWebsite.type.toAndroidType()) { - val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Website.DATA} = ?", arrayOf(storedContact.rawId.toString(), serverWebsite.url)).withValue(ContactsContract.CommonDataKinds.Website.TYPE, serverWebsite.type.toAndroidType()).build() + val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Website.DATA} = ?", + arrayOf(storedContact.rawId.toString(), serverWebsite.url) + ).withValue(ContactsContract.CommonDataKinds.Website.TYPE, serverWebsite.type.toAndroidType()) + .build() ops += updateTypeOp } if (storedWebsite.customTypeName != serverWebsite.customTypeName) { - val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Website.DATA} = ?", arrayOf(storedContact.rawId.toString(), serverWebsite.url)).withValue(ContactsContract.CommonDataKinds.Website.LABEL, serverWebsite.customTypeName).build() + val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Website.DATA} = ?", + arrayOf(storedContact.rawId.toString(), serverWebsite.url) + ).withValue(ContactsContract.CommonDataKinds.Website.LABEL, serverWebsite.customTypeName).build() ops += updateCustomTypeNameOp } } else { // it's a new custom website - val createWebsite = insertWebsite(serverWebsite).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).build() + val createWebsite = + insertWebsite(serverWebsite).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId) + .build() ops += createWebsite } } for (storedWebsite in storedContact.websites) { if (serverContact.websites.none { it.url == storedWebsite.url }) { - val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Website.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedWebsite.url)).build() + val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Website.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedWebsite.url) + ).build() ops += deleteOp } } } - private fun checkContactRelationships(storedContact: AndroidContact, serverContact: StructuredContact, ops: ArrayList) { + private fun checkContactRelationships( + storedContact: AndroidContact, + serverContact: StructuredContact, + ops: ArrayList + ) { for (serverRelation in serverContact.relationships) { val storedRelation = storedContact.relationships.find { it.person == serverRelation.person } if (storedRelation != null) { if (storedRelation.type != serverRelation.type.toAndroidType()) { - val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Relation.DATA} = ?", arrayOf(storedContact.rawId.toString(), serverRelation.person)).withValue(ContactsContract.CommonDataKinds.Relation.TYPE, serverRelation.type.toAndroidType()).build() + val updateTypeOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Relation.DATA} = ?", + arrayOf(storedContact.rawId.toString(), serverRelation.person) + ).withValue(ContactsContract.CommonDataKinds.Relation.TYPE, serverRelation.type.toAndroidType()) + .build() ops += updateTypeOp } if (storedRelation.customTypeName != serverRelation.customTypeName) { - val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Relation.DATA} = ?", arrayOf(storedContact.rawId.toString(), serverRelation.person)).withValue(ContactsContract.CommonDataKinds.Relation.LABEL, serverRelation.customTypeName).build() + val updateCustomTypeNameOp = ContentProviderOperation.newUpdate(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Relation.DATA} = ?", + arrayOf(storedContact.rawId.toString(), serverRelation.person) + ).withValue(ContactsContract.CommonDataKinds.Relation.LABEL, serverRelation.customTypeName).build() ops += updateCustomTypeNameOp } } else { // it's a new custom website - val createRelation = insertRelation(serverRelation).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId).build() + val createRelation = + insertRelation(serverRelation).withValue(ContactsContract.Data.RAW_CONTACT_ID, storedContact.rawId) + .build() ops += createRelation } } for (storedRelation in storedContact.relationships) { if (serverContact.relationships.none { it.person == storedRelation.person }) { - val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection("${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Relation.DATA} = ?", arrayOf(storedContact.rawId.toString(), storedRelation.person)).build() + val deleteOp = ContentProviderOperation.newDelete(CONTACT_DATA_URI).withSelection( + "${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}\" AND ${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.CommonDataKinds.Relation.DATA} = ?", + arrayOf(storedContact.rawId.toString(), storedRelation.person) + ).build() ops += deleteOp } } @@ -559,29 +813,98 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo private fun createContact(userId: String, contact: StructuredContact) { val ops = ArrayList() val index = 0 - ops.add(ContentProviderOperation.newInsert(RAW_CONTACT_URI).withValue(RawContacts.ACCOUNT_TYPE, TUTA_ACCOUNT_TYPE).withValue(RawContacts.ACCOUNT_NAME, userId).withValue(RawContacts.SOURCE_ID, contact.id).build()) + ops.add( + ContentProviderOperation.newInsert(RAW_CONTACT_URI).withValue(RawContacts.ACCOUNT_TYPE, TUTA_ACCOUNT_TYPE) + .withValue(RawContacts.ACCOUNT_NAME, userId).withValue(RawContacts.SOURCE_ID, contact.id).build() + ) - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.lastName).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, contact.phoneticFirst).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, contact.phoneticMiddle).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, contact.phoneticLast).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, contact.title).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, contact.nameSuffix).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.lastName) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, contact.phoneticFirst) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, contact.phoneticMiddle) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, contact.phoneticLast) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX, contact.title) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, contact.nameSuffix).build() + ) - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Event.START_DATE, contact.birthday).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Event.START_DATE, contact.birthday) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue( + ContactsContract.CommonDataKinds.Event.TYPE, + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY + ).build() + ) - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, contact.company).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, contact.department).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.TITLE, contact.role).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Organization.TYPE, ContactsContract.CommonDataKinds.Organization.TYPE_WORK).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, contact.company) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Organization.DEPARTMENT, contact.department) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Organization.TITLE, contact.role) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + .withValue( + ContactsContract.CommonDataKinds.Organization.TYPE, + ContactsContract.CommonDataKinds.Organization.TYPE_WORK + ).build() + ) - ops.add(ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Nickname.NAME, contact.nickname).build()) + ops.add( + ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValueBackReference(RawContacts.Data.RAW_CONTACT_ID, index) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Nickname.NAME, contact.nickname).build() + ) for (mailAddress in contact.mailAddresses) { - ops.add(insertMailAddressOperation(mailAddress).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index).build()) + ops.add( + insertMailAddressOperation(mailAddress).withValueBackReference( + ContactsContract.Data.RAW_CONTACT_ID, + index + ).build() + ) } for (phoneNumber in contact.phoneNumbers) { - ops.add(insertPhoneNumberOperations(phoneNumber).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index).build()) + ops.add( + insertPhoneNumberOperations(phoneNumber).withValueBackReference( + ContactsContract.Data.RAW_CONTACT_ID, + index + ).build() + ) } for (address in contact.addresses) { - ops.add(insertAddressOperation(address).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index).build()) + ops.add( + insertAddressOperation(address).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index) + .build() + ) } for (customDate in contact.customDate) { - ops.add(insertCustomDateOperation(customDate).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index).build()) + ops.add( + insertCustomDateOperation(customDate).withValueBackReference( + ContactsContract.Data.RAW_CONTACT_ID, + index + ).build() + ) } for (website in contact.websites) { @@ -589,7 +912,9 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo } for (relationship in contact.relationships) { - ops.add(insertRelation(relationship).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index).build()) + ops.add( + insertRelation(relationship).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index).build() + ) } val result = resolver.applyBatch(ContactsContract.AUTHORITY, ops) @@ -599,8 +924,12 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo private fun readContact(rawContactId: Long, sourceId: String?): AndroidContact { val storedContact = AndroidContact(rawContactId, sourceId) - val entityUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.Entity.CONTENT_DIRECTORY) - resolver.query(entityUri, arrayOf( + val entityUri = Uri.withAppendedPath( + ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), + RawContacts.Entity.CONTENT_DIRECTORY + ) + resolver.query( + entityUri, arrayOf( RawContacts.SOURCE_ID, RawContacts.DELETED, RawContacts.Entity.DATA_ID, @@ -615,7 +944,8 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo RawContacts.Entity.DATA7, RawContacts.Entity.DATA8, RawContacts.Entity.DATA9, - ), null, null, null).use { entityCursor -> + ), null, null, null + ).use { entityCursor -> entityCursor!!.forEachRow { if (entityCursor.getInt(1) == 1) { storedContact.isDeleted = true @@ -649,9 +979,18 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo storedContact.phoneticLast = entityCursor.getNullableString(13) } - ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> storedContact.emailAddresses.add(AndroidEmailAddress(data1, entityCursor.getInt(5), entityCursor.getNullableString(6))) - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> storedContact.phoneNumbers.add(AndroidPhoneNumber(data1, entityCursor.getInt(5), entityCursor.getNullableString(6))) - ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE -> storedContact.addresses.add(AndroidAddress(data1, entityCursor.getInt(5), entityCursor.getNullableString(6))) + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> storedContact.emailAddresses.add( + AndroidEmailAddress(data1, entityCursor.getInt(5), entityCursor.getNullableString(6)) + ) + + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> storedContact.phoneNumbers.add( + AndroidPhoneNumber(data1, entityCursor.getInt(5), entityCursor.getNullableString(6)) + ) + + ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE -> storedContact.addresses.add( + AndroidAddress(data1, entityCursor.getInt(5), entityCursor.getNullableString(6)) + ) + ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE -> storedContact.nickname = data1 ?: "" ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> { storedContact.company = data1 @@ -668,91 +1007,143 @@ class AndroidMobileContactsFacade(private val activity: MainActivity) : MobileCo } } - ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE -> storedContact.relationships.add(AndroidRelationship(data1, entityCursor.getInt(5), entityCursor.getNullableString(6))) - ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE -> storedContact.websites.add(AndroidWebsite(data1, entityCursor.getInt(5), entityCursor.getNullableString(6))) + ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE -> storedContact.relationships.add( + AndroidRelationship(data1, entityCursor.getInt(5), entityCursor.getNullableString(6)) + ) + + ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE -> storedContact.websites.add( + AndroidWebsite( + data1, + entityCursor.getInt(5), + entityCursor.getNullableString(6) + ) + ) + ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE -> storedContact.notes = data1 ?: "" } } private fun insertAddressOperation(address: StructuredAddress): ContentProviderOperation.Builder { - val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredPostal.DATA, address.address).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE).withValue( + val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.StructuredPostal.DATA, address.address) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + .withValue( ContactsContract.CommonDataKinds.StructuredPostal.TYPE, address.type.toAndroidType(), - ) + ) if (address.type == ContactAddressType.CUSTOM) { - contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredPostal.LABEL, address.customTypeName) + contactInsert.withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + ).withValue(ContactsContract.CommonDataKinds.StructuredPostal.LABEL, address.customTypeName) } return contactInsert } private fun insertMailAddressOperation(mailAddress: StructuredMailAddress): ContentProviderOperation.Builder { - val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Email.DATA, mailAddress.address).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).withValue( + val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.DATA, mailAddress.address) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).withValue( ContactsContract.CommonDataKinds.Email.TYPE, mailAddress.type.toAndroidType(), - ) + ) if (mailAddress.type == ContactAddressType.CUSTOM) { - contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Email.LABEL, mailAddress.customTypeName) + contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Email.LABEL, mailAddress.customTypeName) } return contactInsert } private fun insertPhoneNumberOperations(phoneNumber: StructuredPhoneNumber): ContentProviderOperation.Builder { - val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.MIMETYPE).withValue(ContactsContract.CommonDataKinds.Phone.DATA, phoneNumber.number).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue( + val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.MIMETYPE) + .withValue(ContactsContract.CommonDataKinds.Phone.DATA, phoneNumber.number) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue( ContactsContract.CommonDataKinds.Phone.TYPE, phoneNumber.type.toAndroidType(), - ) + ) if (phoneNumber.type == ContactPhoneNumberType.CUSTOM) { - contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.customTypeName) + contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Phone.LABEL, phoneNumber.customTypeName) } return contactInsert } private fun insertCustomDateOperation(customDate: StructuredCustomDate): ContentProviderOperation.Builder { - val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.MIMETYPE).withValue(ContactsContract.CommonDataKinds.Event.DATA, customDate.dateIso).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue( + val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.MIMETYPE) + .withValue(ContactsContract.CommonDataKinds.Event.DATA, customDate.dateIso) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue( ContactsContract.CommonDataKinds.Event.TYPE, customDate.type.toAndroidType(), - ) + ) if (customDate.type == ContactCustomDateType.CUSTOM) { - contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Event.LABEL, customDate.customTypeName) + contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE) + .withValue(ContactsContract.CommonDataKinds.Event.LABEL, customDate.customTypeName) } return contactInsert } private fun insertWebsite(customWebsite: StructuredWebsite): ContentProviderOperation.Builder { - val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.MIMETYPE).withValue(ContactsContract.CommonDataKinds.Website.DATA, customWebsite.url).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE).withValue( + val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.MIMETYPE) + .withValue(ContactsContract.CommonDataKinds.Website.DATA, customWebsite.url) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE).withValue( ContactsContract.CommonDataKinds.Website.TYPE, customWebsite.type.toAndroidType(), - ) + ) if (customWebsite.type == ContactWebsiteType.CUSTOM) { - contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Website.LABEL, customWebsite.customTypeName) + contactInsert.withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE + ).withValue(ContactsContract.CommonDataKinds.Website.LABEL, customWebsite.customTypeName) } return contactInsert } private fun insertRelation(relation: StructuredRelationship): ContentProviderOperation.Builder { - val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Relation.MIMETYPE).withValue(ContactsContract.CommonDataKinds.Relation.DATA, relation.person).withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE).withValue( + val contactInsert = ContentProviderOperation.newInsert(CONTACT_DATA_URI) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Relation.MIMETYPE) + .withValue(ContactsContract.CommonDataKinds.Relation.DATA, relation.person) + .withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE) + .withValue( ContactsContract.CommonDataKinds.Relation.TYPE, relation.type.toAndroidType(), - ) + ) if (relation.type == ContactRelationshipType.CUSTOM) { - contactInsert.withValue(RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Relation.LABEL, relation.customTypeName) + contactInsert.withValue( + RawContacts.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE + ).withValue(ContactsContract.CommonDataKinds.Relation.LABEL, relation.customTypeName) } return contactInsert } companion object { - private val PROJECTION = arrayOf(ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, ContactsContract.CommonDataKinds.Email.ADDRESS) + private val PROJECTION = arrayOf( + ContactsContract.Contacts._ID, + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, + ContactsContract.CommonDataKinds.Email.ADDRESS + ) const val TAG = "Contact" private const val TUTA_ACCOUNT_TYPE = BuildConfig.APPLICATION_ID - private val RAW_CONTACT_URI = RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build() - private val CONTACT_DATA_URI = ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build() + private val RAW_CONTACT_URI = + RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") + .build() + private val CONTACT_DATA_URI = ContactsContract.Data.CONTENT_URI.buildUpon() + .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build() } - private data class SaveContactsResult(val cleanContacts: Map, val dirtyContacts: List) + private data class SaveContactsResult( + val cleanContacts: Map, + val dirtyContacts: List + ) private fun Cursor.getNullableString(index: Int): String { if (this.isNull(index)) { diff --git a/app-android/app/src/main/java/de/tutao/tutanota/StubSyncService.kt b/app-android/app/src/main/java/de/tutao/tutanota/StubSyncService.kt new file mode 100644 index 000000000000..79b39abf7627 --- /dev/null +++ b/app-android/app/src/main/java/de/tutao/tutanota/StubSyncService.kt @@ -0,0 +1,53 @@ +package de.tutao.tutanota + +import android.accounts.Account +import android.app.Service +import android.content.AbstractThreadedSyncAdapter +import android.content.ContentProviderClient +import android.content.Context +import android.content.Intent +import android.content.SyncResult +import android.os.Bundle +import android.os.IBinder +import android.util.Log + +private const val TAG = "Sync" + +/** A stubbed sync service that returns a stubbed sync adapter on `onBind()`. + * It is used to mark Tuta accounts as syncable as a fix for #6568 */ +class StubSyncService : Service() { + override fun onCreate() { + synchronized(syncAdapterLock) { + syncAdapter = syncAdapter ?: StubSyncAdapter(applicationContext, true) + } + } + + override fun onBind(intent: Intent): IBinder { + return syncAdapter?.syncAdapterBinder ?: throw IllegalStateException() + } + + companion object { + private var syncAdapter: StubSyncAdapter? = null + private val syncAdapterLock = Any() + } +} + +/** A stubbed sync adapter that does nothing when called. */ +private class StubSyncAdapter @JvmOverloads constructor( + context: Context, + autoInitialize: Boolean, + allowParallelSyncs: Boolean = false, +) : AbstractThreadedSyncAdapter(context, autoInitialize, allowParallelSyncs) { + + override fun onPerformSync( + account: Account, + extras: Bundle, + authority: String, + provider: ContentProviderClient, + syncResult: SyncResult + ) { + Log.w(TAG, "Sync requested to stub Sync Adapter!") + } +} + + diff --git a/app-android/app/src/main/res/xml/syncadapter.xml b/app-android/app/src/main/res/xml/syncadapter.xml new file mode 100644 index 000000000000..61c490f482cd --- /dev/null +++ b/app-android/app/src/main/res/xml/syncadapter.xml @@ -0,0 +1,11 @@ + + +