Commit 3f5c2d96 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Rewrite to Kotlin

parent 5a203f07
Pipeline #9310173 passed with stage
in 9 minutes and 51 seconds
buildscript {
ext.kotlin_version = '1.1.3'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
jcenter()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
repositories {
jcenter()
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 26
......@@ -50,9 +48,10 @@ android {
}
dependencies {
compile 'org.apache.commons:commons-lang3:3.5'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'org.apache.commons:commons-text:1.1'
compile 'commons-io:commons-io:2.5'
provided 'org.projectlombok:lombok:1.16.16'
// ez-vcard to parse/generate VCards
compile('com.googlecode.ez-vcard:ez-vcard:0.10.2') {
......@@ -66,8 +65,10 @@ dependencies {
androidTestCompile 'com.android.support.test:runner:+'
androidTestCompile 'com.android.support.test:rules:+'
androidTestCompile 'junit:junit:4.12'
androidTestCompile 'org.projectlombok:lombok:1.16.16'
testCompile 'junit:junit:4.12'
testCompile 'org.projectlombok:lombok:1.16.16'
}
// grant permissions for unit tests
......
......@@ -21,6 +21,8 @@ import org.junit.Test;
import java.util.Arrays;
import at.bitfire.vcard4android.impl.TestAddressBook;
import static android.support.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
......@@ -47,7 +49,7 @@ public class AndroidAddressBookTest {
@Test
public void testSettings() throws ContactsStorageException {
AndroidAddressBook addressBook = new AndroidAddressBook(testAccount, provider, AndroidGroupFactory.INSTANCE, AndroidContactFactory.INSTANCE);
AndroidAddressBook addressBook = new TestAddressBook(testAccount, provider);
ContentValues values = new ContentValues();
values.put(ContactsContract.Settings.SHOULD_SYNC, false);
......@@ -68,7 +70,7 @@ public class AndroidAddressBookTest {
@Test
public void testSyncState() throws ContactsStorageException {
AndroidAddressBook addressBook = new AndroidAddressBook(testAccount, provider, AndroidGroupFactory.INSTANCE, AndroidContactFactory.INSTANCE);
AndroidAddressBook addressBook = new TestAddressBook(testAccount, provider);
addressBook.setSyncState(new byte[0]);
assertEquals(0, addressBook.getSyncState().length);
......
......@@ -26,7 +26,9 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.sql.Date;
import java.util.Arrays;
import java.util.List;
import at.bitfire.vcard4android.impl.TestAddressBook;
import ezvcard.VCardVersion;
import ezvcard.property.Address;
import ezvcard.property.Birthday;
......@@ -52,7 +54,7 @@ public class AndroidContactTest {
provider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
assertNotNull(provider);
addressBook = new AndroidAddressBook(testAccount, provider, AndroidGroupFactory.INSTANCE, AndroidContactFactory.INSTANCE);
addressBook = new TestAddressBook(testAccount, provider);
}
@After
......@@ -64,30 +66,30 @@ public class AndroidContactTest {
@Test
public void testAddAndReadContact() throws ContactsStorageException, FileNotFoundException {
Contact vcard = new Contact();
vcard.displayName = "Mya Contact";
vcard.prefix = "Magª";
vcard.givenName = "Mya";
vcard.familyName = "Contact";
vcard.suffix = "BSc";
vcard.phoneticGivenName = "Först";
vcard.phoneticMiddleName = "Mittelerde";
vcard.phoneticFamilyName = "Fämilie";
vcard.birthDay = new Birthday(Date.valueOf("1980-04-16"));
vcard.setDisplayName("Mya Contact");
vcard.setPrefix("Magª");
vcard.setGivenName("Mya");
vcard.setFamilyName("Contact");
vcard.setSuffix("BSc");
vcard.setPhoneticGivenName("Först");
vcard.setPhoneticMiddleName("Mittelerde");
vcard.setPhoneticFamilyName("Fämilie");
vcard.setBirthDay(new Birthday(Date.valueOf("1980-04-16")));
AndroidContact contact = new AndroidContact(addressBook, vcard, null, null);
contact.create();
@Cleanup("delete") AndroidContact contact2 = new AndroidContact(addressBook, contact.id, null, null);
@Cleanup("delete") AndroidContact contact2 = new AndroidContact(addressBook, contact.getId(), null, null);
Contact vcard2 = contact2.getContact();
assertEquals(vcard.displayName, vcard2.displayName);
assertEquals(vcard.prefix, vcard2.prefix);
assertEquals(vcard.givenName, vcard2.givenName);
assertEquals(vcard.familyName, vcard2.familyName);
assertEquals(vcard.suffix, vcard2.suffix);
assertEquals(vcard.phoneticGivenName, vcard2.phoneticGivenName);
assertEquals(vcard.phoneticMiddleName, vcard2.phoneticMiddleName);
assertEquals(vcard.phoneticFamilyName, vcard2.phoneticFamilyName);
assertEquals(vcard.birthDay, vcard2.birthDay);
assertEquals(vcard.getDisplayName(), vcard2.getDisplayName());
assertEquals(vcard.getPrefix(), vcard2.getPrefix());
assertEquals(vcard.getGivenName(), vcard2.getGivenName());
assertEquals(vcard.getFamilyName(), vcard2.getFamilyName());
assertEquals(vcard.getSuffix(), vcard2.getSuffix());
assertEquals(vcard.getPhoneticGivenName(), vcard2.getPhoneticGivenName());
assertEquals(vcard.getPhoneticMiddleName(), vcard2.getPhoneticMiddleName());
assertEquals(vcard.getPhoneticFamilyName(), vcard2.getPhoneticFamilyName());
assertEquals(vcard.getBirthDay(), vcard2.getBirthDay());
}
@Test
......@@ -99,37 +101,37 @@ public class AndroidContactTest {
"TEL;CELL=;PREF=:+12345\r\n" +
"EMAIL;PREF=invalid:test@example.com\r\n" +
"END:VCARD\r\n";
Contact[] contacts = Contact.fromStream(IOUtils.toInputStream(vCard, charset), charset, null);
List<Contact> contacts = Contact.fromStream(IOUtils.toInputStream(vCard, charset), charset, null);
AndroidContact dbContact = new AndroidContact(addressBook, contacts[0], null, null);
AndroidContact dbContact = new AndroidContact(addressBook, contacts.get(0), null, null);
dbContact.create();
@Cleanup("delete") AndroidContact dbContact2 = new AndroidContact(addressBook, dbContact.id, null, null);
@Cleanup("delete") AndroidContact dbContact2 = new AndroidContact(addressBook, dbContact.getId(), null, null);
Contact contact2 = dbContact2.getContact();
assertEquals("Test", contact2.displayName);
assertEquals("+12345", contact2.phoneNumbers.get(0).property.getText());
assertEquals("test@example.com", contact2.emails.get(0).property.getValue());
assertEquals("Test", contact2.getDisplayName());
assertEquals("+12345", contact2.getPhoneNumbers().get(0).getProperty().getText());
assertEquals("test@example.com", contact2.getEmails().get(0).getProperty().getValue());
}
@Test
public void testLargeTransactionManyRows() throws ContactsStorageException, FileNotFoundException {
Contact vcard = new Contact();
vcard.displayName = "Large Transaction (many rows)";
vcard.setDisplayName("Large Transaction (many rows)");
for (int i = 0; i < 4000; i++)
vcard.emails.add(new LabeledProperty<Email>(new Email("test" + i + "@example.com")));
vcard.getEmails().add(new LabeledProperty<Email>(new Email("test" + i + "@example.com")));
AndroidContact contact = new AndroidContact(addressBook, vcard, null, null);
contact.create();
@Cleanup("delete") AndroidContact contact2 = new AndroidContact(addressBook, contact.id, null, null);
@Cleanup("delete") AndroidContact contact2 = new AndroidContact(addressBook, contact.getId(), null, null);
Contact vcard2 = contact2.getContact();
assertEquals(4000, vcard2.emails.size());
assertEquals(4000, vcard2.getEmails().size());
}
@Test(expected = ContactsStorageException.class)
public void testLargeTransactionSingleRow() throws ContactsStorageException {
Contact vcard = new Contact();
vcard.displayName = "Large Transaction (one row which is too large)";
vcard.setDisplayName("Large Transaction (one row which is too large)");
// 1 MB eTag ... have fun
char data[] = new char[1024*1024];
......@@ -146,7 +148,7 @@ public class AndroidContactTest {
address.setLabel("My \"Label\"\nLine 2");
address.setStreetAddress("Street \"Address\"");
Contact contact = new Contact();
contact.addresses.add(new LabeledProperty<>(address));
contact.getAddresses().add(new LabeledProperty<>(address));
/* label-param = "LABEL=" param-value
* param-values must not contain DQUOTE and should be encoded as defined in RFC 6868
......@@ -171,16 +173,16 @@ public class AndroidContactTest {
@Test
public void testBirthdayWithoutYear() throws ContactsStorageException, FileNotFoundException {
Contact vcard = new Contact();
vcard.displayName = "Mya Contact";
vcard.birthDay = new Birthday(PartialDate.parse("-04-16"));
vcard.setDisplayName("Mya Contact");
vcard.setBirthDay(new Birthday(PartialDate.parse("-04-16")));
AndroidContact contact = new AndroidContact(addressBook, vcard, null, null);
contact.create();
@Cleanup("delete") AndroidContact contact2 = new AndroidContact(addressBook, contact.id, null, null);
@Cleanup("delete") AndroidContact contact2 = new AndroidContact(addressBook, contact.getId(), null, null);
Contact vcard2 = contact2.getContact();
assertEquals(vcard.displayName, vcard2.displayName);
assertEquals(vcard.birthDay, vcard2.birthDay);
assertEquals(vcard.getDisplayName(), vcard2.getDisplayName());
assertEquals(vcard.getBirthDay(), vcard2.getBirthDay());
}
......
......@@ -19,6 +19,9 @@ import org.junit.Before;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.util.List;
import at.bitfire.vcard4android.impl.TestAddressBook;
import static android.support.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.assertEquals;
......@@ -37,7 +40,7 @@ public class AndroidGroupTest {
provider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.AUTHORITY);
assertNotNull(provider);
addressBook = new AndroidAddressBook(testAccount, provider, AndroidGroupFactory.INSTANCE, AndroidContactFactory.INSTANCE);
addressBook = new TestAddressBook(testAccount, provider);
}
@After
......@@ -49,24 +52,24 @@ public class AndroidGroupTest {
@Test
public void testCreateReadDeleteGroup() throws FileNotFoundException, ContactsStorageException {
Contact contact = new Contact();
contact.displayName = "at.bitfire.vcard4android-AndroidGroupTest";
contact.note = "(test group)";
contact.setDisplayName("at.bitfire.vcard4android-AndroidGroupTest");
contact.setNote("(test group)");
// ensure we start without this group
assertEquals(0, addressBook.queryGroups(ContactsContract.Groups.TITLE + "=?", new String[] { contact.displayName }).length);
assertEquals(0, addressBook.queryGroups(ContactsContract.Groups.TITLE + "=?", new String[] { contact.getDisplayName() }).size());
// create group
AndroidGroup group = new AndroidGroup(addressBook, contact, null, null);
group.create();
AndroidGroup[] groups = addressBook.queryGroups(ContactsContract.Groups.TITLE + "=?", new String[] { contact.displayName } );
assertEquals(1, groups.length);
Contact contact2 = groups[0].getContact();
assertEquals(contact.displayName, contact2.displayName);
assertEquals(contact.note, contact2.note);
List<AndroidGroup> groups = addressBook.queryGroups(ContactsContract.Groups.TITLE + "=?", new String[] { contact.getDisplayName() } );
assertEquals(1, groups.size());
Contact contact2 = groups.get(0).getContact();
assertEquals(contact.getDisplayName(), contact2.getDisplayName());
assertEquals(contact.getNote(), contact2.getNote());
// delete group
group.delete();
assertEquals(0, addressBook.queryGroups(ContactsContract.Groups.TITLE + "=?", new String[] { contact.displayName }).length);
assertEquals(0, addressBook.queryGroups(ContactsContract.Groups.TITLE + "=?", new String[] { contact.getDisplayName() }).size());
}
}
/*
* Copyright © Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.vcard4android.impl
import android.accounts.Account
import android.content.ContentProviderClient
import at.bitfire.vcard4android.*
class TestAddressBook(
account: Account,
provider: ContentProviderClient
): AndroidAddressBook<AndroidContact, AndroidGroup>(account, provider, ContactFactory, GroupFactory) {
object ContactFactory: AndroidContactFactory<AndroidContact> {
override fun newInstance(addressBook: AndroidAddressBook<AndroidContact, AndroidGroup>, id: Long, fileName: String?, eTag: String?) =
AndroidContact(addressBook, id, fileName, eTag)
override fun newInstance(addressBook: AndroidAddressBook<AndroidContact, AndroidGroup>, contact: Contact, fileName: String?, eTag: String?): AndroidContact =
AndroidContact(addressBook, contact, fileName, eTag)
}
object GroupFactory: AndroidGroupFactory<AndroidGroup> {
override fun newInstance(addressBook: AndroidAddressBook<AndroidContact, AndroidGroup>, id: Long, fileName: String?, eTag: String?) =
AndroidGroup(addressBook, id, fileName, eTag)
override fun newInstance(addressBook: AndroidAddressBook<AndroidContact, AndroidGroup>, contact: Contact, fileName: String?, eTag: String?) =
AndroidGroup(addressBook, contact, fileName, eTag)
}
}
\ No newline at end of file
/*
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.vcard4android;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import java.io.FileNotFoundException;
import java.util.LinkedList;
import java.util.List;
import lombok.Cleanup;
public class AndroidAddressBook {
public Account account;
final public ContentProviderClient provider;
final AndroidGroupFactory groupFactory;
final AndroidContactFactory contactFactory;
public AndroidAddressBook(Account account, ContentProviderClient provider, AndroidGroupFactory groupFactory, AndroidContactFactory contactFactory) {
this.account = account;
this.provider = provider;
this.groupFactory = groupFactory;
this.contactFactory = contactFactory;
}
// account-specific address book settings
@SuppressWarnings("Recycle")
public ContentValues getSettings() throws ContactsStorageException {
try {
@Cleanup Cursor cursor = provider.query(syncAdapterURI(ContactsContract.Settings.CONTENT_URI), null, null, null, null);
if (cursor != null && cursor.moveToNext()) {
ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(cursor, values);
return values;
} else
throw new FileNotFoundException();
} catch(FileNotFoundException|RemoteException e) {
throw new ContactsStorageException("Couldn't read contacts settings", e);
}
}
public void updateSettings(ContentValues values) throws ContactsStorageException {
values.put(ContactsContract.Settings.ACCOUNT_NAME, account.name);
values.put(ContactsContract.Settings.ACCOUNT_TYPE, account.type);
try {
provider.insert(syncAdapterURI(ContactsContract.Settings.CONTENT_URI), values);
} catch(RemoteException e) {
throw new ContactsStorageException("Couldn't write contacts settings", e);
}
}
// account-specific address book sync state
public byte[] getSyncState() throws ContactsStorageException {
try {
return ContactsContract.SyncState.get(provider, account);
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't read contacts sync state", e);
}
}
public void setSyncState(byte[] data) throws ContactsStorageException {
try {
ContactsContract.SyncState.set(provider, account, data);
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't write contacts sync state", e);
}
}
// groups
@SuppressWarnings("Recycle")
protected AndroidGroup[] queryGroups(String where, String[] whereArgs) throws ContactsStorageException {
try {
@Cleanup Cursor cursor = provider.query(syncAdapterURI(Groups.CONTENT_URI),
new String[] { Groups._ID, AndroidGroup.COLUMN_FILENAME, AndroidGroup.COLUMN_ETAG }, where, whereArgs, null);
List<AndroidGroup> groups = new LinkedList<>();
while (cursor != null && cursor.moveToNext())
groups.add(groupFactory.newInstance(this, cursor.getLong(0), cursor.getString(1), cursor.getString(2)));
return groups.toArray(groupFactory.newArray(groups.size()));
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't query contact groups", e);
}
}
@SuppressWarnings("Recycle")
protected AndroidContact[] queryContacts(String where, String[] whereArgs) throws ContactsStorageException {
try {
@Cleanup Cursor cursor = provider.query(syncAdapterURI(RawContacts.CONTENT_URI),
new String[] { RawContacts._ID, AndroidContact.COLUMN_FILENAME, AndroidContact.COLUMN_ETAG },
where, whereArgs, null);
List<AndroidContact> contacts = new LinkedList<>();
while (cursor != null && cursor.moveToNext())
contacts.add(contactFactory.newInstance(this, cursor.getLong(0), cursor.getString(1), cursor.getString(2)));
return contacts.toArray(contactFactory.newArray(contacts.size()));
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't query contacts", e);
}
}
// helpers
public Uri syncAdapterURI(Uri uri) {
return uri.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
}
/*
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.vcard4android
import android.accounts.Account
import android.annotation.SuppressLint
import android.content.ContentProviderClient
import android.content.ContentValues
import android.database.DatabaseUtils
import android.net.Uri
import android.os.RemoteException
import android.provider.ContactsContract
import android.provider.ContactsContract.Groups
import android.provider.ContactsContract.RawContacts
import java.io.FileNotFoundException
import java.util.*
open class AndroidAddressBook<out T1: AndroidContact, out T2: AndroidGroup>(
var account: Account,
val provider: ContentProviderClient,
val contactFactory: AndroidContactFactory<T1>,
val groupFactory: AndroidGroupFactory<T2>
) {
// account-specific address book settings
@Throws(ContactsStorageException::class)
fun getSettings(): ContentValues {
val values = ContentValues()
try {
provider.query(syncAdapterURI(ContactsContract.Settings.CONTENT_URI), null, null, null, null)?.use { cursor ->
if (cursor.moveToNext())
DatabaseUtils.cursorRowToContentValues(cursor, values)
else
throw FileNotFoundException()
}
} catch(e: Exception) {
throw ContactsStorageException("Couldn't read address book settings", e)
}
return values
}
@Throws(ContactsStorageException::class)
fun updateSettings(values: ContentValues) {
values.put(ContactsContract.Settings.ACCOUNT_NAME, account.name)
values.put(ContactsContract.Settings.ACCOUNT_TYPE, account.type)
try {
provider.insert(syncAdapterURI(ContactsContract.Settings.CONTENT_URI), values)
} catch(e: RemoteException) {
throw ContactsStorageException("Couldn't write address book settings", e)
}
}
// account-specific address book sync state
@Throws(ContactsStorageException::class)
fun getSyncState(): ByteArray =
try {
ContactsContract.SyncState.get(provider, account)
} catch(e: RemoteException) {
throw ContactsStorageException("Couldn't read address book sync state", e)
}
@Throws(ContactsStorageException::class)
fun setSyncState(data: ByteArray) {
try {
ContactsContract.SyncState.set(provider, account, data)
} catch(e: RemoteException) {
throw ContactsStorageException("Couldn't write contacts sync state", e)
}
}
// groups
@SuppressLint("Recycle")
@Throws(ContactsStorageException::class)
protected fun queryContacts(where: String?, whereArgs: Array<String>?): List<T1> {
val contacts = LinkedList<T1>()
try {
provider.query(syncAdapterURI(RawContacts.CONTENT_URI),
arrayOf(RawContacts._ID, AndroidContact.COLUMN_FILENAME, AndroidContact.COLUMN_ETAG),
where, whereArgs, null)?.let { cursor ->
while (cursor.moveToNext())
contacts += contactFactory.newInstance(this, cursor.getLong(0), cursor.getString(1), cursor.getString(2))
}
} catch(e: RemoteException) {
throw ContactsStorageException("Couldn't query contacts", e)
}
return contacts
}
@SuppressLint("Recycle")
@Throws(ContactsStorageException::class)
protected fun queryGroups(where: String?, whereArgs: Array<String>?): List<T2> {
val groups = LinkedList<T2>()
try {
provider.query(syncAdapterURI(Groups.CONTENT_URI),
arrayOf(Groups._ID, AndroidGroup.COLUMN_FILENAME, AndroidGroup.COLUMN_ETAG),
where, whereArgs, null)?.use { cursor ->
while (cursor.moveToNext())
groups += groupFactory.newInstance(this, cursor.getLong(0), cursor.getString(1), cursor.getString(2))
}
} catch(e: RemoteException) {
throw ContactsStorageException("Couldn't query contact groups", e)
}
return groups
}
// helpers
fun syncAdapterURI(uri: Uri) = uri.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build()!!
}
/*
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.vcard4android;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Entity;
import android.content.EntityIterator;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import ezvcard.parameter.AddressType;
import ezvcard.parameter.EmailType;
import ezvcard.parameter.ImppType;
import ezvcard.parameter.RelatedType;
import ezvcard.parameter.TelephoneType;
import ezvcard.property.Address;
import ezvcard.property.Anniversary;
import ezvcard.property.Birthday;
import ezvcard.property.DateOrTimeProperty;
import ezvcard.property.Impp;
import ezvcard.property.Related;
import ezvcard.property.Telephone;
import ezvcard.property.Url;
import ezvcard.util.PartialDate;
import lombok.Cleanup;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
@ToString(of={ "id","fileName","eTag","contact" }, doNotUseGetters=true)
public class AndroidContact {