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>2021-07-31 22:33:19 +0300
committerRicki Hirner <hirner@bitfire.at>2021-07-31 22:43:38 +0300
commit934656921f63a722d9e0a1c8f23b7d4789043917 (patch)
treed4b5bddcdababad75a209611ce8b8da608945e28
parent07f62f52cc374c584e9263a25c72cf10a54b6dc7 (diff)
Support all Android and Apple Relations/Related types
-rw-r--r--src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt57
-rw-r--r--src/main/java/at/bitfire/vcard4android/AndroidContact.kt100
-rw-r--r--src/main/java/at/bitfire/vcard4android/BatchOperation.kt2
-rw-r--r--src/main/java/at/bitfire/vcard4android/Contact.kt11
-rw-r--r--src/main/java/at/bitfire/vcard4android/ContactReader.kt60
-rw-r--r--src/main/java/at/bitfire/vcard4android/ContactWriter.kt58
-rw-r--r--src/main/java/at/bitfire/vcard4android/property/CustomRelatedType.kt19
-rw-r--r--src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt18
-rw-r--r--src/main/java/at/bitfire/vcard4android/property/XAbLabel.kt7
-rw-r--r--src/main/java/at/bitfire/vcard4android/property/XAbRelatedNames.kt31
-rw-r--r--src/test/java/at/bitfire/vcard4android/ContactReaderTest.kt50
-rw-r--r--src/test/java/at/bitfire/vcard4android/ContactTest.kt2
-rw-r--r--src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt95
13 files changed, 452 insertions, 58 deletions
diff --git a/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt b/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt
index 5afd43d..342f94e 100644
--- a/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt
+++ b/src/androidTest/java/at/bitfire/vcard4android/AndroidContactTest.kt
@@ -12,18 +12,22 @@ import android.Manifest
import android.accounts.Account
import android.content.ContentProviderClient
import android.provider.ContactsContract
+import android.provider.ContactsContract.CommonDataKinds.Relation
import android.util.Base64
import androidx.test.filters.MediumTest
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import at.bitfire.vcard4android.impl.TestAddressBook
+import at.bitfire.vcard4android.property.CustomRelatedType
import at.bitfire.vcard4android.property.XAbDate
import ezvcard.VCardVersion
import ezvcard.parameter.EmailType
+import ezvcard.parameter.RelatedType
import ezvcard.property.Address
import ezvcard.property.Birthday
import ezvcard.property.Email
+import ezvcard.property.Related
import ezvcard.util.PartialDate
import org.junit.After
import org.junit.Assert.*
@@ -322,4 +326,57 @@ class AndroidContactTest {
assertEquals("CyanogenModForum", AndroidContact.toURIScheme("CyanogenMod_Forum"))
}
+
+ // specific data rows
+
+ @Test
+ fun testInsertRelation_Assistant() {
+ val batch = BatchOperation(provider)
+ AndroidContact(addressBook).insertRelation(batch, Related().apply {
+ text = "My Assistant"
+ types.add(RelatedType.CO_WORKER)
+ types.add(CustomRelatedType.ASSISTANT)
+ })
+ assertEquals(1, batch.queue.size)
+ assertEquals("My Assistant", batch.queue[0].values[Relation.NAME])
+ assertEquals(Relation.TYPE_ASSISTANT, batch.queue[0].values[Relation.TYPE])
+ }
+
+ @Test
+ fun testInsertRelation_Custom() {
+ val batch = BatchOperation(provider)
+ AndroidContact(addressBook).insertRelation(batch, Related().apply {
+ text = "Someone"
+ types.add(RelatedType.get("Some Relationship"))
+ })
+ assertEquals(1, batch.queue.size)
+ assertEquals("Someone", batch.queue[0].values[Relation.NAME])
+ assertEquals(Relation.TYPE_CUSTOM, batch.queue[0].values[Relation.TYPE])
+ assertEquals("Some Relationship", batch.queue[0].values[Relation.LABEL])
+ }
+
+ @Test
+ fun testInsertRelation_Custom_TwoValues() {
+ val batch = BatchOperation(provider)
+ AndroidContact(addressBook).insertRelation(batch, Related().apply {
+ text = "Someone"
+ types.add(RelatedType.get("type1"))
+ types.add(RelatedType.get("type2"))
+ })
+ assertEquals(1, batch.queue.size)
+ assertEquals("Someone", batch.queue[0].values[Relation.NAME])
+ assertEquals(Relation.TYPE_CUSTOM, batch.queue[0].values[Relation.TYPE])
+ assertEquals("Type1, Type2", batch.queue[0].values[Relation.LABEL])
+ }
+
+ @Test
+ fun testInsertRelation_NoType_Email() {
+ val batch = BatchOperation(provider)
+ AndroidContact(addressBook).insertRelation(batch, Related.email("test@example.com"))
+ assertEquals(1, batch.queue.size)
+ assertEquals("mailto:test@example.com", batch.queue[0].values[Relation.NAME])
+ assertEquals(Relation.TYPE_CUSTOM, batch.queue[0].values[Relation.TYPE])
+ assertEquals("Other", batch.queue[0].values[Relation.LABEL])
+ }
+
}
diff --git a/src/main/java/at/bitfire/vcard4android/AndroidContact.kt b/src/main/java/at/bitfire/vcard4android/AndroidContact.kt
index 2d5172d..1d8d19f 100644
--- a/src/main/java/at/bitfire/vcard4android/AndroidContact.kt
+++ b/src/main/java/at/bitfire/vcard4android/AndroidContact.kt
@@ -26,6 +26,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName
import android.provider.ContactsContract.RawContacts
import android.provider.ContactsContract.RawContacts.Data
import androidx.annotation.CallSuper
+import at.bitfire.vcard4android.property.CustomRelatedType
import at.bitfire.vcard4android.property.XAbDate
import ezvcard.parameter.*
import ezvcard.property.*
@@ -33,6 +34,7 @@ import ezvcard.util.PartialDate
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.builder.ToStringBuilder
+import org.apache.commons.text.WordUtils
import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException
import java.io.IOException
@@ -472,29 +474,52 @@ open class AndroidContact(
related.text = name
when (row.getAsInteger(Relation.TYPE)) {
- Relation.TYPE_ASSISTANT,
- Relation.TYPE_MANAGER ->
+ Relation.TYPE_ASSISTANT -> {
+ related.types += CustomRelatedType.ASSISTANT
related.types += RelatedType.CO_WORKER
- Relation.TYPE_BROTHER,
- Relation.TYPE_SISTER ->
+ }
+ Relation.TYPE_BROTHER -> {
+ related.types += CustomRelatedType.BROTHER
related.types += RelatedType.SIBLING
+ }
Relation.TYPE_CHILD ->
related.types += RelatedType.CHILD
+ Relation.TYPE_DOMESTIC_PARTNER -> {
+ related.types += CustomRelatedType.DOMESTIC_PARTNER
+ related.types += RelatedType.SPOUSE
+ }
+ Relation.TYPE_FATHER -> {
+ related.types += CustomRelatedType.FATHER
+ related.types += RelatedType.PARENT
+ }
Relation.TYPE_FRIEND ->
related.types += RelatedType.FRIEND
- Relation.TYPE_FATHER,
- Relation.TYPE_MOTHER,
+ Relation.TYPE_MANAGER -> {
+ related.types += CustomRelatedType.MANAGER
+ related.types += RelatedType.CO_WORKER
+ }
+ Relation.TYPE_MOTHER -> {
+ related.types += CustomRelatedType.MOTHER
+ related.types += RelatedType.PARENT
+ }
Relation.TYPE_PARENT ->
related.types += RelatedType.PARENT
- Relation.TYPE_DOMESTIC_PARTNER,
- Relation.TYPE_PARTNER,
- Relation.TYPE_SPOUSE ->
- related.types += RelatedType.SPOUSE
+ Relation.TYPE_PARTNER ->
+ related.types += CustomRelatedType.PARTNER
+ Relation.TYPE_REFERRED_BY ->
+ related.types += CustomRelatedType.REFERRED_BY
Relation.TYPE_RELATIVE ->
related.types += RelatedType.KIN
+ Relation.TYPE_SISTER -> {
+ related.types += CustomRelatedType.SISTER
+ related.types += RelatedType.SIBLING
+ }
+ Relation.TYPE_SPOUSE ->
+ related.types += RelatedType.SPOUSE
Relation.TYPE_CUSTOM ->
- row.getAsString(Relation.LABEL)?.split(",")?.forEach {
- related.types += RelatedType.get(it.trim())
+ StringUtils.trimToNull(row.getAsString(Relation.LABEL))?.let { typeStr ->
+ for (type in typeStr.split(','))
+ related.types += RelatedType.get(type.lowercase().trim())
}
}
@@ -1039,28 +1064,47 @@ open class AndroidContact(
batch.enqueue(builder)
}
- protected open fun insertRelation(batch: BatchOperation, related: Related) {
- if (related.text.isNullOrEmpty())
+ internal open fun insertRelation(batch: BatchOperation, related: Related) {
+ val name = StringUtils.trimToNull(related.text) ?: StringUtils.trimToNull(related.uri)
+ if (name.isNullOrEmpty())
return
- var typeCode = Relation.TYPE_CUSTOM
-
- val labels = LinkedList<String>()
- for (type in related.types)
- when (type) {
- RelatedType.CHILD -> typeCode = Relation.TYPE_CHILD
- RelatedType.SPOUSE -> typeCode = Relation.TYPE_PARTNER
- RelatedType.FRIEND -> typeCode = Relation.TYPE_FRIEND
- RelatedType.KIN -> typeCode = Relation.TYPE_RELATIVE
- RelatedType.PARENT -> typeCode = Relation.TYPE_PARENT
- else -> labels += type.value
- }
+ var typeCode = when {
+ // specific Android types (not defined in RFC 6350)
+ related.types.contains(CustomRelatedType.ASSISTANT) -> Relation.TYPE_ASSISTANT
+ related.types.contains(CustomRelatedType.BROTHER) -> Relation.TYPE_BROTHER
+ related.types.contains(CustomRelatedType.DOMESTIC_PARTNER) -> Relation.TYPE_DOMESTIC_PARTNER
+ related.types.contains(CustomRelatedType.FATHER) -> Relation.TYPE_FATHER
+ related.types.contains(CustomRelatedType.MANAGER) -> Relation.TYPE_MANAGER
+ related.types.contains(CustomRelatedType.MOTHER) -> Relation.TYPE_MOTHER
+ related.types.contains(CustomRelatedType.PARTNER) -> Relation.TYPE_PARTNER
+ related.types.contains(CustomRelatedType.REFERRED_BY) -> Relation.TYPE_REFERRED_BY
+ related.types.contains(CustomRelatedType.SISTER) -> Relation.TYPE_SISTER
+
+ // standard types (defined in RFC 6350) supported by Android
+ related.types.contains(RelatedType.FRIEND) -> Relation.TYPE_FRIEND
+ related.types.contains(RelatedType.KIN) -> Relation.TYPE_RELATIVE
+ related.types.contains(RelatedType.PARENT) -> Relation.TYPE_PARENT
+ related.types.contains(RelatedType.SPOUSE) -> Relation.TYPE_SPOUSE
+
+ // other standard types are set as TYPE_CUSTOM
+ else -> Relation.TYPE_CUSTOM
+ }
val builder = insertDataBuilder(Relation.RAW_CONTACT_ID)
.withValue(Relation.MIMETYPE, Relation.CONTENT_ITEM_TYPE)
- .withValue(Relation.NAME, related.text)
+ .withValue(Relation.NAME, name)
.withValue(Relation.TYPE, typeCode)
- .withValue(Relation.LABEL, StringUtils.trimToNull(labels.joinToString(", ")))
+
+ if (typeCode == Relation.TYPE_CUSTOM) {
+ if (related.types.isEmpty())
+ related.types += CustomRelatedType.OTHER
+
+ val types = related.types.map { type -> WordUtils.capitalize(type.value) }
+ val typesStr = types.joinToString(", ")
+ builder.withValue(Relation.LABEL, typesStr)
+ }
+
batch.enqueue(builder)
}
diff --git a/src/main/java/at/bitfire/vcard4android/BatchOperation.kt b/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
index 0ac1109..b7c9f9f 100644
--- a/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
+++ b/src/main/java/at/bitfire/vcard4android/BatchOperation.kt
@@ -30,7 +30,7 @@ class BatchOperation(
}
- private val queue = LinkedList<CpoBuilder>()
+ internal val queue = LinkedList<CpoBuilder>()
private var results = arrayOfNulls<ContentProviderResult?>(0)
diff --git a/src/main/java/at/bitfire/vcard4android/Contact.kt b/src/main/java/at/bitfire/vcard4android/Contact.kt
index e94322a..5145833 100644
--- a/src/main/java/at/bitfire/vcard4android/Contact.kt
+++ b/src/main/java/at/bitfire/vcard4android/Contact.kt
@@ -8,7 +8,7 @@
package at.bitfire.vcard4android
-import at.bitfire.vcard4android.property.CustomScribes
+import at.bitfire.vcard4android.property.CustomScribes.registerCustomScribes
import at.bitfire.vcard4android.property.XAbDate
import ezvcard.VCardVersion
import ezvcard.io.text.VCardReader
@@ -110,9 +110,6 @@ class Contact {
const val DATE_PARAMETER_OMIT_YEAR = "X-APPLE-OMIT-YEAR"
const val DATE_PARAMETER_OMIT_YEAR_DEFAULT = 1604
- const val DATE_LABEL_ANNIVERSARY = "_\$!<Anniversary>!\$_"
- const val DATE_LABEL_OTHER = "_\$!<Other>!\$_"
-
/**
* Parses an InputStream that contains a vCard.
@@ -125,9 +122,9 @@ class Contact {
*/
fun fromReader(reader: Reader, downloader: Downloader?): List<Contact> {
// create new vCard reader and add custom scribes
- val vCardReader = VCardReader(reader, VCardVersion.V3_0) // CardDAV requires vCard 3 or newer
- CustomScribes.registerAt(vCardReader)
- val vCards = vCardReader.readAll()
+ val vCards = VCardReader(reader, VCardVersion.V3_0) // CardDAV requires vCard 3 or newer
+ .registerCustomScribes()
+ .readAll()
return vCards.map { vCard ->
// convert every vCard to a Contact data object
diff --git a/src/main/java/at/bitfire/vcard4android/ContactReader.kt b/src/main/java/at/bitfire/vcard4android/ContactReader.kt
index 21c2078..46c78c2 100644
--- a/src/main/java/at/bitfire/vcard4android/ContactReader.kt
+++ b/src/main/java/at/bitfire/vcard4android/ContactReader.kt
@@ -1,8 +1,10 @@
package at.bitfire.vcard4android
import at.bitfire.vcard4android.property.*
+import at.bitfire.vcard4android.property.CustomScribes.registerCustomScribes
import ezvcard.Ezvcard
import ezvcard.VCard
+import ezvcard.parameter.RelatedType
import ezvcard.property.*
import ezvcard.util.PartialDate
import org.apache.commons.lang3.StringUtils
@@ -157,10 +159,10 @@ class ContactReader internal constructor(val vCard: VCard, val downloader: Conta
is XAbDate -> {
checkPartialDate(prop)
var label = findAndRemoveLabel(prop.group)
- if (label == Contact.DATE_LABEL_OTHER) // drop Apple "Other" label
+ if (label == XAbLabel.APPLE_OTHER) // drop Apple "Other" label
label = null
- if (label == Contact.DATE_LABEL_ANNIVERSARY) { // convert custom date with Apple "Anniversary" label to real anniversary
+ if (label == XAbLabel.APPLE_ANNIVERSARY) { // convert custom date with Apple "Anniversary" label to real anniversary
if (prop.date != null)
c.anniversary = Anniversary(prop.date)
else if (prop.partialDate != null)
@@ -172,6 +174,54 @@ class ContactReader internal constructor(val vCard: VCard, val downloader: Conta
is Related ->
if (!prop.uri.isNullOrBlank() || !prop.text.isNullOrBlank())
c.relations += prop
+ is XAbRelatedNames -> {
+ val relation = Related()
+ relation.text = prop.value
+
+ val labelStr = findAndRemoveLabel(prop.group)
+ when (labelStr) {
+ XAbRelatedNames.APPLE_ASSISTANT -> {
+ relation.types.add(CustomRelatedType.ASSISTANT)
+ relation.types.add(RelatedType.CO_WORKER)
+ }
+ XAbRelatedNames.APPLE_BROTHER -> {
+ relation.types.add(CustomRelatedType.BROTHER)
+ relation.types.add(RelatedType.SIBLING)
+ }
+ XAbRelatedNames.APPLE_CHILD ->
+ relation.types.add(RelatedType.CHILD)
+ XAbRelatedNames.APPLE_FATHER -> {
+ relation.types.add(CustomRelatedType.FATHER)
+ relation.types.add(RelatedType.PARENT)
+ }
+ XAbRelatedNames.APPLE_FRIEND ->
+ relation.types.add(RelatedType.FRIEND)
+ XAbRelatedNames.APPLE_MANAGER -> {
+ relation.types.add(CustomRelatedType.MANAGER)
+ relation.types.add(RelatedType.CO_WORKER)
+ }
+ XAbRelatedNames.APPLE_MOTHER -> {
+ relation.types.add(CustomRelatedType.MOTHER)
+ relation.types.add(RelatedType.PARENT)
+ }
+ XAbRelatedNames.APPLE_SISTER -> {
+ relation.types.add(CustomRelatedType.SISTER)
+ relation.types.add(RelatedType.SIBLING)
+ }
+ XAbRelatedNames.APPLE_PARENT ->
+ relation.types.add(RelatedType.PARENT)
+ XAbRelatedNames.APPLE_PARTNER ->
+ relation.types.add(CustomRelatedType.PARTNER)
+ XAbRelatedNames.APPLE_SPOUSE ->
+ relation.types.add(RelatedType.SPOUSE)
+
+ is String /* label != null */ -> {
+ for (label in labelStr.split(','))
+ relation.types.add(RelatedType.get(label.trim().lowercase()))
+ }
+ }
+ c.relations += relation
+ }
is Note -> {
StringUtils.trimToNull(prop.value)?.let { note ->
@@ -223,12 +273,12 @@ class ContactReader internal constructor(val vCard: VCard, val downloader: Conta
if (vCard.properties.isNotEmpty() || vCard.extendedProperties.isNotEmpty())
try {
- val writer = Ezvcard
+ c.unknownProperties = Ezvcard
.write(vCard)
.prodId(false)
.version(vCard.version)
- CustomScribes.registerAt(writer)
- c.unknownProperties = writer.go()
+ .registerCustomScribes()
+ .go()
} catch(e: Exception) {
Constants.log.log(Level.WARNING, "Couldn't serialize unknown properties, dropping them", e)
}
diff --git a/src/main/java/at/bitfire/vcard4android/ContactWriter.kt b/src/main/java/at/bitfire/vcard4android/ContactWriter.kt
index 7e882c2..eb95808 100644
--- a/src/main/java/at/bitfire/vcard4android/ContactWriter.kt
+++ b/src/main/java/at/bitfire/vcard4android/ContactWriter.kt
@@ -1,13 +1,16 @@
package at.bitfire.vcard4android
import at.bitfire.vcard4android.property.*
+import at.bitfire.vcard4android.property.CustomScribes.registerCustomScribes
import ezvcard.Ezvcard
import ezvcard.VCard
import ezvcard.VCardVersion
import ezvcard.io.text.VCardWriter
import ezvcard.parameter.ImageType
+import ezvcard.parameter.RelatedType
import ezvcard.property.*
import org.apache.commons.lang3.StringUtils
+import org.apache.commons.text.WordUtils
import java.io.OutputStream
import java.util.*
import java.util.logging.Level
@@ -71,7 +74,7 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard
addDates()
for (relation in contact.relations)
- vCard.addRelated(relation)
+ addRelation(relation)
contact.note?.let { note -> vCard.addNote(note) }
@@ -99,8 +102,8 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard
} else /* version == VCardVersion.V3_0 */ {
// vCard3 doesn't support partial dates
rewritePartialDate(anniversary)
- // vCard3 doesn't support ANNIVERSARY, rewrite to X-ABDate
- addLabeledProperty(LabeledProperty(XAbDate(anniversary.date), Contact.DATE_LABEL_ANNIVERSARY))
+ // vCard3 doesn't support ANNIVERSARY, rewrite to X-ABDATE
+ addLabeledProperty(LabeledProperty(XAbDate(anniversary.date), XAbLabel.APPLE_ANNIVERSARY))
vCard.anniversary = null
}
}
@@ -157,6 +160,53 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard
contact.jobDescription?.let { vCard.addRole(it) }
}
+ private fun addRelation(relation: Related) {
+ if (version == VCardVersion.V4_0)
+ vCard.addRelated(relation)
+
+ else /* version == VCardVersion.V3_0 */ {
+ val name = XAbRelatedNames(relation.text ?: relation.uri)
+ var label: String? = null
+
+ val types = LinkedList(relation.types)
+ types.remove(CustomRelatedType.OTHER) // ignore this type (has to be inserted by ContactReader when no type is set)
+
+ when {
+ types.contains(CustomRelatedType.ASSISTANT) ->
+ label = XAbRelatedNames.APPLE_ASSISTANT
+ types.contains(CustomRelatedType.BROTHER) ->
+ label = XAbRelatedNames.APPLE_BROTHER
+ types.contains(RelatedType.CHILD) ->
+ label = XAbRelatedNames.APPLE_CHILD
+ types.contains(CustomRelatedType.FATHER) ->
+ label = XAbRelatedNames.APPLE_FATHER
+ types.contains(RelatedType.FRIEND) ->
+ label = XAbRelatedNames.APPLE_FRIEND
+ types.contains(CustomRelatedType.MANAGER) ->
+ label = XAbRelatedNames.APPLE_MANAGER
+ types.contains(CustomRelatedType.MOTHER) ->
+ label = XAbRelatedNames.APPLE_MOTHER
+ types.contains(RelatedType.PARENT) ->
+ label = XAbRelatedNames.APPLE_PARENT
+ types.contains(CustomRelatedType.PARTNER) ->
+ label = XAbRelatedNames.APPLE_PARTNER
+ types.contains(CustomRelatedType.SISTER) ->
+ label = XAbRelatedNames.APPLE_SISTER
+ types.contains(RelatedType.SPOUSE) ->
+ label = XAbRelatedNames.APPLE_SPOUSE
+
+ else -> {
+ if (relation.types.isEmpty())
+ name.addParameter("TYPE", "other")
+ else
+ label = relation.types.map { type -> WordUtils.capitalize(type.value) }.joinToString(", ")
+ }
+ }
+
+ addLabeledProperty(LabeledProperty(name, label))
+ }
+ }
+
private fun addStructuredName() {
val n = StructuredName()
@@ -278,7 +328,7 @@ class ContactWriter private constructor(val contact: Contact, val version: VCard
val writer = VCardWriter(stream, version).apply {
isAddProdId = Contact.productID == null
- CustomScribes.registerAt(scribeIndex)
+ registerCustomScribes()
// include trailing semicolons for maximum compatibility
isIncludeTrailingSemicolons = true
diff --git a/src/main/java/at/bitfire/vcard4android/property/CustomRelatedType.kt b/src/main/java/at/bitfire/vcard4android/property/CustomRelatedType.kt
new file mode 100644
index 0000000..74f426d
--- /dev/null
+++ b/src/main/java/at/bitfire/vcard4android/property/CustomRelatedType.kt
@@ -0,0 +1,19 @@
+package at.bitfire.vcard4android.property
+
+import ezvcard.parameter.RelatedType
+
+object CustomRelatedType {
+
+ val ASSISTANT = RelatedType.get("assistant")
+ val BROTHER = RelatedType.get("brother")
+ val DOMESTIC_PARTNER = RelatedType.get("domestic-partner")
+ val FATHER = RelatedType.get("father")
+ val MANAGER = RelatedType.get("manager")
+ val MOTHER = RelatedType.get("mother")
+ val PARTNER = RelatedType.get("partner")
+ val REFERRED_BY = RelatedType.get("referred-by")
+ val SISTER = RelatedType.get("sister")
+
+ val OTHER = RelatedType.get("other")
+
+} \ No newline at end of file
diff --git a/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt b/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt
index 84fc41b..6a35f17 100644
--- a/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt
+++ b/src/main/java/at/bitfire/vcard4android/property/CustomScribes.kt
@@ -3,6 +3,7 @@ package at.bitfire.vcard4android.property
import ezvcard.io.chain.ChainingTextWriter
import ezvcard.io.scribe.ScribeIndex
import ezvcard.io.text.VCardReader
+import ezvcard.io.text.VCardWriter
object CustomScribes {
@@ -10,6 +11,7 @@ object CustomScribes {
val customScribes = arrayOf(
XAbDate.Scribe,
XAbLabel.Scribe,
+ XAbRelatedNames.Scribe,
XAddressBookServerKind.Scribe,
XAddressBookServerMember.Scribe,
XPhoneticFirstName.Scribe,
@@ -18,20 +20,22 @@ object CustomScribes {
XSip.Scribe
)
- fun registerAt(writer: ChainingTextWriter) {
+
+ fun ChainingTextWriter.registerCustomScribes(): ChainingTextWriter {
for (scribe in customScribes)
- writer.register(scribe)
+ register(scribe)
+ return this
}
- fun registerAt(index: ScribeIndex) {
+ fun VCardReader.registerCustomScribes(): VCardReader {
for (scribe in customScribes)
- index.register(scribe)
+ scribeIndex.register(scribe)
+ return this
}
- fun registerAt(reader: VCardReader): VCardReader {
+ fun VCardWriter.registerCustomScribes() {
for (scribe in customScribes)
- reader.scribeIndex.register(scribe)
- return reader
+ scribeIndex.register(scribe)
}
}
diff --git a/src/main/java/at/bitfire/vcard4android/property/XAbLabel.kt b/src/main/java/at/bitfire/vcard4android/property/XAbLabel.kt
index 103f153..1d07f64 100644
--- a/src/main/java/at/bitfire/vcard4android/property/XAbLabel.kt
+++ b/src/main/java/at/bitfire/vcard4android/property/XAbLabel.kt
@@ -5,6 +5,13 @@ import ezvcard.property.TextProperty
class XAbLabel(value: String?): TextProperty(value) {
+ companion object {
+
+ const val APPLE_ANNIVERSARY = "_\$!<Anniversary>!\$_"
+ const val APPLE_OTHER = "_\$!<Other>!\$_"
+
+ }
+
object Scribe :
StringPropertyScribe<XAbLabel>(XAbLabel::class.java, "X-ABLABEL") {
diff --git a/src/main/java/at/bitfire/vcard4android/property/XAbRelatedNames.kt b/src/main/java/at/bitfire/vcard4android/property/XAbRelatedNames.kt
new file mode 100644
index 0000000..7875cdd
--- /dev/null
+++ b/src/main/java/at/bitfire/vcard4android/property/XAbRelatedNames.kt
@@ -0,0 +1,31 @@
+package at.bitfire.vcard4android.property
+
+import ezvcard.io.scribe.StringPropertyScribe
+import ezvcard.property.TextProperty
+
+class XAbRelatedNames(value: String?): TextProperty(value) {
+
+ companion object {
+
+ const val APPLE_ASSISTANT = "_\$!<Assistant>!\$_"
+ const val APPLE_BROTHER = "_\$!<Brother>!\$_"
+ const val APPLE_CHILD = "_\$!<Child>!\$_"
+ const val APPLE_FATHER = "_\$!<Father>!\$_"
+ const val APPLE_FRIEND = "_\$!<Friend>!\$_"
+ const val APPLE_MANAGER = "_\$!<Manager>!\$_"
+ const val APPLE_MOTHER = "_\$!<Mother>!\$_"
+ const val APPLE_PARENT = "_\$!<Parent>!\$_"
+ const val APPLE_PARTNER = "_\$!<Partner>!\$_"
+ const val APPLE_SISTER = "_\$!<Sister>!\$_"
+ const val APPLE_SPOUSE = "_\$!<Spouse>!\$_"
+
+ }
+
+ object Scribe :
+ StringPropertyScribe<XAbRelatedNames>(XAbRelatedNames::class.java, "X-ABRELATEDNAMES") {
+
+ override fun _parseValue(value: String?) = XAbRelatedNames(value)
+
+ }
+
+} \ No newline at end of file
diff --git a/src/test/java/at/bitfire/vcard4android/ContactReaderTest.kt b/src/test/java/at/bitfire/vcard4android/ContactReaderTest.kt
index 51007dc..9cfd836 100644
--- a/src/test/java/at/bitfire/vcard4android/ContactReaderTest.kt
+++ b/src/test/java/at/bitfire/vcard4android/ContactReaderTest.kt
@@ -422,7 +422,7 @@ class ContactReaderTest {
val date = Date(101, 6, 30, 0, 0, 0)
val c = ContactReader.fromVCard(VCard().apply {
addProperty(XAbDate(date).apply { group = "test1" })
- addProperty(XAbLabel(Contact.DATE_LABEL_ANNIVERSARY).apply { group = "test1" })
+ addProperty(XAbLabel(XAbLabel.APPLE_ANNIVERSARY).apply { group = "test1" })
})
assertEquals(0, c.customDates.size)
assertEquals(Anniversary(date), c.anniversary)
@@ -433,7 +433,7 @@ class ContactReaderTest {
val date = Date(101, 6, 30, 0, 0, 0)
val c = ContactReader.fromVCard(VCard().apply {
addProperty(XAbDate(date).apply { group = "test1" })
- addProperty(XAbLabel(Contact.DATE_LABEL_OTHER).apply { group = "test1" })
+ addProperty(XAbLabel(XAbLabel.APPLE_OTHER).apply { group = "test1" })
})
assertEquals(date, c.customDates.first.property.date)
assertNull(c.customDates.first.label)
@@ -452,6 +452,52 @@ class ContactReaderTest {
@Test
+ fun testXAbRelatedNames_Sister() {
+ val c = ContactReader.fromVCard(VCard().apply {
+ addProperty(XAbRelatedNames("My Sis").apply { group = "item1" })
+ addProperty(XAbLabel(XAbRelatedNames.APPLE_SISTER).apply { group = "item1" })
+ })
+ assertEquals("My Sis", c.relations.first.text)
+ assertTrue(c.relations.first.types.contains(RelatedType.SIBLING))
+ assertTrue(c.relations.first.types.contains(CustomRelatedType.SISTER))
+ }
+
+ @Test
+ fun testXAbRelatedNames_Custom() {
+ val c = ContactReader.fromVCard(VCard().apply {
+ addProperty(XAbRelatedNames("Someone Other").apply { group = "item1" })
+ addProperty(XAbLabel("Someone").apply { group = "item1" })
+ })
+ assertEquals("Someone Other", c.relations.first.text)
+ assertEquals(1, c.relations.first.types.size)
+ assertTrue(c.relations.first.types.contains(RelatedType.get("someone")))
+ }
+
+ @Test
+ fun testXAbRelatedNames_Custom_Acquitance() {
+ val c = ContactReader.fromVCard(VCard().apply {
+ addProperty(XAbRelatedNames("Someone Other").apply { group = "item1" })
+ addProperty(XAbLabel(RelatedType.ACQUAINTANCE.value).apply { group = "item1" })
+ })
+ assertEquals("Someone Other", c.relations.first.text)
+ assertEquals(1, c.relations.size)
+ assertTrue(c.relations.first.types.contains(RelatedType.ACQUAINTANCE))
+ }
+
+ @Test
+ fun testXAbRelatedNames_Custom_TwoValues() {
+ val c = ContactReader.fromVCard(VCard().apply {
+ addProperty(XAbRelatedNames("Someone Other").apply { group = "item1" })
+ addProperty(XAbLabel("dog, cat").apply { group = "item1" })
+ })
+ assertEquals("Someone Other", c.relations.first.text)
+ assertEquals(2, c.relations.first.types.size)
+ assertTrue(c.relations.first.types.contains(RelatedType.get("cat")))
+ assertTrue(c.relations.first.types.contains(RelatedType.get("dog")))
+ }
+
+
+ @Test
fun testXAddressBookServerKind_Group() {
val c = ContactReader.fromVCard(VCard().apply {
addProperty(XAddressBookServerKind(Kind.GROUP))
diff --git a/src/test/java/at/bitfire/vcard4android/ContactTest.kt b/src/test/java/at/bitfire/vcard4android/ContactTest.kt
index f8aa19a..d003f26 100644
--- a/src/test/java/at/bitfire/vcard4android/ContactTest.kt
+++ b/src/test/java/at/bitfire/vcard4android/ContactTest.kt
@@ -200,7 +200,7 @@ class ContactTest {
assertEquals("Ägidius", rel.text)
rel = c.relations[1]
assertTrue(rel.types.contains(RelatedType.PARENT))
- assertEquals("muuum@example.com", rel.uri)
+ assertEquals("muuum@example.com", rel.text)
// PHOTO
javaClass.classLoader!!.getResourceAsStream("lol.jpg").use { photo ->
diff --git a/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt b/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt
index bab087b..9331786 100644
--- a/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt
+++ b/src/test/java/at/bitfire/vcard4android/ContactWriterTest.kt
@@ -2,9 +2,9 @@ package at.bitfire.vcard4android
import at.bitfire.vcard4android.property.*
import ezvcard.VCard
-import ezvcard.VCardDataType
import ezvcard.VCardVersion
import ezvcard.parameter.ImageType
+import ezvcard.parameter.RelatedType
import ezvcard.property.*
import ezvcard.util.PartialDate
import org.junit.Assert.*
@@ -278,9 +278,98 @@ class ContactWriterTest {
@Test
- fun testRelation() {
+ fun testRelation_vCard3_Assistant() { // combination of custom type (assistant) and vCard4 standard type (co-worker)
+ val vCard = generate(version = VCardVersion.V3_0) {
+ relations += Related().apply {
+ text = "My Assistant"
+ types.add(CustomRelatedType.ASSISTANT)
+ types.add(RelatedType.CO_WORKER)
+ }
+ }
+ vCard.getProperty(XAbRelatedNames::class.java).apply {
+ assertEquals("My Assistant", value)
+ assertEquals("item1", group)
+ }
+ vCard.getProperty(XAbLabel::class.java).apply {
+ assertEquals(XAbRelatedNames.APPLE_ASSISTANT, value)
+ assertEquals("item1", group)
+ }
+ assertTrue(vCard.relations.isEmpty())
+ }
+
+ @Test
+ fun testRelation_vCard3_Child() { // vCard4 standard type
+ val vCard = generate(version = VCardVersion.V3_0) {
+ relations += Related().apply {
+ text = "My Child"
+ types.add(RelatedType.CHILD)
+ }
+ }
+ vCard.getProperty(XAbRelatedNames::class.java).apply {
+ assertEquals("My Child", value)
+ assertEquals("item1", group)
+ }
+ vCard.getProperty(XAbLabel::class.java).apply {
+ assertEquals(XAbRelatedNames.APPLE_CHILD, value)
+ assertEquals("item1", group)
+ }
+ assertTrue(vCard.relations.isEmpty())
+ }
+
+ @Test
+ fun testRelation_vCard3_Custom() {
+ val vCard = generate(version = VCardVersion.V3_0) {
+ relations += Related().apply {
+ text = "Someone"
+ types.add(RelatedType.get("Custom Relationship"))
+ }
+ }
+ vCard.getProperty(XAbRelatedNames::class.java).apply {
+ assertEquals("Someone", value)
+ assertEquals("item1", group)
+ }
+ vCard.getProperty(XAbLabel::class.java).apply {
+ assertEquals("Custom Relationship", value)
+ assertEquals("item1", group)
+ }
+ assertTrue(vCard.relations.isEmpty())
+ }
+
+ @Test
+ fun testRelation_vCard3_Other() {
+ val rel = Related.email("bigbrother@example.com")
+ val vCard = generate(version = VCardVersion.V3_0) { relations += rel }
+ vCard.getProperty(XAbRelatedNames::class.java).apply {
+ assertEquals("mailto:bigbrother@example.com", value)
+ assertEquals("other", getParameter("TYPE"))
+ assertNull(group)
+ }
+ assertTrue(vCard.relations.isEmpty())
+ }
+
+ @Test
+ fun testRelation_vCard3_Partner() { // custom type
+ val vCard = generate(version = VCardVersion.V3_0) {
+ relations += Related().apply {
+ text = "My Partner"
+ types.add(CustomRelatedType.PARTNER)
+ }
+ }
+ vCard.getProperty(XAbRelatedNames::class.java).apply {
+ assertEquals("My Partner", value)
+ assertEquals("item1", group)
+ }
+ vCard.getProperty(XAbLabel::class.java).apply {
+ assertEquals(XAbRelatedNames.APPLE_PARTNER, value)
+ assertEquals("item1", group)
+ }
+ assertTrue(vCard.relations.isEmpty())
+ }
+
+ @Test
+ fun testRelation_vCard4() {
val rel = Related.email("bigbrother@example.com")
- val vCard = generate { relations += rel }
+ val vCard = generate(version = VCardVersion.V4_0) { relations += rel }
assertEquals(rel, vCard.relations.first())
}