Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bitfireAT/vcard4android.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRicki Hirner <hirner@bitfire.at>2020-10-15 18:35:08 +0300
committerRicki Hirner <hirner@bitfire.at>2020-10-15 18:37:37 +0300
commitce2f39525d572047a5d49709e48ead87adc186ca (patch)
tree002166b76dcfa3a1a6a2137329e2d39da2c267c9
parentb840ecbff99b629f3558ca49a858a2c9fcf2632d (diff)
Update to Android gradle plugin 4.1.0; increase target SDK level to 30; update BatchOperation to new API definition
-rw-r--r--build.gradle8
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--src/main/java/at/bitfire/vcard4android/AndroidContact.kt217
-rw-r--r--src/main/java/at/bitfire/vcard4android/BatchOperation.kt137
4 files changed, 175 insertions, 189 deletions
diff --git a/build.gradle b/build.gradle
index 78f914a..981d36f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.0.1'
+ classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
}
@@ -30,12 +30,12 @@ apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.dokka'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
buildToolsVersion '30.0.2'
defaultConfig {
minSdkVersion 16 // Android 4.1
- targetSdkVersion 29 // Android 10
+ targetSdkVersion 30 // Android 11
}
compileOptions {
@@ -81,7 +81,7 @@ dependencies {
// noinspection GradleDependency
implementation "org.apache.commons:commons-text:${versions.commonsText}"
- // ez-vcard to parse/generate VCards
+ // ez-vcard to parse/generate vCards
api('com.googlecode.ez-vcard:ez-vcard:0.11.0') {
// hCard functionality not needed
exclude group: 'org.jsoup'
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1e2b225..d042ef0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
diff --git a/src/main/java/at/bitfire/vcard4android/AndroidContact.kt b/src/main/java/at/bitfire/vcard4android/AndroidContact.kt
index 98bc509..8ab9a32 100644
--- a/src/main/java/at/bitfire/vcard4android/AndroidContact.kt
+++ b/src/main/java/at/bitfire/vcard4android/AndroidContact.kt
@@ -8,7 +8,6 @@
package at.bitfire.vcard4android
-import android.content.ContentProviderOperation
import android.content.ContentUris
import android.content.ContentValues
import android.content.EntityIterator
@@ -549,20 +548,20 @@ open class AndroidContact(
fun add(): Uri {
val batch = BatchOperation(addressBook.provider!!)
- val builder = ContentProviderOperation.newInsert(addressBook.syncAdapterURI(RawContacts.CONTENT_URI))
+ val builder = BatchOperation.CpoBuilder.newInsert(addressBook.syncAdapterURI(RawContacts.CONTENT_URI))
buildContact(builder, false)
- batch.enqueue(BatchOperation.Operation(builder))
+ batch.enqueue(builder)
insertDataRows(batch)
batch.commit()
- val result = batch.getResult(0) ?: throw ContactsStorageException("Empty result from content provider when adding contact")
- id = ContentUris.parseId(result.uri)
+ val resultUri = batch.getResult(0)?.uri ?: throw ContactsStorageException("Empty result from content provider when adding contact")
+ id = ContentUris.parseId(resultUri)
// we need a raw contact ID to insert the photo
insertPhoto(contact!!.photo)
- return result.uri
+ return resultUri
}
fun update(contact: Contact): Uri {
@@ -570,16 +569,16 @@ open class AndroidContact(
val batch = BatchOperation(addressBook.provider!!)
val uri = rawContactSyncURI()
- val builder = ContentProviderOperation.newUpdate(uri)
+ val builder = BatchOperation.CpoBuilder.newUpdate(uri)
buildContact(builder, true)
- batch.enqueue(BatchOperation.Operation(builder))
+ batch.enqueue(builder)
// Delete known data rows before adding the new ones.
// - We don't delete group memberships.
// - We'll only delete rows we have inserted so that unknown rows like
// vnd.android.cursor.item/important_people (= contact is in Samsung "edge panel") remain untouched.
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newDelete(dataSyncURI())
+ batch.enqueue(BatchOperation.CpoBuilder
+ .newDelete(dataSyncURI())
.withSelection(Data.RAW_CONTACT_ID + "=? AND " +
Data.MIMETYPE + " IN (?,?,?,?,?,?,?,?,?,?,?,?,?)",
arrayOf(id.toString(),
@@ -596,7 +595,7 @@ open class AndroidContact(
Event.CONTENT_ITEM_TYPE,
Relation.CONTENT_ITEM_TYPE,
SipAddress.CONTENT_ITEM_TYPE))
- ))
+ )
insertDataRows(batch)
batch.commit()
@@ -609,7 +608,7 @@ open class AndroidContact(
@CallSuper
- protected open fun buildContact(builder: ContentProviderOperation.Builder, update: Boolean) {
+ protected open fun buildContact(builder: BatchOperation.CpoBuilder, update: Boolean) {
if (!update)
builder .withValue(RawContacts.ACCOUNT_NAME, addressBook.account.name)
.withValue(RawContacts.ACCOUNT_TYPE, addressBook.account.type)
@@ -664,15 +663,8 @@ open class AndroidContact(
contact.phoneticGivenName == null && contact.phoneticMiddleName == null && contact.phoneticFamilyName == null)
return
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, StructuredName.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(StructuredName.RAW_CONTACT_ID, id)
- }
- builder .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(StructuredName.RAW_CONTACT_ID)
+ .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
.withValue(StructuredName.DISPLAY_NAME, contact.displayName)
.withValue(StructuredName.PREFIX, contact.prefix)
.withValue(StructuredName.GIVEN_NAME, contact.givenName)
@@ -683,11 +675,8 @@ open class AndroidContact(
.withValue(StructuredName.PHONETIC_MIDDLE_NAME, contact.phoneticMiddleName)
.withValue(StructuredName.PHONETIC_FAMILY_NAME, contact.phoneticFamilyName)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built StructuredName data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertPhoneNumber(batch: BatchOperation, labeledNumber: LabeledProperty<Telephone>) {
@@ -765,26 +754,16 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Phone.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Phone.RAW_CONTACT_ID, id)
- }
- builder .withValue(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Phone.RAW_CONTACT_ID)
+ .withValue(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, number.text)
.withValue(Phone.TYPE, typeCode)
.withValue(Phone.LABEL, typeLabel)
.withValue(Phone.IS_PRIMARY, if (isPrimary) 1 else 0)
.withValue(Phone.IS_SUPER_PRIMARY, if (isPrimary) 1 else 0)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Phone data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertEmail(batch: BatchOperation, labeledEmail: LabeledProperty<ezvcard.property.Email>) {
@@ -830,26 +809,16 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Email.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Email.RAW_CONTACT_ID, id)
- }
- builder .withValue(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Email.RAW_CONTACT_ID)
+ .withValue(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, email.value)
.withValue(Email.TYPE, typeCode)
.withValue(Email.LABEL, typeLabel)
.withValue(Email.IS_PRIMARY, if (isPrimary) 1 else 0)
.withValue(Phone.IS_SUPER_PRIMARY, if (isPrimary) 1 else 0)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Email data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertOrganization(batch: BatchOperation) {
@@ -868,25 +837,15 @@ open class AndroidContact(
department = org.next()
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Organization.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Organization.RAW_CONTACT_ID, id)
- }
- builder .withValue(Organization.MIMETYPE, Organization.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Organization.RAW_CONTACT_ID)
+ .withValue(Organization.MIMETYPE, Organization.CONTENT_ITEM_TYPE)
.withValue(Organization.COMPANY, company)
.withValue(Organization.DEPARTMENT, department)
.withValue(Organization.TITLE, contact.jobTitle)
.withValue(Organization.JOB_DESCRIPTION, contact.jobDescription)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Organization data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertIMPP(batch: BatchOperation, labeledImpp: LabeledProperty<Impp>) {
@@ -940,28 +899,19 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Im.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Im.RAW_CONTACT_ID, id)
- }
-
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
+ val builder: BatchOperation.CpoBuilder
if (sipAddress) {
// save as SIP address
- builder .withValue(SipAddress.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE)
+ builder = insertDataBuilder(SipAddress.RAW_CONTACT_ID)
+ .withValue(SipAddress.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE)
.withValue(SipAddress.DATA, impp.handle)
.withValue(SipAddress.TYPE, typeCode)
.withValue(SipAddress.LABEL, typeLabel)
Constants.log.log(Level.FINER, "Built SipAddress data row", builder.build())
} else {
// save as IM address
- builder .withValue(Im.MIMETYPE, Im.CONTENT_ITEM_TYPE)
+ builder = insertDataBuilder(Im.RAW_CONTACT_ID)
+ .withValue(Im.MIMETYPE, Im.CONTENT_ITEM_TYPE)
.withValue(Im.DATA, impp.handle)
.withValue(Im.TYPE, typeCode)
.withValue(Im.LABEL, typeLabel)
@@ -969,7 +919,7 @@ open class AndroidContact(
.withValue(Im.CUSTOM_PROTOCOL, protocolLabel)
Constants.log.log(Level.FINER, "Built Im data row", builder.build())
}
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertNickname(batch: BatchOperation) {
@@ -993,24 +943,14 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Nickname.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Nickname.RAW_CONTACT_ID, id)
- }
- builder .withValue(Nickname.MIMETYPE, Nickname.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Nickname.RAW_CONTACT_ID)
+ .withValue(Nickname.MIMETYPE, Nickname.CONTENT_ITEM_TYPE)
.withValue(Nickname.NAME, nick.values.first())
.withValue(Nickname.TYPE, typeCode)
.withValue(Nickname.LABEL, typeLabel)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Nickname data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertNote(batch: BatchOperation) {
@@ -1018,22 +958,12 @@ open class AndroidContact(
if (contact.note.isNullOrEmpty())
return
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Note.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Note.RAW_CONTACT_ID, id)
- }
- builder .withValue(Note.MIMETYPE, Note.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Note.RAW_CONTACT_ID)
+ .withValue(Note.MIMETYPE, Note.CONTENT_ITEM_TYPE)
.withValue(Note.NOTE, contact.note)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Note data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertStructuredPostal(batch: BatchOperation, labeledAddress: LabeledProperty<Address>) {
@@ -1083,15 +1013,8 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, StructuredPostal.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(StructuredPostal.RAW_CONTACT_ID, id)
- }
- builder .withValue(StructuredPostal.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(StructuredPostal.RAW_CONTACT_ID)
+ .withValue(StructuredPostal.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)
.withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress)
.withValue(StructuredPostal.TYPE, typeCode)
.withValue(StructuredPostal.LABEL, typeLabel)
@@ -1103,11 +1026,8 @@ open class AndroidContact(
.withValue(StructuredPostal.POSTCODE, address.postalCode)
.withValue(StructuredPostal.COUNTRY, address.country)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built StructuredPostal data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertWebsite(batch: BatchOperation, labeledUrl: LabeledProperty<Url>) {
@@ -1135,24 +1055,14 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Website.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Website.RAW_CONTACT_ID, id)
- }
- builder .withValue(Website.MIMETYPE, Website.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Website.RAW_CONTACT_ID)
+ .withValue(Website.MIMETYPE, Website.CONTENT_ITEM_TYPE)
.withValue(Website.URL, url.value)
.withValue(Website.TYPE, typeCode)
.withValue(Website.LABEL, typeLabel)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Website data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertEvent(batch: BatchOperation, type: Int, dateOrTime: DateOrTimeProperty) {
@@ -1170,23 +1080,13 @@ open class AndroidContact(
}
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Event.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Event.RAW_CONTACT_ID, id)
- }
- builder .withValue(Event.MIMETYPE, Event.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Event.RAW_CONTACT_ID)
+ .withValue(Event.MIMETYPE, Event.CONTENT_ITEM_TYPE)
.withValue(Event.TYPE, type)
.withValue(Event.START_DATE, dateStr)
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
- batch.enqueue(op)
Constants.log.log(Level.FINER, "Built Event data row", builder.build())
+ batch.enqueue(builder)
}
protected open fun insertRelation(batch: BatchOperation, related: Related) {
@@ -1206,24 +1106,14 @@ open class AndroidContact(
else -> labels += type.value
}
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
- if (id == null)
- op = BatchOperation.Operation(builder, Relation.RAW_CONTACT_ID, 0)
- else {
- op = BatchOperation.Operation(builder)
- builder.withValue(Relation.RAW_CONTACT_ID, id)
- }
- builder .withValue(Relation.MIMETYPE, Relation.CONTENT_ITEM_TYPE)
+ val builder = insertDataBuilder(Relation.RAW_CONTACT_ID)
+ .withValue(Relation.MIMETYPE, Relation.CONTENT_ITEM_TYPE)
.withValue(Relation.NAME, related.text)
.withValue(Relation.TYPE, typeCode)
.withValue(Relation.LABEL, StringUtils.trimToNull(labels.joinToString(", ")))
- if (addressBook.readOnly)
- builder.withValue(Data.IS_READ_ONLY, 1)
-
Constants.log.log(Level.FINER, "Built Relation data row", builder.build())
- batch.enqueue(op)
+ batch.enqueue(builder)
}
protected open fun insertPhoto(orig: ByteArray?) {
@@ -1307,6 +1197,19 @@ open class AndroidContact(
// helpers
+ protected fun insertDataBuilder(rawContactKeyName: String): BatchOperation.CpoBuilder {
+ val builder = BatchOperation.CpoBuilder.newInsert(dataSyncURI())
+ if (id == null)
+ builder.withValueBackReference(rawContactKeyName, 0)
+ else
+ builder.withValue(rawContactKeyName, id)
+
+ if (addressBook.readOnly)
+ builder.withValue(Data.IS_READ_ONLY, 1)
+
+ return builder
+ }
+
protected open fun queryPhotoMaxDimensions(): Int {
try {
addressBook.provider?.query(ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
diff --git a/src/main/java/at/bitfire/vcard4android/BatchOperation.kt b/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
index da09a35..8228230 100644
--- a/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
+++ b/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
@@ -9,6 +9,7 @@
package at.bitfire.vcard4android
import android.content.*
+import android.net.Uri
import android.os.RemoteException
import android.os.TransactionTooLargeException
import java.util.*
@@ -17,13 +18,24 @@ class BatchOperation(
private val providerClient: ContentProviderClient
) {
- private val queue = LinkedList<Operation>()
+ companion object {
+
+ /**
+ * See https://android.googlesource.com/platform/packages/providers/ContactsProvider.git/+/refs/heads/android11-release/src/com/android/providers/contacts/AbstractContactsProvider.java#70
+ *
+ * Some operations may count more than one operation, so use a safe value of 450 instead of 500.
+ */
+ const val MAX_OPERATIONS_PER_YIELD_POINT = 450
+
+ }
+
+ private val queue = LinkedList<CpoBuilder>()
private var results = arrayOfNulls<ContentProviderResult?>(0)
fun nextBackrefIdx() = queue.size
- fun enqueue(operation: Operation) = queue.add(operation)
+ fun enqueue(operation: CpoBuilder) = queue.add(operation)
fun commit(): Int {
var affected = 0
@@ -36,7 +48,7 @@ class BatchOperation(
for (result in results.filterNotNull())
when {
- result.count != null -> affected += result.count
+ result.count != null -> affected += result.count ?: 0
result.uri != null -> affected += 1
}
Constants.log.fine("… $affected record(s) affected")
@@ -57,9 +69,9 @@ class BatchOperation(
* Catches [TransactionTooLargeException] and splits the operations accordingly.
* @param start index of first operation which will be run (inclusive)
* @param end index of last operation which will be run (exclusive!)
- * @throws RemoteException on contact provider errors
+ * @throws RemoteException on calendar provider errors
* @throws OperationApplicationException when the batch can't be processed
- * @throws ContactsStorageException if the transaction is too large
+ * @throws CalendarStorageException if the transaction is too large
*/
private fun runBatch(start: Int, end: Int) {
if (end == start)
@@ -90,34 +102,105 @@ class BatchOperation(
private fun toCPO(start: Int, end: Int): ArrayList<ContentProviderOperation> {
val cpo = ArrayList<ContentProviderOperation>(end - start)
- for ((i, op) in queue.subList(start, end).withIndex()) {
- val builder = op.builder
- op.backrefKey?.let { key ->
- if (op.backrefIdx < start)
- // back reference is outside of the current batch
- results[op.backrefIdx]?.let { result ->
- builder .withValueBackReferences(null)
- .withValue(key, ContentUris.parseId(result.uri))
- }
- else
- // back reference is in current batch, apply offset
- builder.withValueBackReference(key, op.backrefIdx - start)
+ for ((i, cpoBuilder) in queue.subList(start, end).withIndex()) {
+ for ((backrefKey, backrefIdx) in cpoBuilder.valueBackrefs) {
+ if (backrefIdx < start) {
+ // back reference is outside of the current batch, get result from previous execution ...
+ val resultUri = results[backrefIdx]?.uri ?: throw ContactsStorageException("Referenced operation didn't produce a valid result")
+ val resultId = ContentUris.parseId(resultUri)
+ // ... and use result directly instead of using a back reference
+ cpoBuilder .removeValueBackReference(backrefKey)
+ .withValue(backrefKey, resultId)
+ } else
+ // back reference is in current batch, shift index
+ cpoBuilder.withValueBackReference(backrefKey, backrefIdx - start)
}
- // set a yield point at least every 300 operations
- if (i % 300 == 0)
- builder.withYieldAllowed(true)
+ if (i % MAX_OPERATIONS_PER_YIELD_POINT == MAX_OPERATIONS_PER_YIELD_POINT - 1)
+ cpoBuilder.yieldAllowed = true
- cpo += builder.build()
+ cpo += cpoBuilder.build()
}
return cpo
}
- class Operation constructor(
- val builder: ContentProviderOperation.Builder,
- val backrefKey: String? = null,
- val backrefIdx: Int = -1
- )
+ /**
+ * Wrapper for [ContentProviderOperation.Builder] that allows to reset previously-set
+ * value back references.
+ */
+ class CpoBuilder private constructor(
+ val uri: Uri,
+ val type: Type
+ ) {
+
+ enum class Type { INSERT, UPDATE, DELETE }
+
+ companion object {
+
+ fun newInsert(uri: Uri) = CpoBuilder(uri, Type.INSERT)
+ fun newUpdate(uri: Uri) = CpoBuilder(uri, Type.UPDATE)
+ fun newDelete(uri: Uri) = CpoBuilder(uri, Type.DELETE)
+
+ }
+
+
+ var selection: String? = null
+ var selectionArguments: Array<String>? = null
+
+ val values = mutableMapOf<String, Any>()
+ val valueBackrefs = mutableMapOf<String, Int>()
+
+ var yieldAllowed = false
+
+
+ fun withSelection(select: String, args: Array<String>): CpoBuilder {
+ selection = select
+ selectionArguments = args
+ return this
+ }
+
+ fun withValueBackReference(key: String, index: Int): CpoBuilder {
+ valueBackrefs[key] = index
+ return this
+ }
+
+ fun removeValueBackReference(key: String): CpoBuilder {
+ if (valueBackrefs.remove(key) == null)
+ throw IllegalArgumentException("$key was not set as value back reference")
+ return this
+ }
+
+ fun withValue(key: String, value: Any?): CpoBuilder {
+ if (value != null)
+ values[key] = value
+ else
+ values -= key
+ return this
+ }
+
+
+ fun build(): ContentProviderOperation {
+ val builder = when (type) {
+ Type.INSERT -> ContentProviderOperation.newInsert(uri)
+ Type.UPDATE -> ContentProviderOperation.newUpdate(uri)
+ Type.DELETE -> ContentProviderOperation.newDelete(uri)
+ }
+
+ if (selection != null)
+ builder.withSelection(selection, selectionArguments)
+
+ for ((key, value) in values)
+ builder.withValue(key, value)
+ for ((key, index) in valueBackrefs)
+ builder.withValueBackReference(key, index)
+
+ if (yieldAllowed)
+ builder.withYieldAllowed(true)
+
+ return builder.build()
+ }
+
+ }
-}
+} \ No newline at end of file