rewriting contact exporting/importing, rely on vcard at parsing

This commit is contained in:
tibbi 2018-08-23 23:23:45 +02:00
parent 389d6e9266
commit c0720e3e73
10 changed files with 215 additions and 439 deletions

View file

@ -49,6 +49,7 @@ dependencies {
implementation 'joda-time:joda-time:2.9.9'
implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
compile 'com.googlecode.ez-vcard:ez-vcard:0.10.4'
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"

View file

@ -4,7 +4,6 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.provider.ContactsContract
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
@ -17,7 +16,6 @@ import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.getColoredBitmap
import com.simplemobiletools.commons.extensions.getContrastColor
import com.simplemobiletools.commons.helpers.getDateFormats
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.extensions.config
@ -26,10 +24,6 @@ import com.simplemobiletools.contacts.extensions.sendSMSIntent
import com.simplemobiletools.contacts.extensions.shareContacts
import com.simplemobiletools.contacts.helpers.ContactsHelper
import com.simplemobiletools.contacts.models.Contact
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
abstract class ContactActivity : SimpleActivity() {
@ -68,31 +62,6 @@ abstract class ContactActivity : SimpleActivity() {
}).into(photoView)
}
fun getDateTime(dateString: String, viewToUpdate: TextView? = null): DateTime {
val dateFormats = getDateFormats()
var date = DateTime()
for (format in dateFormats) {
try {
date = DateTime.parse(dateString, DateTimeFormat.forPattern(format))
val formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())
var localPattern = (formatter as SimpleDateFormat).toLocalizedPattern()
val hasYear = format.contains("y")
if (!hasYear) {
localPattern = localPattern.replace("y", "").trim()
date = date.withYear(DateTime().year)
}
val formattedString = date.toString(localPattern)
viewToUpdate?.text = formattedString
break
} catch (ignored: Exception) {
}
}
return date
}
fun deleteContact() {
ConfirmationDialog(this) {
if (contact != null) {

View file

@ -427,7 +427,7 @@ class EditContactActivity : ContactActivity() {
(eventHolder as ViewGroup).apply {
val contactEvent = contact_event.apply {
getDateTime(event.value, this)
event.value.getDateTimeFromDateString(this)
tag = event.value
alpha = 1f
}
@ -595,7 +595,7 @@ class EditContactActivity : ContactActivity() {
}
}
val date = getDateTime(eventField.tag?.toString() ?: "")
val date = (eventField.tag?.toString() ?: "").getDateTimeFromDateString()
DatePickerDialog(this, getDialogTheme(), setDateListener, date.year, date.monthOfYear - 1, date.dayOfMonth).show()
}

View file

@ -73,8 +73,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
} else {
checkContactPermissions()
}
}
storeStateVariables()
checkWhatsNewDialog()
}

View file

@ -293,7 +293,7 @@ class ViewContactActivity : ContactActivity() {
layoutInflater.inflate(R.layout.item_event, contact_events_holder, false).apply {
contact_events_holder.addView(this)
contact_event.alpha = 1f
getDateTime(it.value, contact_event)
it.value.getDateTimeFromDateString(contact_event)
contact_event_type.setText(getEventTextId(it.type))
contact_event_remove.beGone()
}

View file

@ -0,0 +1,34 @@
package com.simplemobiletools.contacts.extensions
import android.widget.TextView
import com.simplemobiletools.commons.helpers.getDateFormats
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
fun String.getDateTimeFromDateString(viewToUpdate: TextView? = null): DateTime {
val dateFormats = getDateFormats()
var date = DateTime()
for (format in dateFormats) {
try {
date = DateTime.parse(this, DateTimeFormat.forPattern(format))
val formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())
var localPattern = (formatter as SimpleDateFormat).toLocalizedPattern()
val hasYear = format.contains("y")
if (!hasYear) {
localPattern = localPattern.replace("y", "").trim()
date = date.withYear(DateTime().year)
}
val formattedString = date.toString(localPattern)
viewToUpdate?.text = formattedString
break
} catch (ignored: Exception) {
}
}
return date
}

View file

