diff --git a/library/src/androidTest/java/com/alexstyl/contactstore/AddValuesToExistingContactContactStoreTest.kt b/library/src/androidTest/java/com/alexstyl/contactstore/AddValuesToExistingContactContactStoreTest.kt index 596123a5..3183acd7 100644 --- a/library/src/androidTest/java/com/alexstyl/contactstore/AddValuesToExistingContactContactStoreTest.kt +++ b/library/src/androidTest/java/com/alexstyl/contactstore/AddValuesToExistingContactContactStoreTest.kt @@ -9,6 +9,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses import com.alexstyl.contactstore.Label.RelationManager import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -235,4 +236,27 @@ class AddValuesToExistingContactContactStoreTest : ContactStoreTestBase() { assertContactUpdatedNoId(expected) } + + @Test + fun updatesSipAddresses(): Unit = runBlocking { + val contact = buildStoreContact(Names, SipAddresses) { + firstName = "Paolo" + lastName = "Melendez" + } + + val expected = contact.mutableCopy().apply { + sipAddresses.add( + LabeledValue( + SipAddress("123"), + Label.LocationHome + ) + ) + } + store.execute { + update(expected) + } + + assertContactUpdatedNoId(expected) + } + } diff --git a/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreInsertContactTest.kt b/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreInsertContactTest.kt index 96c2683b..6fa00dc6 100644 --- a/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreInsertContactTest.kt +++ b/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreInsertContactTest.kt @@ -9,6 +9,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses import com.alexstyl.contactstore.Label.DateBirthday import com.alexstyl.contactstore.Label.LocationHome @@ -307,4 +308,23 @@ class ContactStoreInsertContactTest : ContactStoreTestBase() { assertOnlyContact(actual = actual, expected = expected) } + + @Test + fun insertsContactWithSip(): Unit = runBlocking { + store.execute { + insert { + sipAddress(address = "123", label = LocationHome) + } + } + + val actual = store.fetchContacts(columnsToFetch = listOf(SipAddresses)).first() + val expected = contact( + sipAddresses = listOf( + LabeledValue(SipAddress(raw = "123"), LocationHome) + ), + columns = listOf(SipAddresses) + ) + + assertOnlyContact(actual = actual, expected = expected) + } } diff --git a/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreTestBase.kt b/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreTestBase.kt index b14ec2bd..a2324f11 100644 --- a/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreTestBase.kt +++ b/library/src/androidTest/java/com/alexstyl/contactstore/ContactStoreTestBase.kt @@ -70,6 +70,7 @@ abstract class ContactStoreTestBase { events: List> = emptyList(), webAddresses: List> = emptyList(), imAddresses: List> = emptyList(), + sipAddresses: List> = emptyList(), relations : List> = emptyList(), note: Note? = null, prefix: String? = null, @@ -89,6 +90,7 @@ abstract class ContactStoreTestBase { lastName = lastName, events = events, postalAddresses = postalAddresses, + sipAddresses = sipAddresses, note = note, columns = columns, webAddresses = webAddresses, diff --git a/library/src/androidTest/java/com/alexstyl/contactstore/EqualContentsContactMatcher.kt b/library/src/androidTest/java/com/alexstyl/contactstore/EqualContentsContactMatcher.kt index ce2f3760..76552ae4 100644 --- a/library/src/androidTest/java/com/alexstyl/contactstore/EqualContentsContactMatcher.kt +++ b/library/src/androidTest/java/com/alexstyl/contactstore/EqualContentsContactMatcher.kt @@ -11,6 +11,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses import org.hamcrest.Description import org.hamcrest.Matcher @@ -71,6 +72,10 @@ class EqualContentsContactMatcher( putCommaIfNeeded() append("webAddresses = ${labeledValues(webAddresses)}") } + if (containsColumn(SipAddresses)) { + putCommaIfNeeded() + append("sipAddresses = ${labeledValues(sipAddresses)}") + } if (containsColumn(Events)) { putCommaIfNeeded() append("events = ${labeledValues(events)}") @@ -126,6 +131,10 @@ class EqualContentsContactMatcher( mismatchDescription.appendText("imAddresses were ${labeledValues(actual.imAddresses)}") false } + sipAreDifferent(actual) -> { + mismatchDescription.appendText("sipAddresses were ${labeledValues(actual.sipAddresses)}") + false + } organizationIsDifferent(actual) -> { mismatchDescription.appendText("organization was ${actual.organization}, jobTitle was ${actual.jobTitle}") false @@ -177,6 +186,13 @@ class EqualContentsContactMatcher( return areLabeledValuesDifferentIgnoringId(actual.imAddresses, expected.imAddresses) } + private fun sipAreDifferent(actual: Contact): Boolean { + if (expected.containsColumn(SipAddresses).not()) { + return false + } + return areLabeledValuesDifferentIgnoringId(actual.sipAddresses, expected.sipAddresses) + } + private fun namesAreDifferent(actual: Contact): Boolean { if (expected.containsColumn(Names).not()) { return false diff --git a/library/src/main/java/com/alexstyl/contactstore/Contact.kt b/library/src/main/java/com/alexstyl/contactstore/Contact.kt index d70eee1e..ce911836 100644 --- a/library/src/main/java/com/alexstyl/contactstore/Contact.kt +++ b/library/src/main/java/com/alexstyl/contactstore/Contact.kt @@ -22,6 +22,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses interface Contact { @@ -107,6 +108,11 @@ interface Contact { */ val phones: List> + /** + * Requires: [ContactColumn.SipAddresses] + */ + val sipAddresses: List> + /** * Requires: [ContactColumn.Mails] */ @@ -194,6 +200,10 @@ fun Contact.mutableCopy(): MutableContact { webAddresses.toMutableList() else mutableListOf(), + sipAddresses = if (containsColumn(SipAddresses)) + sipAddresses.toMutableList() + else + mutableListOf(), imAddresses = if (containsColumn(ImAddresses)) imAddresses.toMutableList() else mutableListOf(), relations = if(containsColumn(Relations)) relations.toMutableList() else mutableListOf(), note = if (containsColumn(Note)) note else null, diff --git a/library/src/main/java/com/alexstyl/contactstore/ContactColumn.kt b/library/src/main/java/com/alexstyl/contactstore/ContactColumn.kt index ed1cd3fe..e6e6b8e9 100644 --- a/library/src/main/java/com/alexstyl/contactstore/ContactColumn.kt +++ b/library/src/main/java/com/alexstyl/contactstore/ContactColumn.kt @@ -72,6 +72,11 @@ sealed class ContactColumn { */ object ImAddresses : ContactColumn() + /** + * A column that will populate the [Contact.sipAddresses] field of all queried contacts when requested. + */ + object SipAddresses : ContactColumn() + /** * A column that will populate the [Contact.relations] field of all queried contacts when requested. */ @@ -93,5 +98,6 @@ fun standardColumns(): List { ContactColumn.GroupMemberships, ContactColumn.ImAddresses, ContactColumn.Relations, + ContactColumn.SipAddresses, ) } diff --git a/library/src/main/java/com/alexstyl/contactstore/ContactQueries.kt b/library/src/main/java/com/alexstyl/contactstore/ContactQueries.kt index 247f708d..7c837b41 100644 --- a/library/src/main/java/com/alexstyl/contactstore/ContactQueries.kt +++ b/library/src/main/java/com/alexstyl/contactstore/ContactQueries.kt @@ -10,6 +10,7 @@ import android.provider.ContactsContract.CommonDataKinds.Organization as Organiz import android.provider.ContactsContract.CommonDataKinds.Phone as PhoneColumns import android.provider.ContactsContract.CommonDataKinds.Photo as PhotoColumns import android.provider.ContactsContract.CommonDataKinds.Relation as RelationColumns +import android.provider.ContactsContract.CommonDataKinds.SipAddress as SipColumns import android.provider.ContactsContract.CommonDataKinds.StructuredName as NameColumns import android.provider.ContactsContract.CommonDataKinds.StructuredPostal as PostalColumns import android.provider.ContactsContract.CommonDataKinds.Website as WebColumns @@ -228,6 +229,7 @@ internal class ContactQueries( val phones = mutableSetOf>() val mails = mutableSetOf>() val webAddresses = mutableSetOf>() + val sipAddresses = mutableSetOf>() val events = mutableSetOf>() val imAddresses = mutableSetOf>() val relations = mutableSetOf>() @@ -325,6 +327,14 @@ internal class ContactQueries( events.add(entry) } } + SipColumns.CONTENT_ITEM_TYPE -> { + val address = row[SipColumns.SIP_ADDRESS] + val id = row[SipColumns._ID].toLongOrNull() + if (address.isNotBlank() && id != null) { + val value = LabeledValue(SipAddress(address), sipLabel(row), id) + sipAddresses.add(value) + } + } PostalColumns.CONTENT_ITEM_TYPE -> { val formattedAddress = row[PostalColumns.FORMATTED_ADDRESS] val id = row[PostalColumns._ID].toLongOrNull() @@ -422,6 +432,7 @@ internal class ContactQueries( nickname = nickname, phoneticMiddleName = phoneticMiddleName, phoneticNameStyle = phoneticNameStyle, + sipAddresses = sipAddresses.toList(), groups = groupIds.toList(), linkedAccountValues = linkedAccountValues.toList(), imAddresses = imAddresses.toList(), @@ -430,6 +441,16 @@ internal class ContactQueries( } } + private fun sipLabel(row: Cursor): Label { + return when (row[SipColumns.TYPE].toIntOrNull()) { + SipColumns.TYPE_HOME -> Label.LocationHome + SipColumns.TYPE_OTHER -> Label.Other + SipColumns.TYPE_WORK -> Label.LocationWork + SipColumns.TYPE_CUSTOM -> Label.Custom(row[SipColumns.LABEL]) + else -> Label.Other + } + } + private fun relationLabel(row: Cursor): Label { return when (row[RelationColumns.TYPE].toIntOrNull()) { RelationColumns.TYPE_ASSISTANT -> Label.PhoneNumberAssistant @@ -491,6 +512,7 @@ internal class ContactQueries( GroupMemberships -> GroupColumns.CONTENT_ITEM_TYPE ImAddresses -> ImColumns.CONTENT_ITEM_TYPE Relations -> RelationColumns.CONTENT_ITEM_TYPE + SipAddresses -> SipColumns.CONTENT_ITEM_TYPE is LinkedAccountValues -> error("Tried to map a LinkedAccountColumn as standard column") } diff --git a/library/src/main/java/com/alexstyl/contactstore/ContactStoreDSL.kt b/library/src/main/java/com/alexstyl/contactstore/ContactStoreDSL.kt index 9bb3d297..be04a492 100644 --- a/library/src/main/java/com/alexstyl/contactstore/ContactStoreDSL.kt +++ b/library/src/main/java/com/alexstyl/contactstore/ContactStoreDSL.kt @@ -33,6 +33,7 @@ fun SaveRequest.insert(builder: MutableContactBuilder.() -> Unit) { postalAddresses.addAll(values.postalAddresses) webAddresses.addAll(values.webAddresses) groups.addAll(values.groupMemberships) + sipAddresses.addAll(values.sipAddresses) relations.addAll(values.relations) imAddresses.addAll(values.imAddresses) }) diff --git a/library/src/main/java/com/alexstyl/contactstore/DataClasses.kt b/library/src/main/java/com/alexstyl/contactstore/DataClasses.kt index 7da32767..92de8aea 100644 --- a/library/src/main/java/com/alexstyl/contactstore/DataClasses.kt +++ b/library/src/main/java/com/alexstyl/contactstore/DataClasses.kt @@ -61,6 +61,8 @@ data class ImAddress( val protocol: String, ) +data class SipAddress(val raw: String) + internal fun GroupMembership.requireId(): Long { return requireNotNull(_id) } diff --git a/library/src/main/java/com/alexstyl/contactstore/ExistingContactOperationsFactory.kt b/library/src/main/java/com/alexstyl/contactstore/ExistingContactOperationsFactory.kt index c0053be3..99b42713 100644 --- a/library/src/main/java/com/alexstyl/contactstore/ExistingContactOperationsFactory.kt +++ b/library/src/main/java/com/alexstyl/contactstore/ExistingContactOperationsFactory.kt @@ -12,6 +12,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName as NameC import android.provider.ContactsContract.CommonDataKinds.StructuredPostal as PostalColumns import android.provider.ContactsContract.CommonDataKinds.Website as WebAddressColumns import android.provider.ContactsContract.CommonDataKinds.Relation as RelationColumns +import android.provider.ContactsContract.CommonDataKinds.SipAddress as SipColumns import android.content.ContentProviderOperation import android.content.ContentProviderOperation.newDelete import android.content.ContentProviderOperation.newInsert @@ -33,6 +34,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses import com.alexstyl.contactstore.utils.get import com.alexstyl.contactstore.utils.runQuery @@ -64,8 +66,9 @@ internal class ExistingContactOperationsFactory( updateGroupMembership(newContact = contact, oldContact = existingContact) + updatePostalAddresses(newContact = contact, oldContact = existingContact) + updateImAddresses(newContact = contact, oldContact = existingContact) + + updateSipAddresses(newContact = contact, oldContact = existingContact) + updateRelations(newContact = contact, oldContact = existingContact) + - replaceWebAddresses(newContact = contact, oldContact = existingContact) + updateWebAddresses(newContact = contact, oldContact = existingContact) } private fun updateGroupMembership( @@ -309,6 +312,24 @@ internal class ExistingContactOperationsFactory( .withImLabel(labeledValue.label) } } + + private fun updateSipAddresses( + newContact: MutableContact, + oldContact: Contact + ): List { + if (newContact.containsColumn(SipAddresses).not()) { + return emptyList() + } + return buildOperations( + forContactId = newContact.contactId, + oldValues = oldContact.sipAddresses, + newValues = newContact.sipAddresses, + mimeType = SipColumns.CONTENT_ITEM_TYPE, + ) { labeledValue -> + withValue(SipColumns.SIP_ADDRESS, labeledValue.value.raw) + .withSipLabel(labeledValue.label) + } + } private fun updateRelations( newContact: MutableContact, @@ -367,7 +388,7 @@ internal class ExistingContactOperationsFactory( } } - private fun replaceWebAddresses( + private fun updateWebAddresses( newContact: MutableContact, oldContact: Contact ): List { diff --git a/library/src/main/java/com/alexstyl/contactstore/MutableContact.kt b/library/src/main/java/com/alexstyl/contactstore/MutableContact.kt index a5042df5..91a8ae5e 100644 --- a/library/src/main/java/com/alexstyl/contactstore/MutableContact.kt +++ b/library/src/main/java/com/alexstyl/contactstore/MutableContact.kt @@ -12,6 +12,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses class MutableContact internal constructor( @@ -41,6 +42,7 @@ class MutableContact internal constructor( groups: MutableList, linkedAccountValues: List, imAddresses: MutableList>, + sipAddresses: MutableList>, relations: MutableList>, override val columns: List, ) : Contact { @@ -55,6 +57,8 @@ class MutableContact internal constructor( by requireColumn(WebAddresses, webAddresses) override val imAddresses: MutableList> by requireColumn(ImAddresses, imAddresses) + override val sipAddresses: MutableList> + by requireColumn(SipAddresses, sipAddresses) override val linkedAccountValues: List by requireAnyLinkedAccountColumn(linkedAccountValues) @@ -87,6 +91,7 @@ class MutableContact internal constructor( events = mutableListOf(), postalAddresses = mutableListOf(), webAddresses = mutableListOf(), + sipAddresses = mutableListOf(), relations = mutableListOf(), note = null, isStarred = false, diff --git a/library/src/main/java/com/alexstyl/contactstore/MutableContactBuilder.kt b/library/src/main/java/com/alexstyl/contactstore/MutableContactBuilder.kt index 1ea3a3cd..452ba545 100644 --- a/library/src/main/java/com/alexstyl/contactstore/MutableContactBuilder.kt +++ b/library/src/main/java/com/alexstyl/contactstore/MutableContactBuilder.kt @@ -47,6 +47,10 @@ data class MutableContactBuilder( val groupMemberships: List get() = _groups.toList() + private val _sipAddresses: MutableList> = mutableListOf() + val sipAddresses: List> + get() = _sipAddresses.toList() + private val _relations: MutableList> = mutableListOf() val relations: List> get() = _relations.toList() @@ -125,4 +129,8 @@ data class MutableContactBuilder( fun relation(name: String, label: Label) { _relations.add(LabeledValue(Relation(name = name), label)) } + + fun sipAddress(address: String, label: Label) { + _sipAddresses.add(LabeledValue(SipAddress(address), label)) + } } diff --git a/library/src/main/java/com/alexstyl/contactstore/NewContactOperationsFactory.kt b/library/src/main/java/com/alexstyl/contactstore/NewContactOperationsFactory.kt index df03c5c2..378a3236 100644 --- a/library/src/main/java/com/alexstyl/contactstore/NewContactOperationsFactory.kt +++ b/library/src/main/java/com/alexstyl/contactstore/NewContactOperationsFactory.kt @@ -11,6 +11,7 @@ import android.provider.ContactsContract.CommonDataKinds.Relation as RelationCol import android.provider.ContactsContract.CommonDataKinds.StructuredName as NameColumns import android.provider.ContactsContract.CommonDataKinds.StructuredPostal as PostalColumns import android.provider.ContactsContract.CommonDataKinds.Website as WebsiteColumns +import android.provider.ContactsContract.CommonDataKinds.SipAddress as SipColumns import android.content.ContentProviderOperation import android.content.ContentProviderOperation.newInsert import android.os.Build @@ -35,6 +36,7 @@ internal class NewContactOperationsFactory { contact.postalAddresses.forEach { add(insertPostalOperation(it)) } contact.note?.run { add(insertNoteOperation(this)) } contact.imAddresses.forEach { add(insertImOperation(it)) } + contact.sipAddresses.forEach { add(insertSipOperation(it)) } contact.relations.forEach { add(insertRelationOperation(it)) } if (hasOrganizationDetails(contact)) { @@ -79,6 +81,15 @@ internal class NewContactOperationsFactory { .build() } + private fun insertSipOperation(labeledValue: LabeledValue): ContentProviderOperation { + return newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, NEW_CONTACT_INDEX) + .withValue(Data.MIMETYPE, SipColumns.CONTENT_ITEM_TYPE) + .withValue(SipColumns.SIP_ADDRESS, labeledValue.value.raw) + .withSipLabel(labeledValue.label) + .build() + } + private fun insertNoteOperation(note: Note): ContentProviderOperation { return newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, NEW_CONTACT_INDEX) @@ -384,7 +395,6 @@ internal class NewContactOperationsFactory { else -> error("Unsupported Im Label $label") } } - private fun ContentProviderOperation.Builder.withEventLabel( label: Label ): ContentProviderOperation.Builder { @@ -491,3 +501,30 @@ fun ContentProviderOperation.Builder.withRelationLabel( else -> error("Unsupported Postal Label $label") } } + +fun ContentProviderOperation.Builder.withSipLabel( + label: Label +): ContentProviderOperation.Builder { + return when (label) { + Label.LocationHome -> withValue( + Contactables.TYPE, + SipColumns.TYPE_HOME + ) + Label.Other -> withValue( + Contactables.TYPE, + SipColumns.TYPE_OTHER + ) + Label.LocationWork -> withValue( + Contactables.TYPE, + SipColumns.TYPE_WORK + ) + is Label.Custom -> { + withValue( + Contactables.TYPE, + Contactables.TYPE_CUSTOM + ) + .withValue(Contactables.LABEL, label.label) + } + else -> error("Unsupported Im Label $label") + } +} diff --git a/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt b/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt index 776734d0..d2a10517 100644 --- a/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt +++ b/library/src/main/java/com/alexstyl/contactstore/PartialContact.kt @@ -13,6 +13,7 @@ import com.alexstyl.contactstore.ContactColumn.Organization import com.alexstyl.contactstore.ContactColumn.Phones import com.alexstyl.contactstore.ContactColumn.PostalAddresses import com.alexstyl.contactstore.ContactColumn.Relations +import com.alexstyl.contactstore.ContactColumn.SipAddresses import com.alexstyl.contactstore.ContactColumn.WebAddresses class PartialContact constructor( @@ -27,6 +28,7 @@ class PartialContact constructor( organization: String? = null, jobTitle: String? = null, webAddresses: List> = emptyList(), + sipAddresses: List> = emptyList(), phones: List> = emptyList(), mails: List> = emptyList(), events: List> = emptyList(), @@ -65,6 +67,7 @@ class PartialContact constructor( override val postalAddresses by requireColumn(PostalAddresses, postalAddresses) override val note by requireColumn(Note, note) override val webAddresses by requireColumn(WebAddresses, webAddresses) + override val sipAddresses by requireColumn(SipAddresses, sipAddresses) override val organization by requireColumn(Organization, organization) override val jobTitle by requireColumn(Organization, jobTitle) override val linkedAccountValues by requireAnyLinkedAccountColumn(linkedAccountValues)