package com.moez.QKSMS.common.vcard;
import android.annotation.TargetApi;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.os.Build;
import android.os.RemoteException;
import android.provider.ContactsContract;
import ezvcard.VCard;
import ezvcard.property.Address;
import ezvcard.property.Birthday;
import ezvcard.property.Email;
import ezvcard.property.FormattedName;
import ezvcard.property.Impp;
import ezvcard.property.Nickname;
import ezvcard.property.Note;
import ezvcard.property.Organization;
import ezvcard.property.Photo;
import ezvcard.property.RawProperty;
import ezvcard.property.StructuredName;
import ezvcard.property.Telephone;
import ezvcard.property.Title;
import ezvcard.property.Url;
import ezvcard.property.VCardProperty;
import ezvcard.util.TelUri;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static android.text.TextUtils.isEmpty;
/*
Copyright (c) 2014-2015, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
/**
* Inserts a {@link VCard} into an Android database.
*
* @author Pratyush
* @author Michael Angstadt
*/
public class ContactOperations {
private static final int rawContactID = 0;
private final Context context;
private final NonEmptyContentValues account;
public ContactOperations(Context context) {
this(context, null, null);
}
public ContactOperations(Context context, String accountName, String accountType) {
this.context = context;
account = new NonEmptyContentValues();
account.put(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType);
account.put(ContactsContract.RawContacts.ACCOUNT_NAME, accountName);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void insertContact(VCard vcard) throws RemoteException, OperationApplicationException {
// TODO handle Raw properties - Raw properties include various extension which start with "X-" like X-ASSISTANT, X-AIM, X-SPOUSE
List<NonEmptyContentValues> contentValues = new ArrayList<>();
contentValues.add(account);
convertName(contentValues, vcard);
convertNickname(contentValues, vcard);
convertPhones(contentValues, vcard);
convertEmails(contentValues, vcard);
convertAddresses(contentValues, vcard);
convertIms(contentValues, vcard);
// handle Android Custom fields..This is only valid for Android generated Vcards. As the Android would
// generate NickName, ContactEvents other than Birthday and RelationShip with this "X-ANDROID-CUSTOM" name
convertCustomFields(contentValues, vcard);
// handle Iphone kinda of group properties. which are grouped together.
convertGroupedProperties(contentValues, vcard);
convertBirthdays(contentValues, vcard);
convertWebsites(contentValues, vcard);
convertNotes(contentValues, vcard);
convertPhotos(contentValues, vcard);
convertOrganization(contentValues, vcard);
ArrayList<ContentProviderOperation> operations = new ArrayList<>(contentValues.size());
for (NonEmptyContentValues values : contentValues) {
ContentValues cv = values.getContentValues();
if (cv.size() == 0) {
continue;
}
//@formatter:off
ContentProviderOperation operation =
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValues(cv)
.build();
//@formatter:on
operations.add(operation);
}
// Executing all the insert operations as a single database transaction
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
}
private void convertName(List<NonEmptyContentValues> contentValues, VCard vcard) {
NonEmptyContentValues values = new NonEmptyContentValues(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
String firstName = null, lastName = null, namePrefix = null, nameSuffix = null;
StructuredName n = vcard.getStructuredName();
if (n != null) {
firstName = n.getGiven();
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, firstName);
lastName = n.getFamily();
values.put(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, lastName);
List<String> prefixes = n.getPrefixes();
if (!prefixes.isEmpty()) {
namePrefix = prefixes.get(0);
values.put(ContactsContract.CommonDataKinds.StructuredName.PREFIX, namePrefix);
}
List<String> suffixes = n.getSuffixes();
if (!suffixes.isEmpty()) {
nameSuffix = suffixes.get(0);
values.put(ContactsContract.CommonDataKinds.StructuredName.SUFFIX, nameSuffix);
}
}
FormattedName fn = vcard.getFormattedName();
String formattedName = (fn == null) ? null : fn.getValue();
String displayName;
if (isEmpty(formattedName)) {
StringBuilder sb = new StringBuilder();
if (!isEmpty(namePrefix)){
sb.append(namePrefix).append(' ');
}
if (!isEmpty(firstName)){
sb.append(firstName).append(' ');
}
if (!isEmpty(lastName)){
sb.append(lastName).append(' ');
}
if (!isEmpty(nameSuffix)){
if (sb.length() > 0){
sb.deleteCharAt(sb.length()-1); //delete space character
sb.append(", ");
}
sb.append(nameSuffix);
}
displayName = sb.toString().trim();
} else {
displayName = formattedName;
}
values.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName);
RawProperty xPhoneticFirstName = vcard.getExtendedProperty("X-PHONETIC-FIRST-NAME");
String firstPhoneticName = (xPhoneticFirstName == null) ? null : xPhoneticFirstName.getValue();
values.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, firstPhoneticName);
RawProperty xPhoneticLastName = vcard.getExtendedProperty("X-PHONETIC-LAST-NAME");
String lastPhoneticName = (xPhoneticLastName == null) ? null : xPhoneticLastName.getValue();
values.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, lastPhoneticName);
contentValues.add(values);
}
private void convertNickname(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Nickname nickname : vcard.getNicknames()) {
List<String> nicknameValues = nickname.getValues();
if (nicknameValues.isEmpty()) {
continue;
}
for (String nicknameValue : nicknameValues) {
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Nickname.NAME, nicknameValue);
contentValues.add(cv);
}
}
}
private void convertPhones(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Telephone telephone : vcard.getTelephoneNumbers()) {
String value = telephone.getText();
TelUri uri = telephone.getUri();
if (isEmpty(value)) {
if (uri == null) {
continue;
}
value = uri.toString();
}
int phoneKind = DataMappings.getPhoneType(telephone);
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Phone.NUMBER, value);
cv.put(ContactsContract.CommonDataKinds.Phone.TYPE, phoneKind);
contentValues.add(cv);
}
}
private void convertEmails(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Email email : vcard.getEmails()) {
String value = email.getValue();
if (isEmpty(value)) {
continue;
}
int emailKind = DataMappings.getEmailType(email);
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Email.ADDRESS, value);
cv.put(ContactsContract.CommonDataKinds.Email.TYPE, emailKind);
contentValues.add(cv);
}
}
private void convertAddresses(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Address address : vcard.getAddresses()) {
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
String street = address.getStreetAddress();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.STREET, street);
String poBox = address.getPoBox();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.POBOX, poBox);
String city = address.getLocality();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.CITY, city);
String state = address.getRegion();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.REGION, state);
String zipCode = address.getPostalCode();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, zipCode);
String country = address.getCountry();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, country);
String label = address.getLabel();
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.LABEL, label);
int addressKind = DataMappings.getAddressType(address);
cv.put(ContactsContract.CommonDataKinds.StructuredPostal.TYPE, addressKind);
contentValues.add(cv);
}
}
private void convertIms(List<NonEmptyContentValues> contentValues, VCard vcard) {
//handle extended properties
for (Map.Entry<String, Integer> entry : DataMappings.getImPropertyNameMappings().entrySet()) {
String propertyName = entry.getKey();
Integer protocolType = entry.getValue();
List<RawProperty> rawProperties = vcard.getExtendedProperties(propertyName);
for (RawProperty rawProperty : rawProperties) {
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE);
String value = rawProperty.getValue();
cv.put(ContactsContract.CommonDataKinds.Im.DATA, value);
cv.put(ContactsContract.CommonDataKinds.Im.PROTOCOL, protocolType);
contentValues.add(cv);
}
}
//handle IMPP properties
for (Impp impp : vcard.getImpps()) {
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE);
String immpAddress = impp.getHandle();
cv.put(ContactsContract.CommonDataKinds.Im.DATA, immpAddress);
int immpProtocolType = DataMappings.getIMTypeFromProtocol(impp.getProtocol());
cv.put(ContactsContract.CommonDataKinds.Im.PROTOCOL, immpProtocolType);
contentValues.add(cv);
}
}
private void convertCustomFields(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (AndroidCustomField customField : vcard.getProperties(AndroidCustomField.class)) {
List<String> values = customField.getValues();
if (values.isEmpty()) {
continue;
}
NonEmptyContentValues cv;
if (customField.isNickname()) {
cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Nickname.NAME, values.get(0));
} else if (customField.isContactEvent()) {
cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Event.START_DATE, values.get(0));
cv.put(ContactsContract.CommonDataKinds.Event.TYPE, values.get(1));
} else if (customField.isRelation()) {
cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Relation.NAME, values.get(0));
cv.put(ContactsContract.CommonDataKinds.Relation.TYPE, values.get(1));
} else {
continue;
}
contentValues.add(cv);
}
}
private void convertGroupedProperties(List<NonEmptyContentValues> contentValues, VCard vcard) {
List<RawProperty> extendedProperties = vcard.getExtendedProperties();
Map<String, List<RawProperty>> orderedByGroup = orderPropertiesByGroup(extendedProperties);
final int ABDATE = 1, ABRELATEDNAMES = 2;
for (List<RawProperty> properties : orderedByGroup.values()) {
if (properties.size() == 1) {
continue;
}
String label = null;
String val = null;
int mime = 0;
for (RawProperty property : properties) {
String name = property.getPropertyName();
if (name.equalsIgnoreCase("X-ABDATE")) {
label = property.getValue(); //date
mime = ABDATE;
continue;
}
if (name.equalsIgnoreCase("X-ABRELATEDNAMES")) {
label = property.getValue(); //name
mime = ABRELATEDNAMES;
continue;
}
if (name.equalsIgnoreCase("X-ABLABEL")) {
val = property.getValue(); // type of value ..Birthday,anniversary
continue;
}
}
NonEmptyContentValues cv = null;
switch (mime) {
case ABDATE:
cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Event.START_DATE, label);
int type = DataMappings.getDateType(val);
cv.put(ContactsContract.CommonDataKinds.Event.TYPE, type);
break;
case ABRELATEDNAMES:
if (val != null) {
cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Nickname.NAME, label);
if (!val.equals("Nickname")) {
type = DataMappings.getNameType(val);
cv.put(ContactsContract.CommonDataKinds.Relation.TYPE, type);
}
}
break;
default:
continue;
}
contentValues.add(cv);
}
}
private void convertBirthdays(List<NonEmptyContentValues> contentValues, VCard vcard) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
for (Birthday birthday : vcard.getBirthdays()) {
Date date = birthday.getDate();
if (date == null) {
continue;
}
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY);
cv.put(ContactsContract.CommonDataKinds.Event.START_DATE, df.format(date));
contentValues.add(cv);
}
}
private void convertWebsites(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Url url : vcard.getUrls()) {
String urlValue = url.getValue();
if (isEmpty(urlValue)) {
continue;
}
int type = DataMappings.getWebSiteType(url.getType());
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Website.URL, urlValue);
cv.put(ContactsContract.CommonDataKinds.Website.TYPE, type);
contentValues.add(cv);
}
}
private void convertNotes(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Note note : vcard.getNotes()) {
String noteValue = note.getValue();
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Note.NOTE, noteValue);
contentValues.add(cv);
}
}
private void convertPhotos(List<NonEmptyContentValues> contentValues, VCard vcard) {
for (Photo photo : vcard.getPhotos()) {
byte[] data = photo.getData();
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
cv.put(ContactsContract.CommonDataKinds.Photo.PHOTO, data);
contentValues.add(cv);
}
}
private void convertOrganization(List<NonEmptyContentValues> contentValues, VCard vcard) {
NonEmptyContentValues cv = new NonEmptyContentValues(ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE);
Organization organization = vcard.getOrganization();
if (organization != null) {
List<String> values = organization.getValues();
String keys[] = { ContactsContract.CommonDataKinds.Organization.COMPANY, ContactsContract.CommonDataKinds.Organization.DEPARTMENT, ContactsContract.CommonDataKinds.Organization.OFFICE_LOCATION };
for (int i = 0; i < values.size(); i++) {
String key = keys[i];
String value = values.get(i);
cv.put(key, value);
}
}
List<Title> titleList = vcard.getTitles();
if (!titleList.isEmpty()) {
cv.put(ContactsContract.CommonDataKinds.Organization.TITLE, titleList.get(0).getValue());
}
contentValues.add(cv);
}
/**
* Groups properties by their group name.
* @param properties the properties to group
* @return a map where the key is the group name (null for no group) and the
* value is the list of properties that belong to that group
*/
private <T extends VCardProperty> Map<String, List<T>> orderPropertiesByGroup(List<T> properties) {
Map<String, List<T>> groupedProperties = new HashMap<>();
for (T property : properties) {
String group = property.getGroup();
if (isEmpty(group)) {
continue;
}
List<T> groupPropertiesList = groupedProperties.get(group);
if (groupPropertiesList == null) {
groupPropertiesList = new ArrayList<T>();
groupedProperties.put(group, groupPropertiesList);
}
groupPropertiesList.add(property);
}
return groupedProperties;
}
/**
* A wrapper for {@link ContentValues} that only adds values which are
* non-null and non-empty (in the case of Strings).
*/
private static class NonEmptyContentValues {
private final ContentValues contentValues = new ContentValues();
private final String contentItemType;
public NonEmptyContentValues() {
this(null);
}
/**
* @param contentItemType the MIME type (value of
* {@link ContactsContract.Contacts.Data#MIMETYPE})
*/
public NonEmptyContentValues(String contentItemType) {
this.contentItemType = contentItemType;
}
public void put(String key, String value) {
if (isEmpty(value)) {
return;
}
contentValues.put(key, value);
}
public void put(String key, int value) {
contentValues.put(key, value);
}
public void put(String key, byte[] value) {
if (value == null) {
return;
}
contentValues.put(key, value);
}
/**
* Gets the wrapped {@link ContentValues} object, adding the MIME type
* entry if the values map is not empty.
* @return the wrapped {@link ContentValues} object
*/
public ContentValues getContentValues() {
if (contentValues.size() > 0 && contentItemType != null) {
put(ContactsContract.Contacts.Data.MIMETYPE, contentItemType);
}
return contentValues;
}
}
}