@ -52,26 +52,6 @@ const val PHOTO_REMOVED = 2
const val PHOTO_CHANGED = 3
const val PHOTO_UNCHANGED = 4
// export/import
const val BEGIN_VCARD = "BEGIN:VCARD"
const val END_VCARD = "END:VCARD"
const val N = "N"
const val NICKNAME = "NICKNAME"
const val TEL = "TEL"
const val BDAY = "BDAY:"
const val ANNIVERSARY = "ANNIVERSARY:"
const val PHOTO = "PHOTO"
const val EMAIL = "EMAIL"
const val ADR = "ADR"
const val NOTE = "NOTE"
const val ORG = "ORG"
const val TITLE = "TITLE"
const val URL = "URL"
const val ENCODING = "ENCODING"
const val BASE64 = "BASE64"
const val JPEG = "JPEG"
const val VERSION_2_1 = "VERSION:2.1"
// phone number/email types
const val CELL = "CELL"
const val WORK = "WORK"
@ -83,7 +63,6 @@ const val WORK_FAX = "WORK;FAX"
const val HOME_FAX = "HOME;FAX"
const val PAGER = "PAGER"
const val MOBILE = "MOBILE"
const val VOICE = "VOICE"
const val ON_CLICK_CALL_CONTACT = 1
const val ON_CLICK_VIEW_CONTACT = 2

View file

