From cf65ed0f1b3ba68d5e1fc5815f20680be7825219 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 10 Mar 2022 00:00:29 +0100 Subject: Add various messengers --- .../vcard4android/contactrow/ImBuilderTest.kt | 34 ++++-- .../vcard4android/contactrow/ImHandlerTest.kt | 10 -- .../vcard4android/contactrow/ImMappingTest.kt | 133 +++++++++++++++++++++ .../bitfire/vcard4android/contactrow/ImBuilder.kt | 73 +++-------- .../bitfire/vcard4android/contactrow/ImHandler.kt | 39 ++---- .../bitfire/vcard4android/contactrow/ImMapping.kt | 107 +++++++++++++---- .../vcard4android/contactrow/SipAddressBuilder.kt | 2 +- .../vcard4android/contactrow/SipAddressHandler.kt | 2 +- .../bitfire/vcard4android/property/CustomType.kt | 20 +--- 9 files changed, 274 insertions(+), 146 deletions(-) create mode 100644 src/androidTest/java/at/bitfire/vcard4android/contactrow/ImMappingTest.kt diff --git a/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImBuilderTest.kt b/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImBuilderTest.kt index fb50d6e..cc9f58e 100644 --- a/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImBuilderTest.kt +++ b/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImBuilderTest.kt @@ -5,6 +5,7 @@ package at.bitfire.vcard4android.contactrow import android.net.Uri +import android.os.Build import android.provider.ContactsContract.CommonDataKinds import at.bitfire.vcard4android.Contact import at.bitfire.vcard4android.LabeledProperty @@ -38,7 +39,12 @@ class ImBuilderTest { ImBuilder(Uri.EMPTY, null, Contact().apply { impps += LabeledProperty(Impp("test@example.com")) }).build().also { result -> - assertEquals(0, result.size) + assertEquals(1, result.size) + + assertEquals(CommonDataKinds.Im.PROTOCOL_CUSTOM, result[0].values[CommonDataKinds.Im.PROTOCOL]) + assertEquals("test@example.com", result[0].values[CommonDataKinds.Im.DATA]) + assertNull(result[0].values[CommonDataKinds.Im.CUSTOM_PROTOCOL]) + assertNull(result[0].values[CommonDataKinds.Im.LABEL]) } } @@ -62,24 +68,26 @@ class ImBuilderTest { ImBuilder(Uri.EMPTY, null, Contact().apply { impps += LabeledProperty(Impp.xmpp("jabber@example.com")) impps += LabeledProperty(Impp.skype("skype-id")) - impps += LabeledProperty(Impp("qq", "qq-id")) }).build().also { result -> - assertEquals(3, result.size) - - assertEquals(CommonDataKinds.Im.PROTOCOL_JABBER, result[0].values[CommonDataKinds.Im.PROTOCOL]) + assertEquals(2, result.size) + + if (Build.VERSION.SDK_INT < 31) + assertEquals(CommonDataKinds.Im.PROTOCOL_JABBER, result[0].values[CommonDataKinds.Im.PROTOCOL]) + else { + assertEquals(CommonDataKinds.Im.PROTOCOL_CUSTOM, result[0].values[CommonDataKinds.Im.PROTOCOL]) + assertEquals(ImMapping.MESSENGER_XMPP, result[0].values[CommonDataKinds.Im.CUSTOM_PROTOCOL]) + } assertEquals("jabber@example.com", result[0].values[CommonDataKinds.Im.DATA]) - assertNull(result[0].values[CommonDataKinds.Im.CUSTOM_PROTOCOL]) assertNull(result[0].values[CommonDataKinds.Im.LABEL]) - assertEquals(CommonDataKinds.Im.PROTOCOL_SKYPE, result[1].values[CommonDataKinds.Im.PROTOCOL]) + if (Build.VERSION.SDK_INT < 31) + assertEquals(CommonDataKinds.Im.PROTOCOL_SKYPE, result[1].values[CommonDataKinds.Im.PROTOCOL]) + else { + assertEquals(CommonDataKinds.Im.PROTOCOL_CUSTOM, result[1].values[CommonDataKinds.Im.PROTOCOL]) + assertEquals(ImMapping.MESSENGER_SKYPE, result[1].values[CommonDataKinds.Im.CUSTOM_PROTOCOL]) + } assertEquals("skype-id", result[1].values[CommonDataKinds.Im.DATA]) - assertNull(result[1].values[CommonDataKinds.Im.CUSTOM_PROTOCOL]) assertNull(result[1].values[CommonDataKinds.Im.LABEL]) - - assertEquals(CommonDataKinds.Im.PROTOCOL_QQ, result[2].values[CommonDataKinds.Im.PROTOCOL]) - assertEquals("qq-id", result[2].values[CommonDataKinds.Im.DATA]) - assertNull(result[2].values[CommonDataKinds.Im.CUSTOM_PROTOCOL]) - assertNull(result[2].values[CommonDataKinds.Im.LABEL]) } } diff --git a/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImHandlerTest.kt b/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImHandlerTest.kt index b729aaf..1a4bd23 100644 --- a/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImHandlerTest.kt +++ b/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImHandlerTest.kt @@ -129,14 +129,4 @@ class ImHandlerTest { assertEquals(ImppType.WORK, contact.impps[0].property.types[0]) } - - // tests for helpers - - @Test - fun testProtocolToUriScheme() { - assertNull(ImHandler.protocolToUriScheme(null)) - assertEquals("", ImHandler.protocolToUriScheme("")) - assertEquals("protocol", ImHandler.protocolToUriScheme("PrO/ätO\\cOl")) - } - } \ No newline at end of file diff --git a/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImMappingTest.kt b/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImMappingTest.kt new file mode 100644 index 0000000..64d396b --- /dev/null +++ b/src/androidTest/java/at/bitfire/vcard4android/contactrow/ImMappingTest.kt @@ -0,0 +1,133 @@ +/*************************************************************************************************** + * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. + **************************************************************************************************/ + +package at.bitfire.vcard4android.contactrow + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.net.URI + +class ImMappingTest { + + @Test + fun testMessengerToUri_Known() { + assertEquals(URI("aim:user"), ImMapping.messengerToUri("AIM", "user")) + assertEquals(URI("https://facebook.com/user"), ImMapping.messengerToUri("Facebook", "user")) + assertEquals(URI("https://icq.im/user"), ImMapping.messengerToUri("ICQ", "user")) + assertEquals(URI("irc:/network/user"), ImMapping.messengerToUri("IRC", "/network/user")) + assertEquals(URI("mqq://im/chat?uin=user"), ImMapping.messengerToUri("QQ", "user")) + assertEquals(URI("skype:user"), ImMapping.messengerToUri("Skype", "user")) + assertEquals( + URI("https://threema.id/THREEMA_ID"), + ImMapping.messengerToUri("Threema", "THREEMA_ID") + ) + assertEquals( + URI("xmpp:user@example.com"), + ImMapping.messengerToUri("XMPP", "user@example.com") + ) + } + + @Test + fun testMessengerToUri_Null() { + assertEquals(URI("user@example.com"), ImMapping.messengerToUri(null, "user@example.com")) + } + + @Test + fun testMessengerToUri_Unknown() { + assertEquals( + URI("unknown-massenger:user@example.com"), + ImMapping.messengerToUri("Unknown Mässenger", "user@example.com") + ) + } + + + @Test + fun testUriToMessenger_Blank() { + assertEquals( + Pair(null, ""), + ImMapping.uriToMessenger(URI("")) + ) + } + + @Test + fun testUriToMessenger_Known() { + assertEquals( + Pair("AIM", "user"), + ImMapping.uriToMessenger(URI("aim:user")) + ) + assertEquals( + Pair("facebook", "user"), + ImMapping.uriToMessenger(URI("https://facebook.com/user")) + ) + assertEquals( + Pair("facebook", "user@example.com"), + ImMapping.uriToMessenger(URI("xmpp:user@example.com"), "Facebook") + ) + assertEquals( + Pair("facebook", "user"), + ImMapping.uriToMessenger(URI("xmpp:user@facebook.com"), "Facebook") + ) + assertEquals( + Pair("ICQ", "user"), + ImMapping.uriToMessenger(URI("icq:user")) + ) + assertEquals( + Pair("ICQ", "user"), + ImMapping.uriToMessenger(URI("https://icq.im/user")) + ) + assertEquals( + Pair("IRC", "freenode/user,isnick"), + ImMapping.uriToMessenger(URI("irc:freenode/user,isnick")) + ) + assertEquals( + Pair("QQ", "user"), + ImMapping.uriToMessenger(URI("mqq://im/chat?chat_type=wpa&uin=user")) + ) + assertEquals( + Pair("QQ", "user"), + ImMapping.uriToMessenger(URI("mqq:user")) + ) + assertEquals( + Pair("QQ", "user"), + ImMapping.uriToMessenger(URI("qq:user")) + ) + assertEquals( + Pair("Skype", "user"), + ImMapping.uriToMessenger(URI("skype:user")) + ) + assertEquals( + Pair("Threema", "THREEMA_ID"), + ImMapping.uriToMessenger(URI("https://threema.id/THREEMA_ID")) + ) + assertEquals( + Pair("XMPP", "user@example.com"), + ImMapping.uriToMessenger(URI("xmpp:user@example.com")) + ) + } + + @Test + fun testUriToMessenger_RelativeUri() { + assertEquals( + Pair(null, "relative/uri@example.com"), + ImMapping.uriToMessenger(URI("relative/uri@example.com")) + ) + } + + @Test + fun testUriToMessenger_RelativeUri_WithType() { + assertEquals( + Pair("MyMessenger", "uri@example.com"), + ImMapping.uriToMessenger(URI("uri@example.com"), "MyMessenger") + ) + } + + @Test + fun testUriToMessenger_Unknown() { + assertEquals( + Pair("Unknown", "write?uid=test"), + ImMapping.uriToMessenger(URI("unknown:write?uid=test")) + ) + } + +} \ No newline at end of file diff --git a/src/main/java/at/bitfire/vcard4android/contactrow/ImBuilder.kt b/src/main/java/at/bitfire/vcard4android/contactrow/ImBuilder.kt index 0cc9ef2..4092261 100644 --- a/src/main/java/at/bitfire/vcard4android/contactrow/ImBuilder.kt +++ b/src/main/java/at/bitfire/vcard4android/contactrow/ImBuilder.kt @@ -12,7 +12,6 @@ import at.bitfire.vcard4android.BatchOperation import at.bitfire.vcard4android.Contact import at.bitfire.vcard4android.property.CustomType import ezvcard.parameter.ImppType -import org.apache.commons.lang3.StringUtils import java.util.* class ImBuilder(dataRowUri: Uri, rawContactId: Long?, contact: Contact) @@ -23,8 +22,9 @@ class ImBuilder(dataRowUri: Uri, rawContactId: Long?, contact: Contact) for (labeledIm in contact.impps) { val impp = labeledIm.property - var protocol = impp.protocol ?: "" - var user = impp.handle + if ((impp.uri.scheme == null && impp.uri.schemeSpecificPart == "") || // empty URI + ImMapping.SCHEME_SIP.equals(impp.uri.scheme, true)) // handled by SipAddressBuilder + continue var typeCode = Im.TYPE_OTHER var typeLabel: String? = null @@ -41,71 +41,34 @@ class ImBuilder(dataRowUri: Uri, rawContactId: Long?, contact: Contact) } var protocolCode = Im.PROTOCOL_CUSTOM - var customProtocol: String? = null + + // We parse SERVICE-TYPE (for instance used by iCloud), but don't use it actively. + val serviceType = impp.getParameter(CustomType.Im.PARAMETER_SERVICE_TYPE) + ?: impp.getParameter(CustomType.Im.PARAMETER_SERVICE_TYPE_ALT) + ?: impp.getParameter("TYPE") // look for known messengers - ImMapping.uriToMessenger(impp.uri)?.let { (messenger, handle) -> + val customProtocol: String? + val user: String + ImMapping.uriToMessenger(impp.uri, serviceType).let { (messenger, handle) -> customProtocol = messenger user = handle } - if (customProtocol == null) { - // TODO move this code to ImMapping.uriToMessenger - - // We parse SERVICE-TYPE (for instance used by iCloud), but don't use it actively. - val serviceType = - impp.getParameter(CustomType.Im.PARAMETER_SERVICE_TYPE) - ?: impp.getParameter(CustomType.Im.PARAMETER_SERVICE_TYPE_ALT) - - customProtocol = // protocol name shown in Android - serviceType?.let { StringUtils.capitalize(it) } // use service type, if available - ?: StringUtils.capitalize(protocol) // fall back to raw URI scheme - } - if (Build.VERSION.SDK_INT < 31) { - // Since API level 31, PROTOCOL_XXX values are deprecated and only PROTOCOL_CUSTOM should be used. + /* On Android <12, we assign specific protocols like AIM etc. although most of them are not used anymore. + It's impossible to keep an up-to-date table of messengers, which is probably the reason why these + constants were deprecated in Android 12 (SDK level 31). */ @Suppress("DEPRECATION") when (customProtocol) { ImMapping.MESSENGER_AIM -> protocolCode = Im.PROTOCOL_AIM - // TODO + ImMapping.MESSENGER_ICQ -> protocolCode = Im.PROTOCOL_ICQ + ImMapping.MESSENGER_SKYPE -> protocolCode = Im.PROTOCOL_SKYPE + ImMapping.MESSENGER_QQ -> protocolCode = Im.PROTOCOL_QQ + ImMapping.MESSENGER_XMPP -> protocolCode = Im.PROTOCOL_JABBER } } - /*if (Build.VERSION.SDK_INT >= 31) { - - } else { - /* On Android <12, we assign specific protocols like AIM etc. although most of them are not used anymore. - It's impossible to keep an up-to-date table of messengers, which is probably the reason why these - constants were deprecated. */ - @Suppress("DEPRECATION") - when { - protocol.equals(CustomType.Im.PROTOCOL_AIM, true) -> - protocolCode = Im.PROTOCOL_AIM - protocol.equals(CustomType.Im.PROTOCOL_GOOGLE_TALK, true) || - protocol.equals(CustomType.Im.PROTOCOL_GOOGLE_TALK_ALT, true) -> - protocolCode = Im.PROTOCOL_GOOGLE_TALK - protocol.equals(CustomType.Im.PROTOCOL_ICQ, true) -> - protocolCode = Im.PROTOCOL_ICQ - protocol.equals(CustomType.Im.PROTOCOL_XMPP, true) -> - protocolCode = Im.PROTOCOL_JABBER - protocol.equals(CustomType.Im.PROTOCOL_MSN, true) || - protocol.equals(CustomType.Im.PROTOCOL_MSN_ALT, true) -> - protocolCode = Im.PROTOCOL_MSN - protocol.equals(CustomType.Im.PROTOCOL_QQ, true) || - protocol.equals(CustomType.Im.PROTOCOL_QQ_ALT, true) -> - protocolCode = Im.PROTOCOL_QQ - protocol.equals(CustomType.Im.PROTOCOL_CALLTO, true) || // includes NetMeeting, which is dead - protocol.equals(CustomType.Im.PROTOCOL_SKYPE, true) -> - protocolCode = Im.PROTOCOL_SKYPE - protocol.equals(CustomType.Im.PROTOCOL_YAHOO, true) -> - protocolCode = Im.PROTOCOL_YAHOO - - protocol.equals(CustomType.Im.PROTOCOL_SIP, true) -> - // IMPP:sip:… is handled by SipAddressBuilder - continue - } - }*/ - // save as IM address result += newDataRow() .withValue(Im.DATA, user) diff --git a/src/main/java/at/bitfire/vcard4android/contactrow/ImHandler.kt b/src/main/java/at/bitfire/vcard4android/contactrow/ImHandler.kt index afcaf7c..18b86a9 100644 --- a/src/main/java/at/bitfire/vcard4android/contactrow/ImHandler.kt +++ b/src/main/java/at/bitfire/vcard4android/contactrow/ImHandler.kt @@ -10,12 +10,9 @@ import android.provider.ContactsContract.CommonDataKinds.Im import at.bitfire.vcard4android.Constants import at.bitfire.vcard4android.Contact import at.bitfire.vcard4android.LabeledProperty -import at.bitfire.vcard4android.Utils.normalizeNFD import at.bitfire.vcard4android.property.CustomType import ezvcard.parameter.ImppType import ezvcard.property.Impp -import org.apache.commons.lang3.StringUtils -import java.net.URI import java.util.logging.Level object ImHandler: DataRowHandler() { @@ -33,34 +30,24 @@ object ImHandler: DataRowHandler() { } val protocolCode = values.getAsInteger(Im.PROTOCOL) - val impp = when (protocolCode) { - Im.PROTOCOL_AIM -> - Impp.aim(handle) - Im.PROTOCOL_MSN -> - Impp.msn(handle) - Im.PROTOCOL_SKYPE -> - Impp.skype(handle) - Im.PROTOCOL_GOOGLE_TALK -> - Impp(CustomType.Im.PROTOCOL_GOOGLE_TALK, handle) - Im.PROTOCOL_ICQ -> - Impp.icq(handle) - Im.PROTOCOL_JABBER -> - Impp.xmpp(handle) - Im.PROTOCOL_NETMEETING -> - Impp.skype(handle) // NetMeeting is dead and has most likely been replaced by Skype - Im.PROTOCOL_QQ -> - Impp(CustomType.Im.PROTOCOL_QQ, handle) - Im.PROTOCOL_YAHOO -> - Impp.yahoo(handle) - Im.PROTOCOL_CUSTOM -> { - val customProtocol = values.getAsString(Im.CUSTOM_PROTOCOL) - Impp(ImMapping.messengerToUri(customProtocol, handle)) - } + val messenger = when (protocolCode) { + Im.PROTOCOL_AIM -> ImMapping.MESSENGER_AIM + Im.PROTOCOL_MSN, + Im.PROTOCOL_SKYPE -> ImMapping.MESSENGER_SKYPE + Im.PROTOCOL_GOOGLE_TALK -> "GoogleTalk" // dead + Im.PROTOCOL_ICQ -> ImMapping.MESSENGER_ICQ + Im.PROTOCOL_JABBER -> ImMapping.MESSENGER_XMPP + Im.PROTOCOL_NETMEETING -> "NetMeeting" // dead + Im.PROTOCOL_QQ -> ImMapping.MESSENGER_QQ + Im.PROTOCOL_YAHOO -> "Yahoo" // dead + Im.PROTOCOL_CUSTOM -> + values.getAsString(Im.CUSTOM_PROTOCOL) else -> { Constants.log.log(Level.WARNING, "Unknown IM protocol: $protocolCode") return } } + val impp = Impp(ImMapping.messengerToUri(messenger, handle)) val labeledImpp = LabeledProperty(impp) when (values.getAsInteger(Im.TYPE)) { diff --git a/src/main/java/at/bitfire/vcard4android/contactrow/ImMapping.kt b/src/main/java/at/bitfire/vcard4android/contactrow/ImMapping.kt index a14177e..aff4296 100644 --- a/src/main/java/at/bitfire/vcard4android/contactrow/ImMapping.kt +++ b/src/main/java/at/bitfire/vcard4android/contactrow/ImMapping.kt @@ -4,8 +4,10 @@ package at.bitfire.vcard4android.contactrow +import android.net.Uri import at.bitfire.vcard4android.Constants import at.bitfire.vcard4android.Utils.normalizeNFD +import org.apache.commons.lang3.StringUtils import java.net.URI import java.net.URISyntaxException import java.util.logging.Level @@ -13,18 +15,39 @@ import java.util.logging.Level object ImMapping { const val MESSENGER_AIM = "AIM" + const val MESSENGER_FACEBOOK = "facebook" + const val MESSENGER_ICQ = "ICQ" const val MESSENGER_IRC = "IRC" + const val MESSENGER_QQ = "QQ" + const val MESSENGER_SKYPE = "Skype" + const val MESSENGER_TELEGRAM = "Telegram" const val MESSENGER_THREEMA = "Threema" + const val MESSENGER_XMPP = "XMPP" - // TODO Tests + const val SCHEME_AIM = "aim" + const val SCHEME_ICQ = "icq" + const val SCHEME_GOOGLE_TALK = "gtalk" + const val SCHEME_HTTPS = "https" + const val SCHEME_IRC = "irc" + const val SCHEME_QQ = "mqq" + const val SCHEME_SIP = "sip" + const val SCHEME_SKYPE = "skype" + const val SCHEME_XMPP = "xmpp" - fun messengerToUri(messenger: String, handle: String): URI? = + fun messengerToUri(messenger: String?, handle: String): URI? = try { - when (messenger.lowercase()) { - MESSENGER_AIM.lowercase() -> URI("aim", handle, null) - MESSENGER_IRC.lowercase() -> URI("irc", handle, null) - MESSENGER_THREEMA.lowercase() -> URI("https", "threema.id", "/${handle}", null) + when (messenger?.lowercase()) { + null -> URI(null, handle, null) + MESSENGER_AIM.lowercase() -> URI(SCHEME_AIM, handle, null) + MESSENGER_FACEBOOK.lowercase() -> URI(SCHEME_HTTPS, "facebook.com", "/${handle}", null) + MESSENGER_ICQ.lowercase() -> URI(SCHEME_HTTPS, "icq.im", "/${handle}", null) + MESSENGER_IRC.lowercase() -> URI(SCHEME_IRC, handle, null) + MESSENGER_QQ.lowercase() -> URI(SCHEME_QQ, "im", "/chat", "uin=${handle}", null) + MESSENGER_SKYPE.lowercase() -> URI(SCHEME_SKYPE, handle, null) + MESSENGER_TELEGRAM.lowercase() -> URI(SCHEME_HTTPS, "t.me", "/${handle}", null) + MESSENGER_THREEMA.lowercase() -> URI(SCHEME_HTTPS, "threema.id", "/${handle}", null) + MESSENGER_XMPP.lowercase() -> URI(SCHEME_XMPP, handle, null) else -> // fallback for unknown messengers URI(messengerToUriScheme(messenger), handle, null) @@ -34,28 +57,70 @@ object ImMapping { null } - fun uriToMessenger(uri: URI): Pair? = + fun uriToMessenger(uri: URI, serviceType: String? = null): Pair = when { - uri.scheme.equals("aim", true) -> + SCHEME_AIM.equals(uri.scheme, true) -> Pair(MESSENGER_AIM, uri.schemeSpecificPart) - uri.scheme.equals("irc", true) -> + SCHEME_ICQ.equals(uri.scheme, true) -> + Pair(MESSENGER_ICQ, uri.schemeSpecificPart) + SCHEME_IRC.equals(uri.scheme, true) -> Pair(MESSENGER_IRC, uri.schemeSpecificPart) - uri.authority.equals("threema.id", true) -> + SCHEME_QQ.equals(uri.scheme, true) || + "qq".equals(uri.scheme, true) -> { + val uri2 = Uri.parse(uri.toString()) + val uin = + try { + uri2.getQueryParameter("uin") + } catch (e: UnsupportedOperationException) { + null + } + if (uin != null) + Pair(MESSENGER_QQ, uin) + else + Pair(MESSENGER_QQ, uri.schemeSpecificPart) + } + SCHEME_SKYPE.equals(uri.scheme, true) -> + Pair(MESSENGER_SKYPE, uri.schemeSpecificPart) + SCHEME_XMPP.equals(uri.scheme, true) -> + when (serviceType?.lowercase()) { + "facebook" -> Pair(MESSENGER_FACEBOOK, uri.schemeSpecificPart.removeSuffix("@facebook.com")) + else -> + Pair(MESSENGER_XMPP, uri.schemeSpecificPart) + } + + "facebook.com".equals(uri.authority, true) -> + Pair(MESSENGER_FACEBOOK, uri.path.trimStart('/')) + "icq.im".equals(uri.authority, true) -> + Pair(MESSENGER_ICQ, uri.path.trimStart('/')) + "t.me".equals(uri.authority, true) -> + Pair(MESSENGER_TELEGRAM, uri.path.trimStart('/')) + "threema.id".equals(uri.authority, true) -> Pair(MESSENGER_THREEMA, uri.path.trimStart('/')) - else -> null + + else -> { + // fallback for unknown messengers + val messenger: String? = + serviceType?.let { StringUtils.capitalize(it) } ?: // use service type, if available + StringUtils.capitalize(uri.scheme) // otherwise, use the scheme itself + Pair(messenger, uri.schemeSpecificPart) + } } - fun messengerToUriScheme(s: String?) = s - ?.normalizeNFD() // normalize with decomposition first (e.g. Á → A+ ́) + fun messengerToUriScheme(s: String?): String? { + val reduced = s + ?.normalizeNFD() // normalize with decomposition first (e.g. Á → A+ ́) - /* then filter according to RFC 3986 3.1: - scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - DIGIT = %x30-39 ; 0-9 - */ - ?.replace(Regex("^[^a-zA-Z]+"), "") - ?.replace(Regex("[^\\da-zA-Z+-.]"), "") - ?.lowercase() + /* then filter according to RFC 3986 3.1: + scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + DIGIT = %x30-39 ; 0-9 + */ + ?.replace(' ', '-') + ?.replace(Regex("^[^a-zA-Z]+"), "") + ?.replace(Regex("[^\\da-zA-Z+-.]"), "") + ?.lowercase() + return StringUtils.stripToNull(reduced) + } } \ No newline at end of file diff --git a/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressBuilder.kt b/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressBuilder.kt index cecfd56..9f47c53 100644 --- a/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressBuilder.kt +++ b/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressBuilder.kt @@ -20,7 +20,7 @@ class SipAddressBuilder(dataRowUri: Uri, rawContactId: Long?, contact: Contact) val impp = labeledIm.property val protocol = impp.protocol - if (protocol != "sip") + if (!ImMapping.SCHEME_SIP.equals(protocol, true)) // other protocols are handled by ImBuilder continue diff --git a/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressHandler.kt b/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressHandler.kt index d3e0469..4187203 100644 --- a/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressHandler.kt +++ b/src/main/java/at/bitfire/vcard4android/contactrow/SipAddressHandler.kt @@ -21,7 +21,7 @@ object SipAddressHandler: DataRowHandler() { val sip = values.getAsString(SipAddress.SIP_ADDRESS) ?: return try { - val impp = Impp("sip:$sip") + val impp = Impp("${ImMapping.SCHEME_SIP}:$sip") val labeledImpp = LabeledProperty(impp) when (values.getAsInteger(SipAddress.TYPE)) { diff --git a/src/main/java/at/bitfire/vcard4android/property/CustomType.kt b/src/main/java/at/bitfire/vcard4android/property/CustomType.kt index 9eb1aeb..15fd779 100644 --- a/src/main/java/at/bitfire/vcard4android/property/CustomType.kt +++ b/src/main/java/at/bitfire/vcard4android/property/CustomType.kt @@ -21,25 +21,7 @@ object CustomType { } object Im { - // [RFC 4770 and other sources] - const val PROTOCOL_AIM = "aim" - const val PROTOCOL_ICQ = "icq" - const val PROTOCOL_MSN = "msn" - const val PROTOCOL_MSN_ALT = "msnim" - const val PROTOCOL_SIP = "sip" - const val PROTOCOL_SKYPE = "skype" - const val PROTOCOL_XMPP = "xmpp" - const val PROTOCOL_YAHOO = "ymsgr" - - // own values - const val PROTOCOL_CALLTO = "callto" // [https://en.wikipedia.org/wiki/List_of_URI_schemes] - const val PROTOCOL_GOOGLE_TALK = "gtalk" // [https://en.wikipedia.org/wiki/List_of_URI_schemes] - const val PROTOCOL_GOOGLE_TALK_ALT = "google-talk" // DAVx⁵ <4.2 - const val PROTOCOL_NETMEETING = "netmeeting" // DAVx⁵ <4.2 - const val PROTOCOL_QQ = "mqq" // [https://developpaper.com/common-url-scheme/] - const val PROTOCOL_QQ_ALT = "qq" // DAVx⁵ <4.2 - - // [https://datatracker.ietf.org/doc/html/draft-daboo-vcard-service-type] + // https://datatracker.ietf.org/doc/html/draft-daboo-vcard-service-type const val PARAMETER_SERVICE_TYPE = "X-SERVICE-TYPE" const val PARAMETER_SERVICE_TYPE_ALT = "SERVICE-TYPE" } -- cgit v1.2.3