@ -1,22 +1,30 @@
package com.simplemobiletools.contacts.helpers
import android.graphics.Bitmap
import android.net.Uri
import android.provider.ContactsContract.CommonDataKinds
import android.provider.MediaStore
import android.util.Base64
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.extensions.getFileOutputStream
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toFileDirItem
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.extensions.getByteArray
import com.simplemobiletools.contacts.extensions.getDateTimeFromDateString
import com.simplemobiletools.contacts.helpers.VcfExporter.ExportResult.EXPORT_FAIL
import com.simplemobiletools.contacts.models.Contact
import java.io.BufferedWriter
import java.io.ByteArrayOutputStream
import ezvcard.Ezvcard
import ezvcard.VCard
import ezvcard.parameter.AddressType
import ezvcard.parameter.EmailType
import ezvcard.parameter.ImageType
import ezvcard.parameter.TelephoneType
import ezvcard.property.*
import ezvcard.util.PartialDate
import java.io.File
import java.util.*
class VcfExporter {
private val ENCODED_PHOTO_LINE_LENGTH = 74
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL
}
@ -36,65 +44,93 @@ class VcfExporter {
activity.toast(R.string.exporting)
}
it.bufferedWriter().use { out ->
for (contact in contacts) {
out.writeLn(BEGIN_VCARD)
out.writeLn(VERSION_2_1)
out.writeLn("$N${getNames(contact)}")
val cards = ArrayList<VCard>()
for (contact in contacts) {
val card = VCard()
StructuredName().apply {
prefixes.add(contact.prefix)
given = contact.firstName
additionalNames.add(contact.middleName)
family = contact.surname
suffixes.add(contact.suffix)
card.structuredName = this
}
if (contact.nickname.isNotEmpty()) {
out.writeLn("$NICKNAME:${contact.nickname}")
}
if (contact.nickname.isNotEmpty()) {
card.setNickname(contact.nickname)
}
contact.phoneNumbers.forEach {
out.writeLn("$TEL;${getPhoneNumberLabel(it.type)}:${it.value}")
}
contact.phoneNumbers.forEach {
val phoneNumber = Telephone(it.value)
phoneNumber.types.add(TelephoneType.find(getPhoneNumberLabel(it.type)))
card.addTelephoneNumber(phoneNumber)
}
contact.emails.forEach {
val type = getEmailTypeLabel(it.type)
val delimiterType = if (type.isEmpty()) "" else ";$type"
out.writeLn("$EMAIL$delimiterType:${it.value}")
}
contact.emails.forEach {
val email = Email(it.value)
email.types.add(EmailType.find(getEmailTypeLabel(it.type)))
card.addEmail(email)
}
contact.addresses.forEach {
val type = getAddressTypeLabel(it.type)
val delimiterType = if (type.isEmpty()) "" else ";$type"
out.writeLn("$ADR$delimiterType:;;${it.value.replace("\n", "\\n")};;;;")
}
contact.events.forEach {
if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
out.writeLn("$BDAY${it.value}")
contact.events.forEach {
if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY || it.type == CommonDataKinds.Event.TYPE_ANNIVERSARY) {
val dateTime = it.value.getDateTimeFromDateString()
if (it.value.startsWith("--")) {
val partialDate = PartialDate.builder().year(null).month(dateTime.monthOfYear - 1).date(dateTime.dayOfMonth).build()
if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
card.birthdays.add(Birthday(partialDate))
} else {
card.anniversaries.add(Anniversary(partialDate))
}
} else {
Calendar.getInstance().apply {
clear()
set(Calendar.YEAR, dateTime.year)
set(Calendar.MONTH, dateTime.monthOfYear - 1)
set(Calendar.DAY_OF_MONTH, dateTime.dayOfMonth)
if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
card.birthdays.add(Birthday(time))
} else {
card.anniversaries.add(Anniversary(time))
}
}
}
}
if (contact.notes.isNotEmpty()) {
out.writeLn("$NOTE:${contact.notes.replace("\n", "\\n")}")
}
if (!contact.organization.isEmpty()) {
out.writeLn("$ORG:${contact.organization.company.replace("\n", "\\n")}")
out.writeLn("$TITLE:${contact.organization.jobPosition.replace("\n", "\\n")}")
}
contact.websites.forEach {
out.writeLn("$URL:$it")
}
if (contact.thumbnailUri.isNotEmpty()) {
val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri))
addBitmap(bitmap, out)
}
if (contact.photo != null) {
addBitmap(contact.photo!!, out)
}
out.writeLn(END_VCARD)
contactsExported++
}
contact.addresses.forEach {
val address = Address()
address.streetAddress = it.value
address.types.add(AddressType.find(getAddressTypeLabel(it.type)))
card.addAddress(address)
}
if (contact.notes.isNotEmpty()) {
card.addNote(contact.notes)
}
if (!contact.organization.isEmpty()) {
val organization = Organization()
organization.values.add(contact.organization.company)
card.organization = organization
card.titles.add(Title(contact.organization.jobPosition))
}
contact.websites.forEach {
card.addUrl(it)
}
if (contact.thumbnailUri.isNotEmpty()) {
val photoByteArray = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri)).getByteArray()
val photo = Photo(photoByteArray, ImageType.JPEG)
card.addPhoto(photo)
}
cards.add(card)
contactsExported++
}
Ezvcard.write(cards).go(file)
} catch (e: Exception) {
activity.showErrorToast(e)
}
@ -107,71 +143,24 @@ class VcfExporter {
}
}
private fun addBitmap(bitmap: Bitmap, out: BufferedWriter) {
val firstLine = "$PHOTO;$ENCODING=$BASE64;$JPEG:"
val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, byteArrayOutputStream)
bitmap.recycle()
val byteArray = byteArrayOutputStream.toByteArray()
val encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP)
val encodedFirstLineSection = encoded.substring(0, ENCODED_PHOTO_LINE_LENGTH - firstLine.length)
out.writeLn(firstLine + encodedFirstLineSection)
var curStartIndex = encodedFirstLineSection.length
do {
val part = encoded.substring(curStartIndex, Math.min(curStartIndex + ENCODED_PHOTO_LINE_LENGTH - 1, encoded.length))
out.writeLn(" $part")
curStartIndex += ENCODED_PHOTO_LINE_LENGTH - 1
} while (curStartIndex < encoded.length)
out.writeLn("")
}
private fun getNames(contact: Contact): String {
var result = ""
var firstName = contact.firstName
var surName = contact.surname
var middleName = contact.middleName
var prefix = contact.prefix
var suffix = contact.suffix
if (QuotedPrintable.urlEncode(firstName) != firstName
|| QuotedPrintable.urlEncode(surName) != surName
|| QuotedPrintable.urlEncode(middleName) != middleName
|| QuotedPrintable.urlEncode(prefix) != prefix
|| QuotedPrintable.urlEncode(suffix) != suffix) {
firstName = QuotedPrintable.encode(firstName)
surName = QuotedPrintable.encode(surName)
middleName = QuotedPrintable.encode(middleName)
prefix = QuotedPrintable.encode(prefix)
suffix = QuotedPrintable.encode(suffix)
result += ";CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE"
}
return "$result:$surName;$firstName;$middleName;$prefix;$suffix"
}
private fun getPhoneNumberLabel(type: Int) = when (type) {
CommonDataKinds.Phone.TYPE_MOBILE -> CELL
CommonDataKinds.Phone.TYPE_HOME -> HOME
CommonDataKinds.Phone.TYPE_WORK -> WORK
CommonDataKinds.Phone.TYPE_MAIN -> PREF
CommonDataKinds.Phone.TYPE_FAX_WORK -> WORK_FAX
CommonDataKinds.Phone.TYPE_FAX_HOME -> HOME_FAX
CommonDataKinds.Phone.TYPE_PAGER -> PAGER
else -> VOICE
else -> HOME
}
private fun getEmailTypeLabel(type: Int) = when (type) {
CommonDataKinds.Email.TYPE_HOME -> HOME
CommonDataKinds.Email.TYPE_WORK -> WORK
CommonDataKinds.Email.TYPE_MOBILE -> MOBILE
else -> ""
else -> HOME
}
private fun getAddressTypeLabel(type: Int) = when (type) {
CommonDataKinds.StructuredPostal.TYPE_HOME -> HOME
CommonDataKinds.StructuredPostal.TYPE_WORK -> WORK
else -> ""
else -> HOME
}
}

View file

@ -3,8 +3,6 @@ package com.simplemobiletools.contacts.helpers
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.provider.ContactsContract.CommonDataKinds
import android.text.TextUtils
import android.util.Base64
import android.widget.Toast
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.contacts.activities.SimpleActivity
@ -12,45 +10,19 @@ import com.simplemobiletools.contacts.extensions.getCachePhoto
import com.simplemobiletools.contacts.extensions.getCachePhotoUri
import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.*
import com.simplemobiletools.contacts.models.*
import ezvcard.Ezvcard
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.io.File
import java.io.FileOutputStream
import java.util.*
class VcfImporter(val activity: SimpleActivity) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL
}
private var curPrefix = ""
private var curFirstName = ""
private var curMiddleName = ""
private var curSurname = ""
private var curSuffix = ""
private var curNickname = ""
private var curPhotoUri = ""
private var curNotes = ""
private var curCompany = ""
private var curJobPosition = ""
private var curPhoneNumbers = ArrayList<PhoneNumber>()
private var curEmails = ArrayList<Email>()
private var curEvents = ArrayList<Event>()
private var curAddresses = ArrayList<Address>()
private var curGroups = ArrayList<Group>()
private var curWebsites = ArrayList<String>()
private var isGettingPhoto = false
private var currentPhotoString = StringBuilder()
private var currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG
private var isGettingName = false
private var currentNameIsANSI = false
private var currentNameString = StringBuilder()
private var isGettingNotes = false
private var currentNotesSB = StringBuilder()
private var isGettingCompany = false
private var currentCompanyIsANSI = false
private var currentCompany = StringBuilder()
private val PATTERN = "EEE MMM dd HH:mm:ss 'GMT'ZZ YYYY"
private var contactsImported = 0
private var contactsFailed = 0
@ -63,51 +35,70 @@ class VcfImporter(val activity: SimpleActivity) {
activity.assets.open(path)
}
inputStream.bufferedReader().use {
while (true) {
val originalLine = it.readLine() ?: break
val line = originalLine.trim()
if (line.isEmpty()) {
if (isGettingPhoto) {
savePhoto()
isGettingPhoto = false
}
continue
} else if (line.startsWith('\t') && isGettingName) {
currentNameString.append(line.trimStart('\t'))
isGettingName = false
parseNames()
} else if (isGettingNotes) {
if (originalLine.startsWith(' ')) {
currentNotesSB.append(line.substring(1))
} else {
curNotes = currentNotesSB.toString().replace("\\n", "\n").replace("\\,", ",")
isGettingNotes = false
}
} else if (isGettingCompany && currentCompanyIsANSI && line.startsWith("=")) {
currentCompany.append(line)
curCompany = QuotedPrintable.decode(currentCompany.toString().replace("==", "="))
continue
}
val ezContacts = Ezvcard.parse(inputStream).all()
for (ezContact in ezContacts) {
val structuredName = ezContact.structuredName
val prefix = structuredName?.prefixes?.firstOrNull() ?: ""
val firstName = structuredName?.given ?: ""
val middleName = structuredName?.additionalNames?.firstOrNull() ?: ""
val surname = structuredName?.family ?: ""
val suffix = structuredName?.suffixes?.firstOrNull() ?: ""
val nickname = ezContact.nickname?.values?.firstOrNull() ?: ""
val photoUri = ""
when {
line.toUpperCase() == BEGIN_VCARD -> resetValues()
line.toUpperCase().startsWith(NOTE) -> addNotes(line.substring(NOTE.length))
line.toUpperCase().startsWith(NICKNAME) -> addNickname(line.substring(NICKNAME.length))
line.toUpperCase().startsWith(N) -> addNames(line.substring(N.length))
line.toUpperCase().startsWith(TEL) -> addPhoneNumber(line.substring(TEL.length))
line.toUpperCase().startsWith(EMAIL) -> addEmail(line.substring(EMAIL.length))
line.toUpperCase().startsWith(ADR) -> addAddress(line.substring(ADR.length))
line.toUpperCase().startsWith(BDAY) -> addBirthday(line.substring(BDAY.length))
line.toUpperCase().startsWith(ANNIVERSARY) -> addAnniversary(line.substring(ANNIVERSARY.length))
line.toUpperCase().startsWith(PHOTO) -> addPhoto(line.substring(PHOTO.length))
line.toUpperCase().startsWith(ORG) -> addCompany(line.substring(ORG.length))
line.toUpperCase().startsWith(TITLE) -> addJobPosition(line.substring(TITLE.length))
line.toUpperCase().startsWith(URL) -> addWebsite(line.substring(URL.length))
line.toUpperCase() == END_VCARD -> saveContact(targetContactSource)
isGettingPhoto -> currentPhotoString.append(line.trim())
val phoneNumbers = ArrayList<PhoneNumber>()
ezContact.telephoneNumbers.forEach {
val type = getPhoneNumberTypeId(it.types.firstOrNull()?.value ?: MOBILE)
val number = it.text
phoneNumbers.add(PhoneNumber(number, type))
}
val emails = ArrayList<Email>()
ezContact.emails.forEach {
val type = getEmailTypeId(it.types.firstOrNull()?.value ?: HOME)
val email = it.value
emails.add(Email(email, type))
}
val addresses = ArrayList<Address>()
ezContact.addresses.forEach {
val type = getAddressTypeId(it.types.firstOrNull()?.value ?: HOME)
val address = it.streetAddress
if (address?.isNotEmpty() == true) {
addresses.add(Address(address, type))
}
}
val events = ArrayList<Event>()
ezContact.birthdays.forEach {
val event = Event(formatDateToDayCode(it.date), CommonDataKinds.Event.TYPE_BIRTHDAY)
events.add(event)
}
ezContact.anniversaries.forEach {
val event = Event(formatDateToDayCode(it.date), CommonDataKinds.Event.TYPE_ANNIVERSARY)
events.add(event)
}
val starred = 0
val contactId = 0
val notes = ezContact.notes.firstOrNull()?.value ?: ""
val groups = ArrayList<Group>()
val company = ezContact.organization?.values?.firstOrNull() ?: ""
val jobPosition = ezContact.titles?.firstOrNull()?.value ?: ""
val organization = Organization(company, jobPosition)
val websites = ezContact.urls.map { it.value } as ArrayList<String>
val photoData = ezContact.photos.firstOrNull()?.data
val photo = null
val thumbnailUri = savePhoto(photoData)
val contact = Contact(0, prefix, firstName, middleName, surname, suffix, nickname, photoUri, phoneNumbers, emails, addresses, events,
targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites)
if (ContactsHelper(activity).insertContact(contact)) {
contactsImported++
}
}
} catch (e: Exception) {
activity.showErrorToast(e, Toast.LENGTH_LONG)
@ -121,238 +112,51 @@ class VcfImporter(val activity: SimpleActivity) {
}
}
private fun addNames(names: String) {
val parts = names.split(":")
currentNameIsANSI = parts.first().toUpperCase().contains("QUOTED-PRINTABLE")
currentNameString.append(parts[1].trimEnd('='))
if (!isGettingName && currentNameIsANSI && names.endsWith('=')) {
isGettingName = true
} else {
if (names.contains(";")) {
parseNames()
} else if (names.startsWith(":")) {
curFirstName = names.substring(1)
}
}
private fun formatDateToDayCode(date: Date): String {
val dateTime = DateTime.parse(date.toString(), DateTimeFormat.forPattern(PATTERN))
return dateTime.toString("yyyy-MM-dd")
}
private fun parseNames() {
val nameParts = currentNameString.split(";")
curSurname = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[0]) else nameParts[0]
curFirstName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[1]) else nameParts[1]
if (nameParts.size > 2) {
curMiddleName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[2]) else nameParts[2]
curPrefix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[3]) else nameParts[3]
curSuffix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[4]) else nameParts[4]
}
}
private fun addNickname(nickname: String) {
curNickname = if (nickname.startsWith(";CHARSET", true)) {
nickname.substringAfter(":")
} else {
nickname.substring(1)
}
}
private fun addPhoneNumber(phoneNumber: String) {
val phoneParts = phoneNumber.trimStart(';').split(":")
var rawType = phoneParts[0]
var subType = ""
if (rawType.contains('=')) {
val types = rawType.split('=')
if (types.any { it.contains(';') }) {
subType = types[1].split(';')[0]
}
rawType = types.last()
}
val type = getPhoneNumberTypeId(rawType.toUpperCase(), subType)
val value = phoneParts[1]
curPhoneNumbers.add(PhoneNumber(value, type))
}
private fun getPhoneNumberTypeId(type: String, subType: String) = when (type) {
private fun getPhoneNumberTypeId(type: String) = when (type.toUpperCase()) {
CELL -> CommonDataKinds.Phone.TYPE_MOBILE
HOME -> CommonDataKinds.Phone.TYPE_HOME
WORK -> CommonDataKinds.Phone.TYPE_WORK
PREF, MAIN -> CommonDataKinds.Phone.TYPE_MAIN
WORK_FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK
HOME_FAX -> CommonDataKinds.Phone.TYPE_FAX_HOME
FAX -> if (subType == WORK) CommonDataKinds.Phone.TYPE_FAX_WORK else CommonDataKinds.Phone.TYPE_FAX_HOME
FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK
PAGER -> CommonDataKinds.Phone.TYPE_PAGER
else -> CommonDataKinds.Phone.TYPE_OTHER
}
private fun addEmail(email: String) {
val emailParts = email.trimStart(';').split(":")
var rawType = emailParts[0]
if (rawType.contains('=')) {
rawType = rawType.split('=').last()
}
val type = getEmailTypeId(rawType.toUpperCase())
val value = emailParts[1]
curEmails.add(Email(value, type))
}
private fun getEmailTypeId(type: String) = when (type) {
private fun getEmailTypeId(type: String) = when (type.toUpperCase()) {
HOME -> CommonDataKinds.Email.TYPE_HOME
WORK -> CommonDataKinds.Email.TYPE_WORK
MOBILE -> CommonDataKinds.Email.TYPE_MOBILE
else -> CommonDataKinds.Email.TYPE_OTHER
}
private fun addAddress(address: String) {
val addressParts = address.trimStart(';').split(":")
var rawType = addressParts[0]
if (rawType.contains('=')) {
rawType = rawType.split('=').last()
}
val type = getAddressTypeId(rawType.toUpperCase())
val addresses = addressParts[1].split(";")
if (addresses.size == 7) {
var parsedAddress = if (address.contains(";CHARSET=UTF-8:")) {
TextUtils.join(", ", addresses.filter { it.trim().isNotEmpty() })
} else {
addresses[2].replace("\\n", "\n")
}
if (address.contains("QUOTED-PRINTABLE")) {
parsedAddress = QuotedPrintable.decode(parsedAddress)
}
curAddresses.add(Address(parsedAddress, type))
}
}
private fun getAddressTypeId(type: String) = when (type) {
private fun getAddressTypeId(type: String) = when (type.toUpperCase()) {
HOME -> CommonDataKinds.Email.TYPE_HOME
WORK -> CommonDataKinds.Email.TYPE_WORK
else -> CommonDataKinds.Email.TYPE_OTHER
}
private fun addBirthday(birthday: String) {
curEvents.add(Event(birthday, CommonDataKinds.Event.TYPE_BIRTHDAY))
}
private fun addAnniversary(anniversary: String) {
curEvents.add(Event(anniversary, CommonDataKinds.Event.TYPE_ANNIVERSARY))
}
private fun addPhoto(photo: String) {
val photoParts = photo.trimStart(';').split(';')
if (photoParts.size == 2) {
val typeParts = photoParts[1].split(':')
currentPhotoCompressionFormat = getPhotoCompressionFormat(typeParts[0])
val encoding = photoParts[0].split('=').last()
if (encoding == BASE64) {
isGettingPhoto = true
currentPhotoString.append(typeParts[1].trim())
}
private fun savePhoto(byteArray: ByteArray?): String {
if (byteArray == null) {
return ""
}
}
private fun getPhotoCompressionFormat(type: String) = when (type.toLowerCase()) {
"png" -> Bitmap.CompressFormat.PNG
"webp" -> Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.JPEG
}
private fun savePhoto() {
val file = activity.getCachePhoto()
val imageAsBytes = Base64.decode(currentPhotoString.toString().toByteArray(), Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.size)
val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(file)
bitmap.compress(currentPhotoCompressionFormat, 100, fileOutputStream)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream)
} finally {
fileOutputStream?.close()
}
curPhotoUri = activity.getCachePhotoUri(file).toString()
}
private fun addNotes(notes: String) {
if (notes.startsWith(";CHARSET", true)) {
currentNotesSB.append(notes.substringAfter(":"))
} else {
currentNotesSB.append(notes.substring(1))
}
isGettingNotes = true
}
private fun addCompany(company: String) {
curCompany = if (company.startsWith(";")) {
company.substringAfter(":").trim(';')
} else {
company.trimStart(':')
}
currentCompanyIsANSI = company.toUpperCase().contains("QUOTED-PRINTABLE")
currentCompany.append(curCompany)
isGettingCompany = true
}
private fun addJobPosition(jobPosition: String) {
curJobPosition = if (jobPosition.startsWith(";")) {
jobPosition.substringAfter(":")
} else {
jobPosition.trimStart(':')
}
}
private fun addWebsite(website: String) {
if (website.startsWith(";")) {
curWebsites.add(website.substringAfter(":"))
} else {
curWebsites.add(website.trimStart(':'))
}
}
private fun saveContact(source: String) {
val organization = Organization(curCompany, curJobPosition)
val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curNickname, curPhotoUri, curPhoneNumbers,
curEmails, curAddresses, curEvents, source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites)
if (ContactsHelper(activity).insertContact(contact)) {
contactsImported++
}
}
private fun resetValues() {
curPrefix = ""
curFirstName = ""
curMiddleName = ""
curSurname = ""
curSuffix = ""
curNickname = ""
curPhotoUri = ""
curNotes = ""
curCompany = ""
curJobPosition = ""
curPhoneNumbers = ArrayList()
curEmails = ArrayList()
curEvents = ArrayList()
curAddresses = ArrayList()
curGroups = ArrayList()
curWebsites = ArrayList()
isGettingPhoto = false
currentPhotoString = StringBuilder()
currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG
isGettingName = false
currentNameIsANSI = false
currentNameString = StringBuilder()
isGettingNotes = false
currentNotesSB = StringBuilder()
isGettingCompany = false
currentCompanyIsANSI = false
currentCompany = StringBuilder()
return activity.getCachePhotoUri(file).toString()
}
}

View file

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.60'
ext.kotlin_version = '1.2.61'
repositories {
google()