/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2009 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package de.chbosync.android.syncmlclient.source.pim.contact;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import com.funambol.common.pim.model.common.Property;
import com.funambol.common.pim.model.common.XTag;
import com.funambol.common.pim.model.contact.Address;
import com.funambol.common.pim.model.contact.BusinessDetail;
import com.funambol.common.pim.model.contact.Email;
import com.funambol.common.pim.model.contact.Name;
import com.funambol.common.pim.model.contact.Note;
import com.funambol.common.pim.model.contact.PersonalDetail;
import com.funambol.common.pim.model.contact.Phone;
import com.funambol.common.pim.model.contact.Photo;
import com.funambol.common.pim.model.contact.Title;
import com.funambol.common.pim.model.contact.WebPage;
import com.funambol.sync.ItemStatus;
import com.funambol.syncml.protocol.PropParam;
import com.funambol.util.Log;
import com.funambol.util.StringUtil;
import de.chbosync.android.syncmlclient.R;
import de.chbosync.android.syncmlclient.AndroidCustomization;
import de.chbosync.android.syncmlclient.source.AbstractDataManager;
public abstract class ContactManager extends AbstractDataManager<Contact> {
private static final String TAG_LOG = "ContactManager";
private static final Uri RAW_CONTACT_URI = ContactsContract.RawContacts.CONTENT_URI;
public static final String CONTACTS_AUTHORITY = "com.android.contacts";
private static final String FUNAMBOL_SOURCE_ID_PREFIX = "chbosync-";
private static final int MAX_OPS_PER_BATCH = 499;
private static final int COMMIT_THRESHOLD = MAX_OPS_PER_BATCH - 80;
protected AndroidCustomization customization = AndroidCustomization.getInstance();
// TODO FIXME: get this from the server caps
protected boolean multipleFieldsSupported = false;
protected boolean preferredFieldsSupported = false;
protected boolean callerIsSyncAdapter = true;
protected int allItemsCount = Integer.MIN_VALUE;
protected ArrayList<ContentProviderOperation> ops;
protected int lastAddBackReferenceId = 0;
protected Vector newKeys = null;
protected ArrayList<Integer> rawContactIdx = null;
// Constructors------------------------------------------------
public ContactManager(Context context) {
this(context, true);
}
public ContactManager(Context context, boolean callerIsSyncAdapter) {
super(context);
this.callerIsSyncAdapter = callerIsSyncAdapter;
}
public void beginTransaction() {
newKeys = new Vector();
ops = new ArrayList<ContentProviderOperation>();
rawContactIdx = new ArrayList<Integer>();
}
protected String getAuthority() {
return CONTACTS_AUTHORITY;
}
public Contact load(String key) throws IOException {
HashMap<String,List<Integer>> fieldsMap = new HashMap<String, List<Integer>>();
return load(key, fieldsMap);
}
private Contact load(String key, HashMap<String, List<Integer>> fieldsMap)
throws IOException {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading contact: " + key);
}
// Set the id for this contact
Contact contact = new Contact();
contact.setId(key);
// Load all pieces of information
loadAllFields(contact, fieldsMap);
return contact;
}
/**
* Add an item into the store. The operation is actually not committed and must be encapsulated into
* a transaction. In other words the call must be performed after a {@link beginTransaction} and before
* a {@link commit}.
* @param item the item to be committed
* @return null. The key of the new item is not returned here but by the commit method.
* @throws IOException
*/
@Override
public String add(Contact item) throws IOException {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Saving contact");
}
// Commit if it is time to do it
if (ops.size() >= COMMIT_THRESHOLD) {
commitSingleBatch();
}
Uri uri = addCallerIsSyncAdapterFlag(RAW_CONTACT_URI);
// This is the first insert into the raw contacts table
ContentProviderOperation i1 = ContentProviderOperation.newInsert(uri)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
.build();
ops.add(i1);
// Save the backreference for the raw contact id
lastAddBackReferenceId = ops.size() - 1;
rawContactIdx.add(new Integer(lastAddBackReferenceId));
prepareAllFields(item, null, ops);
// At this point this contact cannot have a valid id. Make sure this is true.
//item.setId(-1);
return null;
}
/**
* Update an item into the store. The operation is actually not committed and must be encapsulated into
* a transaction. In other words the call must be performed after a {@link beginTransaction} and before
* a {@link commit}.
* @param key the identified of the item to change
* @param item the new item
* @throws IOException
*/
@Override
public void update(String key, Contact item) throws IOException {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Updating contact: " + key);
}
long rawContactId;
try {
rawContactId = Long.parseLong(key);
} catch(Exception e) {
Log.error(TAG_LOG, "Invalid item key " + key, e);
throw new IOException("Invalid item key");
}
// If the contact does not exist, then we perform an add
if (!exists(key)) {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Tried to update a non existing contact. Creating a new one ");
}
add(item);
return;
}
// Commit if it is time to do it
if (ops.size() >= COMMIT_THRESHOLD) {
commitSingleBatch();
}
// Set the id
item.setId(rawContactId);
// Load the old contact and fill the fields map
HashMap<String,List<Integer>> fieldsMap = new HashMap<String, List<Integer>>();
load(key, fieldsMap);
prepareAllFields(item, fieldsMap, ops);
}
/**
* Delete an item from the store. The operation is actually not committed and must be encapsulated into
* a transaction. In other words the call must be performed after a {@link beginTransaction} and before
* a {@link commit}.
* @param key the identifier of the item to remove
* @throws IOException
*/
@Override
public void delete(String key) throws IOException {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Deleting contact: " + key);
}
if (ops.size() >= COMMIT_THRESHOLD) {
commitSingleBatch();
}
long rawContactId;
try {
rawContactId = Long.parseLong(key);
} catch (Exception e) {
Log.error(TAG_LOG, "Invalid item id " + key, e);
throw new IOException("Invalid item id");
}
// If we are in a transaction then we just prepare the delete operation,
// otherwise we actually perform it
prepareHardDelete(rawContactId);
}
/**
* Permanently remove the given contact from the store. A transaction is not required for
* this operation to succeed.
* @param rawContactId the contact id in the raw_contacts table
* @return the number of deleted rows
*/
public int hardDelete(long rawContactId) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Hard deleting contact: " + rawContactId);
}
Uri uri = addCallerIsSyncAdapterFlag(RAW_CONTACT_URI);
// Delete from raw_contacts (related rows in Data table are
// automatically deleted)
return resolver.delete(uri,
ContactsContract.RawContacts._ID+"="+rawContactId, null);
}
@Override
public void deleteAll() throws IOException {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Deleting all contacts");
}
Uri uri = addCallerIsSyncAdapterFlag(RAW_CONTACT_URI);
// Delete from raw_contacts (related rows in Data table are
// automatically deleted)
// Note: delete only contacts from funambol accounts
int count = resolver.delete(uri,
ContactsContract.RawContacts.ACCOUNT_TYPE+"='"+accountType+"'", null);
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Deleted contacts count: " + count);
}
if (count < 0) {
Log.error(TAG_LOG, "Cannot delete all contacts");
throw new IOException("Cannot delete contacts");
}
}
@Override
public boolean exists(String key) {
long id;
try {
id = Long.parseLong(key);
} catch (Exception e) {
Log.error(TAG_LOG, "Invalid item key " + key, e);
return false;
}
String cols[] = {ContactsContract.RawContacts._ID, ContactsContract.RawContacts.DELETED};
Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, id);
Cursor cur = resolver.query(uri, cols, null, null, null);
boolean found;
if (!cur.moveToFirst()) {
found = false;
} else {
int deleted = cur.getInt(1);
if (deleted == 0) {
found = true;
} else {
found = false;
}
}
cur.close();
return found;
}
@Override
public Enumeration getAllKeys() throws IOException {
Cursor peopleCur = getContactsCursor();
try {
int contactListSize = peopleCur.getCount();
Vector<String> itemKeys = new Vector<String>(contactListSize);
if (!peopleCur.moveToFirst()) {
return itemKeys.elements();
}
for (int i = 0; i < contactListSize; i++) {
String key = peopleCur.getString(0);
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Found item with key: " + key);
}
itemKeys.addElement(key);
peopleCur.moveToNext();
}
// Update the all items count
allItemsCount = contactListSize;
return itemKeys.elements();
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot get all items keys: ", e);
throw new IOException("Cannot get all items keys");
} finally {
peopleCur.close();
}
}
@Override
public int getAllCount() throws IOException {
if (Integer.MIN_VALUE != allItemsCount) {
//variable already initialized
return allItemsCount;
}
int items = 0;
Cursor peopleCur = getContactsCursor();
try {
items = peopleCur.getCount();
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot get all items keys: ", e);
throw new IOException("Cannot get all items keys");
} finally {
peopleCur.close();
}
return items;
}
public void refreshSourceIdAndDirtyFlag(Vector itemsStatus) throws IOException {
if(Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Refreshing source id and dirty flag for groups of contacts");
}
ArrayList<ContentProviderOperation> ops2 = new ArrayList<ContentProviderOperation>();
for(int i=0;i<itemsStatus.size();++i) {
ItemStatus itemStatus = (ItemStatus)itemsStatus.elementAt(i);
String key = itemStatus.getKey();
long id = Long.parseLong(key);
prepareSourceIdAndDirtyFlagOperation(id, ops2);
}
try {
// Apply all the operations to set the sourceId and the dirty flag
resolver.applyBatch(ContactsContract.AUTHORITY, ops2);
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot set items status", e);
throw new IOException("Cannot set items status");
}
}
public void refreshSourceIdAndDirtyFlag(long contactId) {
if(Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Refreshing source id and dirty flag for contact: " + contactId);
}
Uri uri = ContentUris.withAppendedId(RAW_CONTACT_URI, contactId);
uri = addCallerIsSyncAdapterFlag(uri);
ContentValues cv = new ContentValues();
cv.put(ContactsContract.RawContacts.SOURCE_ID,
FUNAMBOL_SOURCE_ID_PREFIX + contactId);
cv.put(ContactsContract.RawContacts.DIRTY, 0);
resolver.update(uri, cv, null, null);
}
public void setPreferredFieldsSupported(boolean preferredFieldsSupported) {
this.preferredFieldsSupported = preferredFieldsSupported;
}
public void setMultipleFieldsSupported(boolean multipleFieldsSupported) {
this.multipleFieldsSupported = multipleFieldsSupported;
}
public Vector commit() throws IOException {
commitSingleBatch();
return newKeys;
}
protected void addProperty(Vector<com.funambol.syncml.protocol.Property>
properties, String propName, String[] values, PropParam[] propParams,
String displayName) {
addProperty(properties, propName, values, propParams, displayName, 0, 0);
}
protected void addProperty(Vector<com.funambol.syncml.protocol.Property>
properties, String propName, String[] values, PropParam[] propParams) {
addProperty(properties, propName, values, propParams, null, 0, 0);
}
protected void addProperty(Vector<com.funambol.syncml.protocol.Property>
properties, String propName, String[] values, PropParam[] propParams,
int maxOccur, int maxSize) {
addProperty(properties, propName, values, propParams, null, maxOccur, maxSize);
}
protected void addProperty(Vector<com.funambol.syncml.protocol.Property>
properties, String propName, String[] values, PropParam[] propParams,
String displayName, int maxOccur, int maxSize) {
if(values == null) {
values = new String[0];
}
if(propParams == null) {
propParams = new PropParam[0];
}
properties.add(new com.funambol.syncml.protocol.Property(propName,
null /* We don't specify the property data type */,
maxOccur,
maxSize,
false, values,
displayName /* We don't specify the property display name */,
propParams));
}
protected void loadAllFields(Contact contact, HashMap<String,List<Integer>> fieldsMap)
throws IOException {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading all fields for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
if (pd == null) {
pd = new PersonalDetail();
contact.setPersonalDetail(pd);
}
BusinessDetail bd = contact.getBusinessDetail();
if (bd == null) {
bd = new BusinessDetail();
contact.setBusinessDetail(bd);
}
Cursor allFields = resolver.query(ContactsContract.Data.CONTENT_URI, null,
ContactsContract.Data.RAW_CONTACT_ID+"="+id, null, null);
// Move to first element
if (!allFields.moveToFirst()) {
if(!exists("" + id)) {
throw new IOException("Cannot find person " + id);
} else {
// The contact exists but there is nothing to load
return;
}
}
loadFromCursor(contact, allFields, fieldsMap);
}
protected void loadFromCursor(Contact contact, Cursor cur,
HashMap<String,List<Integer>> fieldsMap) throws IOException {
try {
do {
String mimeType = cur.getString(cur.getColumnIndexOrThrow(
ContactsContract.Data.MIMETYPE));
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Found a raw of type: " + mimeType);
}
if (CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadNameField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadNickNameField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadPhoneField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadEmailField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadPhotoField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadOrganizationField(contact, cur, fieldsMap);
} else if (CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadPostalAddressField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Event.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadEventField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadImField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Note.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadNoteField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadWebsiteField(contact, cur, fieldsMap);
} else if (CommonDataKinds.Relation.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadRelationField(contact, cur, fieldsMap);
} else if (AdditionalDataKinds.UID.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadUidField(contact, cur, fieldsMap);
} else if (AdditionalDataKinds.TimeZone.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadTimeZoneField(contact, cur, fieldsMap);
} else if (AdditionalDataKinds.Revision.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadRevisionField(contact, cur, fieldsMap);
} else if (AdditionalDataKinds.Geo.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadGeoField(contact, cur, fieldsMap);
} else if (AdditionalDataKinds.Ext.CONTENT_ITEM_TYPE.equals(mimeType)) {
loadExtField(contact, cur, fieldsMap);
} else {
loadCustomField(mimeType, contact, cur, fieldsMap);
}
} while(cur.moveToNext());
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot load contact ", e);
throw new IOException("Cannot load contact");
} finally {
cur.close();
}
}
private void prepareAllFields(Contact contact,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
prepareName (contact, fieldsMap, ops);
prepareNickname (contact, fieldsMap, ops);
preparePhones (contact, fieldsMap, ops);
prepareEmail (contact, fieldsMap, ops);
prepareIm (contact, fieldsMap, ops);
preparePhoto (contact, fieldsMap, ops);
prepareOrganization (contact, fieldsMap, ops);
preparePostalAddress (contact, fieldsMap, ops);
prepareEvent (contact, fieldsMap, ops);
prepareNote (contact, fieldsMap, ops);
prepareWebsite (contact, fieldsMap, ops);
prepareRelation (contact, fieldsMap, ops);
prepareUid (contact, fieldsMap, ops);
prepareTimeZone (contact, fieldsMap, ops);
prepareRevision (contact, fieldsMap, ops);
prepareGeo (contact, fieldsMap, ops);
prepareExtFields (contact, fieldsMap, ops);
prepareCustomFields (contact, fieldsMap, ops);
}
private void appendFieldId(HashMap<String,List<Integer>> fieldsMap,
String key, int rowId)
{
List<Integer> l = fieldsMap.get(key);
if (l == null) {
l = new ArrayList<Integer>();
fieldsMap.put(key, l);
}
l.add(new Integer(rowId));
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Appended fieldId " + rowId + " for " + key);
}
}
/**
* Retrieve the People fields from a Cursor
*/
protected void loadNameField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading name for: " + id);
}
Name nameField = contact.getName();
if(nameField == null) {
nameField = new Name();
}
String dn = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredName.DISPLAY_NAME));
if (dn != null && customization.isDisplayNameSupported()) {
// setting firstName and lastName from the combined name
Property dnProp = new Property(dn);
nameField.setDisplayName(dnProp);
}
String firstName = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredName.GIVEN_NAME));
if (firstName != null) {
Property firstNameProp = new Property(firstName);
nameField.setFirstName(firstNameProp);
}
String middleName = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredName.MIDDLE_NAME));
if (middleName != null) {
Property middleNameProp = new Property(middleName);
nameField.setMiddleName(middleNameProp);
}
String lastName = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredName.FAMILY_NAME));
if (lastName != null) {
Property lastNameProp = new Property(lastName);
nameField.setLastName(lastNameProp);
}
String prefixName = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredName.PREFIX));
if (prefixName != null) {
Property prefixNameProp = new Property(prefixName);
nameField.setSalutation(prefixNameProp);
}
String suffixName = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredName.SUFFIX));
if (suffixName != null) {
Property suffixNameProp = new Property(suffixName);
nameField.setSuffix(suffixNameProp);
}
contact.setName(nameField);
loadFieldToMap(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
/**
* Retrieve the NickName field from a Cursor
*/
protected void loadNickNameField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
Log.error(TAG_LOG, "Nickname fields are not supported");
}
/**
* Retrieve the Phone fields from a Cursor
*/
protected void loadPhoneField(Contact contact, Cursor cur,
HashMap<String, List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Load Phone Field for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
BusinessDetail bd = contact.getBusinessDetail();
String number = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.Phone.NUMBER));
String label = null;
int phoneType = cur.getInt(cur.getColumnIndexOrThrow(CommonDataKinds.Phone.TYPE));
String fieldId = createFieldId(new Object[] {
CommonDataKinds.Phone.CONTENT_ITEM_TYPE, phoneType, label });
List<Integer> items = fieldsMap.get(fieldId);
int idx = items != null ? items.size() + 1 : 1;
Phone phone = new Phone(number);
if(preferredFieldsSupported) {
boolean preferred = cur.getInt(cur.getColumnIndexOrThrow(
CommonDataKinds.Phone.IS_PRIMARY)) != 0;
phone.setPreferred(preferred);
}
if (phoneType == CommonDataKinds.Phone.TYPE_HOME) {
phone.setPhoneType(Phone.HOME_PHONE_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_WORK) {
phone.setPhoneType(Phone.BUSINESS_PHONE_NUMBER);
bd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_MOBILE) {
phone.setPhoneType(Phone.MOBILE_PHONE_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_OTHER) {
phone.setPhoneType(Phone.OTHER_PHONE_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_FAX_HOME) {
phone.setPhoneType(Phone.HOME_FAX_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_FAX_WORK) {
phone.setPhoneType(Phone.BUSINESS_FAX_NUMBER);
bd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_PAGER) {
phone.setPhoneType(Phone.PAGER_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_COMPANY_MAIN) {
phone.setPhoneType(Phone.COMPANY_PHONE_NUMBER);
bd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_OTHER_FAX) {
phone.setPhoneType(Phone.OTHER_FAX_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_MAIN) {
phone.setPhoneType(Phone.PRIMARY_PHONE_NUMBER);
pd.addPhone(phone);
} else if (phoneType == CommonDataKinds.Phone.TYPE_CUSTOM) {
if (context.getString(R.string.label_work2_phone).equals(label)) {
phone.setPhoneType(Phone.BUSINESS_PHONE_NUMBER);
bd.addPhone(phone);
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring custom phone number: " + label);
}
}
} else {
Log.error(TAG_LOG, "Ignoring unknwon phone type: " + phoneType);
}
loadFieldToMap(CommonDataKinds.Phone.CONTENT_ITEM_TYPE, phoneType, label, cur, fieldsMap);
}
/**
* Retrieve the email fields from a Cursor
*/
protected void loadEmailField(Contact contact, Cursor cur, HashMap<String, List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Load Email Field for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
BusinessDetail bd = contact.getBusinessDetail();
String email = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.Email.DATA));
if (StringUtil.isNullOrEmpty(email)) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Ignoring null or empty email address");
}
return;
}
int emailType = cur.getInt(cur.getColumnIndexOrThrow(CommonDataKinds.Email.TYPE));
Email emailObj = new Email(email);
if(preferredFieldsSupported) {
boolean preferred = cur.getInt(cur.getColumnIndexOrThrow(
CommonDataKinds.Email.IS_PRIMARY)) != 0;
emailObj.setPreferred(preferred);
}
switch(emailType) {
case CommonDataKinds.Email.TYPE_HOME:
{
emailObj.setEmailType(Email.HOME_EMAIL);
pd.addEmail(emailObj);
break;
}
case CommonDataKinds.Email.TYPE_WORK:
{
emailObj.setEmailType(Email.WORK_EMAIL);
bd.addEmail(emailObj);
break;
}
case CommonDataKinds.Email.TYPE_OTHER:
{
emailObj.setEmailType(Email.OTHER_EMAIL);
pd.addEmail(emailObj);
break;
}
default:
Log.error(TAG_LOG, "Ignoring unknown email type: " + emailType);
}
loadFieldToMap(CommonDataKinds.Email.CONTENT_ITEM_TYPE, emailType, cur, fieldsMap);
}
/**
* Retrieve the photo fields from a Cursor
*/
protected void loadPhotoField(Contact contact, Cursor cur, HashMap<String, List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading Photo Field for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
byte data[] = cur.getBlob(cur.getColumnIndexOrThrow(CommonDataKinds.Photo.PHOTO));
if (data != null) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "This contact has a photo associated");
}
String type = detectImageFormat(data);
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Photo type: " + type);
}
Photo photo = new Photo();
photo.setImage(data);
photo.setType(type);
if(preferredFieldsSupported) {
boolean preferred = cur.getInt(cur.getColumnIndexOrThrow(
CommonDataKinds.Photo.IS_PRIMARY)) != 0;
photo.setPreferred(preferred);
}
pd.addPhotoObject(photo);
loadFieldToMap(CommonDataKinds.Photo.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
}
/**
* Detects the file format of the given image.
* Supported formats are:
* image/bmp
* image/jpeg
* image/png
* image/gif
* @param image
* @return the image mime type or null if unknown
*/
private String detectImageFormat(byte[] image) {
final byte[] BMP_PREFIX = {(byte)0x42, (byte)0x4D};
final byte[] JPEG_PREFIX = {(byte)0xFF, (byte)0xD8, (byte)0xFF};
final byte[] PNG_PREFIX = {(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47};
final byte[] GIF_PREFIX = {(byte)0x47, (byte)0x49, (byte)0x46, (byte)0x38};
final String BMP_MIME_TYPE = "image/bmp";
final String JPEG_MIME_TYPE = "image/jpeg";
final String PNG_MIME_TYPE = "image/png";
final String GIF_MIME_TYPE = "image/gif";
// Check image prefixes
if(checkImagePrefix(image, BMP_PREFIX)) return BMP_MIME_TYPE;
if(checkImagePrefix(image, JPEG_PREFIX)) return JPEG_MIME_TYPE;
if(checkImagePrefix(image, PNG_PREFIX)) return PNG_MIME_TYPE;
if(checkImagePrefix(image, GIF_PREFIX)) return GIF_MIME_TYPE;
// Image type not detected
return null;
}
private boolean checkImagePrefix(byte[] image, byte[] prefix) {
if(image == null || prefix == null) {
return false;
}
boolean match = true;
for(int i=0; i<prefix.length; i++) {
if(i < image.length) {
match &= image[i] == prefix[i];
} else {
match = false;
break;
}
}
return match;
}
/**
* Retrieve the Organization fields from a Cursor
*/
protected void loadOrganizationField(Contact contact, Cursor cur,
HashMap<String,List<Integer>> fieldsMap) throws IOException
{
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Load Organization Field for: " + id);
}
BusinessDetail bd = contact.getBusinessDetail();
int orgType = cur.getInt(cur.getColumnIndexOrThrow(CommonDataKinds.Organization.TYPE));
if (orgType == CommonDataKinds.Organization.TYPE_WORK) {
int colId = cur.getColumnIndexOrThrow(CommonDataKinds.Organization.COMPANY);
String company = cur.getString(colId);
if (company != null) {
Property companyProp = new Property(company);
bd.setCompany(companyProp);
}
colId = cur.getColumnIndexOrThrow(CommonDataKinds.Organization.TITLE);
String title = cur.getString(colId);
if (title != null) {
ArrayList titles = new ArrayList();
Title titleProp = new Title(title);
titles.add(titleProp);
bd.setTitles(titles);
}
colId = cur.getColumnIndexOrThrow(CommonDataKinds.Organization.DEPARTMENT);
String department = cur.getString(colId);
if (department != null) {
Property departmentProp = new Property(department);
bd.setDepartment(departmentProp);
}
colId = cur.getColumnIndexOrThrow(CommonDataKinds.Organization.OFFICE_LOCATION);
String location = cur.getString(colId);
if (location != null) {
bd.setOfficeLocation(location);
}
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring organization of type " + orgType);
}
}
loadFieldToMap(CommonDataKinds.Organization.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
/**
* Retrieve the postal address fields from a Cursor
*/
protected void loadPostalAddressField(Contact contact, Cursor cur, HashMap<String, List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Load Address Fields for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
BusinessDetail bd = contact.getBusinessDetail();
Address address = new Address();
if(preferredFieldsSupported) {
boolean preferred = cur.getInt(cur.getColumnIndexOrThrow(
CommonDataKinds.StructuredPostal.IS_PRIMARY)) != 0;
address.setPreferred(preferred);
}
String city = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.CITY));
if (city != null) {
Property cityProp = new Property(city);
address.setCity(cityProp);
}
String country = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.COUNTRY));
if (country != null) {
Property countryProp = new Property(country);
address.setCountry(countryProp);
}
String pobox = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.POBOX));
if (pobox != null) {
Property poboxProp = new Property(pobox);
address.setPostOfficeAddress(poboxProp);
}
String poCode = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.POSTCODE));
if (poCode != null) {
Property poCodeProp = new Property(poCode);
address.setPostalCode(poCodeProp);
}
String region = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.REGION));
if (region != null) {
Property stateProp = new Property(region);
address.setState(stateProp);
}
String street = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.STREET));
if (street != null) {
Property streetProp = new Property(street);
address.setStreet(streetProp);
}
String extAddress = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.NEIGHBORHOOD));
if (extAddress != null) {
Property extAddressProp = new Property(extAddress);
address.setExtendedAddress(extAddressProp);
}
int type = cur.getInt(cur.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.TYPE));
if (type == CommonDataKinds.StructuredPostal.TYPE_WORK) {
address.setAddressType(Address.WORK_ADDRESS);
bd.addAddress(address);
} else if (type == CommonDataKinds.StructuredPostal.TYPE_HOME) {
address.setAddressType(Address.HOME_ADDRESS);
pd.addAddress(address);
} else if (type == CommonDataKinds.StructuredPostal.TYPE_OTHER) {
address.setAddressType(Address.OTHER_ADDRESS);
pd.addAddress(address);
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring other address");
}
}
loadFieldToMap(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE, type, cur, fieldsMap);
}
/**
* Retrieve the Event field from a Cursor
*/
protected void loadEventField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading event for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
String eventDate = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.Event.START_DATE));
int eventType = cur.getInt(cur.getColumnIndexOrThrow(CommonDataKinds.Event.TYPE));
if (eventType == CommonDataKinds.Event.TYPE_BIRTHDAY) {
pd.setBirthday(eventDate);
} else if (eventType == CommonDataKinds.Event.TYPE_ANNIVERSARY) {
pd.setAnniversary(eventDate);
} else {
Log.error(TAG_LOG, "Ignoring unknown event type: " + eventType);
}
loadFieldToMap(CommonDataKinds.Event.CONTENT_ITEM_TYPE, eventType, cur, fieldsMap);
}
/**
* Retrieve the Im field from a Cursor. Shall be defined by subclasses in
* order to support it.
*/
protected void loadImField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
Log.error(TAG_LOG, "IM fields are not supported");
}
/**
* Retrieve the Note field from a Cursor
*/
protected void loadNoteField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading note for: " + id);
}
String note = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.Note.NOTE));
if (note != null) {
// The device cannot display \r characters, so we add them back here
note = StringUtil.replaceAll(note, "\n", "\r\n");
}
Note noteField = new Note();
noteField.setPropertyValue(note);
// Ensure the property type is set
noteField.setPropertyType("");
contact.addNote(noteField);
loadFieldToMap(CommonDataKinds.Note.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
/**
* Retrieve the Website field from a Cursor
*/
protected void loadWebsiteField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading Website for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
BusinessDetail bd = contact.getBusinessDetail();
String url = cur.getString(cur.getColumnIndexOrThrow(CommonDataKinds.Website.URL));
int websiteType = cur.getInt(cur.getColumnIndexOrThrow(CommonDataKinds.Website.TYPE));
WebPage website = new WebPage(url);
if(preferredFieldsSupported) {
boolean preferred = cur.getInt(cur.getColumnIndexOrThrow(
CommonDataKinds.Website.IS_PRIMARY)) != 0;
website.setPreferred(preferred);
}
if (websiteType == CommonDataKinds.Website.TYPE_OTHER) {
website.setPropertyType(WebPage.OTHER_WEBPAGE);
pd.addWebPage(website);
} else if (websiteType == CommonDataKinds.Website.TYPE_HOME) {
website.setPropertyType(WebPage.HOME_WEBPAGE);
pd.addWebPage(website);
} else if (websiteType == CommonDataKinds.Website.TYPE_WORK) {
website.setPropertyType(WebPage.WORK_WEBPAGE);
bd.addWebPage(website);
} else {
Log.error(TAG_LOG, "Ignoring unknown Website type: " + websiteType);
}
loadFieldToMap(CommonDataKinds.Website.CONTENT_ITEM_TYPE, websiteType, cur, fieldsMap);
}
/**
* Retrieve the Relation field from a Cursor. Shall be defined by subclasses in
* order to support it.
*/
protected void loadRelationField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
Log.error(TAG_LOG, "Relation fields are not supported");
}
protected void loadUidField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading Uid for: " + id);
}
String uid = cur.getString(cur.getColumnIndexOrThrow(AdditionalDataKinds.UID.VALUE));
contact.setUid(uid);
loadFieldToMap(AdditionalDataKinds.UID.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
protected void loadTimeZoneField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading TimeZone for: " + id);
}
String tz = cur.getString(cur.getColumnIndexOrThrow(AdditionalDataKinds.TimeZone.VALUE));
contact.setTimezone(tz);
loadFieldToMap(AdditionalDataKinds.TimeZone.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
protected void loadRevisionField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading Revision for: " + id);
}
String rev = cur.getString(cur.getColumnIndexOrThrow(AdditionalDataKinds.Revision.VALUE));
contact.setRevision(rev);
loadFieldToMap(AdditionalDataKinds.Revision.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
protected void loadGeoField(Contact contact, Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading Geo for: " + id);
}
PersonalDetail pd = contact.getPersonalDetail();
String geo = cur.getString(cur.getColumnIndexOrThrow(AdditionalDataKinds.Geo.VALUE));
pd.setGeo(new Property(geo));
loadFieldToMap(AdditionalDataKinds.Geo.CONTENT_ITEM_TYPE, 0, cur, fieldsMap);
}
protected void loadExtField(Contact contact, Cursor cur,
HashMap<String,List<Integer>> fieldsMap) {
long id = contact.getId();
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Loading ext field for: " + id);
}
String fieldName = cur.getString(cur.getColumnIndexOrThrow(AdditionalDataKinds.Ext.KEY));
String fieldValue = cur.getString(cur.getColumnIndexOrThrow(AdditionalDataKinds.Ext.VALUE));
XTag tmpxTag = new XTag();
tmpxTag.getXTag().setPropertyValue(fieldValue);
tmpxTag.setXTagValue(fieldName);
contact.addXTag(tmpxTag);
loadFieldToMap(AdditionalDataKinds.Ext.CONTENT_ITEM_TYPE, fieldName, cur, fieldsMap);
}
protected void loadCustomField(String mimetype, Contact contact, Cursor cur,
HashMap<String,List<Integer>> fieldsMap) {
// There is not custom field to load
}
/**
* Load the given field to the fieldsMap if needed
*/
protected void loadFieldToMap(String field, Object fieldType, Cursor cur,
HashMap<String,List<Integer>> fieldsMap) {
loadFieldToMap(field, fieldType, null, cur, fieldsMap);
}
protected void loadFieldToMap(String field, Object fieldType, String label,
Cursor cur, HashMap<String,List<Integer>> fieldsMap) {
if (fieldsMap != null) {
String fieldId = createFieldId(new Object[] {field, fieldType, label});
if (multipleFieldsSupported || fieldsMap.get(fieldId) == null) {
int rowId = cur.getInt(cur.getColumnIndexOrThrow("_ID"));
appendFieldId(fieldsMap, fieldId, rowId);
}
}
}
/**
* Check if the given date string is well formatted. Supported formats:
* 1. yyyymmdd
* 2. yyyy-mm-dd
* 3. yyyy/mm/dd
*/
private boolean checkDate(String date) {
if (date.length() == 8) {
try {
// date must contain only digits
Integer.parseInt(date);
return true;
} catch(NumberFormatException ex) {
return false;
}
} else if (date.length() == 10) {
if((date.charAt(4) == '-' && date.charAt(7) == '-') ||
(date.charAt(4) == '/' && date.charAt(7) == '/')) {
try {
Integer.parseInt(date.substring(0, 4)); // yyyy
Integer.parseInt(date.substring(5, 7)); // mm
Integer.parseInt(date.substring(8, 10)); // dd
return true;
} catch(NumberFormatException ex) {
return false;
}
} else {
// Invalid separators
return false;
}
} else {
// Invalid length
return false;
}
}
protected ContentProviderOperation.Builder prepareBuilder(long contactId, String fieldId,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops,
boolean multipleField)
{
List<Integer> rowIds = null;
if (fieldsMap != null) {
rowIds = fieldsMap.get(fieldId);
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Found " + rowIds + " for " + fieldId);
}
}
boolean insert = true;
ContentProviderOperation.Builder builder = null;
if (rowIds != null && rowIds.size() > 0) {
if (multipleField && multipleFieldsSupported) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "This is a multiple field. prepareBuilder will delete old data");
}
// We must delete all the old fields
prepareRowDeletion(rowIds, ops);
// After deleting all the entries of the given type, we
// add them back with the new values
} else {
// We update the first one here
// TODO: check if we actually need to delete this entry
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "prepareBuilder will perform an update");
}
long rowId = rowIds.get(0);
Uri uri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, rowId);
uri = addCallerIsSyncAdapterFlag(uri);
builder = ContentProviderOperation.newUpdate(uri);
insert = false;
}
}
if (insert) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "prepareBuilder will perform an insert");
}
Uri uri = addCallerIsSyncAdapterFlag(ContactsContract.Data.CONTENT_URI);
builder = ContentProviderOperation.newInsert(uri);
}
// Set the contact id
if (contactId != -1) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Updating contact data: " + contactId);
}
builder = builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, contactId);
} else {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Inserting new contact data: " + lastAddBackReferenceId);
}
builder = builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,
lastAddBackReferenceId);
}
return builder;
}
protected void prepareName(Contact contact,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
Name nameField = contact.getName();
if (nameField == null) {
return;
}
String fieldId = createFieldId(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, 0);
// Set all the various bits and pieces
Property dnProp = nameField.getDisplayName();
Property firstNameProp = nameField.getFirstName();
Property middleNameProp = nameField.getMiddleName();
Property lastNameProp = nameField.getLastName();
Property suffixProp = nameField.getSuffix();
Property salutationProp = nameField.getSalutation();
Property props[] = {firstNameProp, middleNameProp, lastNameProp,
suffixProp, salutationProp};
if (isNull(props)) {
// Simply return if the server didn't send anything
return;
}
String displayName = dnProp != null ? dnProp.getPropertyValueAsString() : null;
String firstName = firstNameProp.getPropertyValueAsString();
String middleName = middleNameProp.getPropertyValueAsString();
String lastName = lastNameProp.getPropertyValueAsString();
String suffix = suffixProp.getPropertyValueAsString();
String salutation = salutationProp.getPropertyValueAsString();
String propValues[] = {firstName, middleName, lastName, suffix, salutation};
if (isNull(propValues)) {
// Simply return if the server didn't send anything
return;
} else if (isFieldEmpty(propValues)) {
if(fieldsMap != null) {
// In this case the server sent an empty name, we shall remove the
// old entry to clean it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
if (customization.isDisplayNameSupported()) {
if (displayName != null) {
builder = builder.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, displayName);
}
} else {
// If the display name is not supported as a stand alone field, then
// we create it as the concatenation of the name components
StringBuffer dn = new StringBuffer();
if (firstName != null) {
dn.append(firstName);
}
if (middleName != null && middleName.length() > 0) {
if (dn.length() > 0) {
dn.append(" ");
}
dn.append(middleName);
}
if (lastName != null && lastName.length() > 0) {
if (dn.length() > 0) {
dn.append(" ");
}
dn.append(lastName);
}
if (dn.length() > 0) {
builder = builder.withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, dn.toString());
}
}
if (firstName != null) {
builder = builder.withValue(CommonDataKinds.StructuredName.GIVEN_NAME, firstName);
}
if (middleName != null) {
builder = builder.withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, middleName);
}
if (lastName != null) {
builder = builder.withValue(CommonDataKinds.StructuredName.FAMILY_NAME, lastName);
}
if (suffix != null) {
builder = builder.withValue(CommonDataKinds.StructuredName.SUFFIX, suffix);
}
if (salutation != null) {
builder = builder.withValue(CommonDataKinds.StructuredName.PREFIX, salutation);
}
// Append the operation
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
protected void prepareNickname(Contact contact,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
Log.error(TAG_LOG, "Nickname fields are not supported");
}
protected void preparePhones(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
PersonalDetail pd = contact.getPersonalDetail();
if (pd != null) {
List<Phone> phones = pd.getPhones();
if (phones != null) {
preparePhones(contact, fieldsMap, phones, ops);
}
}
BusinessDetail bd = contact.getBusinessDetail();
if (bd != null) {
List<Phone> phones = bd.getPhones();
if (phones != null) {
preparePhones(contact, fieldsMap, phones, ops);
}
}
}
protected void preparePhones(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<Phone> phones,
List<ContentProviderOperation> ops)
{
for( Phone phone : phones) {
String number = phone.getPropertyValueAsString();
StringBuffer label = new StringBuffer();
int type = getPhoneType(phone, label);
if(label.length() == 0) {
label = null;
}
if (type != -1) {
String fieldId = createFieldId(new Object[]
{CommonDataKinds.Phone.CONTENT_ITEM_TYPE, type, label});
if (StringUtil.isNullOrEmpty(number)) {
if(fieldsMap != null) {
// The field is empty, we shall remove it as the server
// wants to emtpy it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
continue;
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, true);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Phone.NUMBER, number);
builder = builder.withValue(CommonDataKinds.Phone.TYPE, type);
if (label != null && label.length() > 0) {
builder = builder.withValue(CommonDataKinds.Phone.LABEL, label.toString());
}
builder = builder.withValue(CommonDataKinds.Phone.IS_PRIMARY,
phone.isPreferred() ? 1 : 0);
builder = builder.withValue(CommonDataKinds.Phone.IS_SUPER_PRIMARY,
phone.isPreferred() ? 1 : 0);
ContentProviderOperation operation = builder.build();
ops.add(operation);
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring unknown phone number of type: " + phone.getPhoneType());
}
}
}
}
protected int getPhoneType(Phone phone, StringBuffer customLabel) {
String phoneType = phone.getPhoneType();
int t = -1;
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Getting phone type for: " + phoneType);
}
for(int i=1;i<=2;++i) {
if (Phone.COMPANY_PHONE_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_COMPANY_MAIN;
break;
} else if (Phone.PAGER_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_PAGER;
break;
} else if (Phone.MOBILE_PHONE_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_MOBILE;
break;
} else if (Phone.OTHER_PHONE_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_OTHER;
break;
} else if (Phone.HOME_PHONE_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_HOME;
break;
} else if (Phone.BUSINESS_PHONE_NUMBER.equals(phoneType)) {
if (i == 1) {
t = CommonDataKinds.Phone.TYPE_WORK;
} else {
customLabel.append(context.getString(R.string.label_work2_phone));
t = CommonDataKinds.Phone.TYPE_CUSTOM;
}
break;
} else if (Phone.OTHER_FAX_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_OTHER_FAX;
break;
} else if (Phone.HOME_FAX_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_FAX_HOME;
break;
} else if (Phone.BUSINESS_FAX_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_FAX_WORK;
break;
} else if (Phone.PRIMARY_PHONE_NUMBER.equals(phoneType)) {
t = CommonDataKinds.Phone.TYPE_MAIN;
break;
}
}
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Phone type mapped to: " + t);
}
return t;
}
protected void prepareEmail(Contact contact,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
long id = contact.getId();
PersonalDetail pd = contact.getPersonalDetail();
if (pd != null) {
List<Email> emails = pd.getEmails();
for(Email email : emails) {
String addr = email.getPropertyValueAsString();
if (Email.HOME_EMAIL.equals(email.getEmailType())) {
prepareEmail(id, email, CommonDataKinds.Email.TYPE_HOME,
fieldsMap, ops);
} else if (Email.OTHER_EMAIL.equals(email.getEmailType())) {
prepareEmail(id, email, CommonDataKinds.Email.TYPE_OTHER,
fieldsMap, ops);
} else if (Email.IM_ADDRESS.equals(email.getEmailType())) {
prepareIm(id, addr, CommonDataKinds.Im.TYPE_HOME,
email.isPreferred(), fieldsMap, ops);
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring email address " + email.getEmailType());
}
}
}
}
BusinessDetail bd = contact.getBusinessDetail();
if (bd != null) {
List<Email> emails = bd.getEmails();
for(Email email : emails) {
if (Email.WORK_EMAIL.equals(email.getEmailType())) {
prepareEmail(id, email, CommonDataKinds.Email.TYPE_WORK,
fieldsMap, ops);
} else if (Email.IM_ADDRESS.equals(email.getEmailType())) {
prepareIm(id, email.getPropertyValueAsString(), CommonDataKinds.Im.TYPE_WORK,
email.isPreferred(), fieldsMap, ops);
} else {
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Ignoring email address " + email.getEmailType());
}
}
}
}
}
protected void prepareEmail(long id, Email email, int type,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String address = email.getPropertyValueAsString();
String fieldId = createFieldId(CommonDataKinds.Email.CONTENT_ITEM_TYPE, type);
if (StringUtil.isNullOrEmpty(address)) {
if(fieldsMap != null) {
// The field is empty, so we can just remove it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(id, fieldId, fieldsMap, ops, true);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Email.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Email.DATA, address);
builder = builder.withValue(CommonDataKinds.Email.TYPE, type);
builder = builder.withValue(CommonDataKinds.Email.IS_PRIMARY,
email.isPreferred() ? 1 : 0);
builder = builder.withValue(CommonDataKinds.Email.IS_SUPER_PRIMARY,
email.isPreferred() ? 1 : 0);
ops.add(builder.build());
}
protected void prepareIm(Contact contact,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Direct Im fields are not supported");
}
}
protected void prepareIm(long id, String im, int type, boolean preferred,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String fieldId = createFieldId(CommonDataKinds.Im.CONTENT_ITEM_TYPE, type);
if (StringUtil.isNullOrEmpty(im)) {
if(fieldsMap != null) {
// The field is empty, so we can just remove it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(id, fieldId, fieldsMap, ops, true);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Im.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Im.DATA, im);
builder = builder.withValue(CommonDataKinds.Im.PROTOCOL,
CommonDataKinds.Im.PROTOCOL_AIM);
builder = builder.withValue(CommonDataKinds.Im.TYPE, type);
builder = builder.withValue(CommonDataKinds.Im.IS_PRIMARY,
preferred ? 1 : 0);
builder = builder.withValue(CommonDataKinds.Im.IS_SUPER_PRIMARY,
preferred ? 1 : 0);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
protected void preparePhoto(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
PersonalDetail pd = contact.getPersonalDetail();
List<Photo> photos = pd.getPhotoObjects();
if(photos == null || photos.isEmpty()) {
return;
}
for(Photo photo : photos) {
if (photo != null) {
byte[] photoBytes = photo.getImage();
String fieldId = createFieldId(CommonDataKinds.Photo.CONTENT_ITEM_TYPE, 0);
if (photoBytes != null && photoBytes.length > 0) {
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, true);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Photo.PHOTO, photoBytes);
builder = builder.withValue(CommonDataKinds.Photo.IS_PRIMARY,
photo.isPreferred() ? 1 : 0);
builder = builder.withValue(CommonDataKinds.Photo.IS_SUPER_PRIMARY,
photo.isPreferred() ? 1 : 0);
ContentProviderOperation operation = builder.build();
ops.add(operation);
} else if(fieldsMap != null) {
// The photo is sent empty, we need to remove it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
return;
}
}
}
}
protected void prepareOrganization(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
BusinessDetail bd = contact.getBusinessDetail();
Property companyProp = bd.getCompany();
Property depProp = bd.getDepartment();
String location = bd.getOfficeLocation();
Title t = null;
List<Title> titles = bd.getTitles();
if (titles != null && titles.size() > 0) {
t = titles.get(0);
}
String company = null;
String title = null;
String department = null;
if (companyProp != null) {
company = companyProp.getPropertyValueAsString();
}
if (t != null) {
title = t.getPropertyValueAsString();
}
if (depProp != null) {
department = depProp.getPropertyValueAsString();
}
String fieldId = createFieldId(CommonDataKinds.Organization.CONTENT_ITEM_TYPE, 0);
String allFields[] = {company, title, department, location};
if (isNull(allFields)) {
// If all the properties are empty, then the server did not send this
// field. We can simply return in this case.
return;
} else if (isFieldEmpty(allFields)) {
if(fieldsMap != null) {
// The field is empty, so we can just remove it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
} else {
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Organization.CONTENT_ITEM_TYPE);
if (company != null) {
builder = builder.withValue(CommonDataKinds.Organization.COMPANY, company);
}
if (title != null) {
builder = builder.withValue(CommonDataKinds.Organization.TITLE, title);
}
if (department != null) {
builder = builder.withValue(CommonDataKinds.Organization.DEPARTMENT, department);
}
if (location != null) {
builder = builder.withValue(CommonDataKinds.Organization.OFFICE_LOCATION, location);
}
// Add the type
builder = builder.withValue(CommonDataKinds.Organization.TYPE, CommonDataKinds.Organization.TYPE_WORK);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void preparePostalAddress(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
PersonalDetail pd = contact.getPersonalDetail();
if (pd != null) {
List<Address> addresses = pd.getAddresses();
for(Address address : addresses) {
if (address != null) {
if (Address.HOME_ADDRESS.equals(address.getAddressType())) {
preparePostalAddress(contact, address,
CommonDataKinds.StructuredPostal.TYPE_HOME,
fieldsMap, ops);
} else if (Address.OTHER_ADDRESS.equals(address.getAddressType())) {
preparePostalAddress(contact, address,
CommonDataKinds.StructuredPostal.TYPE_OTHER,
fieldsMap, ops);
}
}
}
}
BusinessDetail bd = contact.getBusinessDetail();
if (bd != null) {
List<Address> addresses = bd.getAddresses();
for(Address address : addresses) {
if (address != null) {
if (Address.WORK_ADDRESS.equals(address.getAddressType())) {
preparePostalAddress(contact, address,
CommonDataKinds.StructuredPostal.TYPE_WORK,
fieldsMap, ops);
}
}
}
}
}
protected void preparePostalAddress(Contact contact, Address address, int type,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String city = null;
String country = null;
String pobox = null;
String poCode = null;
String region = null;
String street = null;
String extAddress = null;
Property cityProp = address.getCity();
if (cityProp != null) {
city = cityProp.getPropertyValueAsString();
}
Property countryProp = address.getCountry();
if (countryProp != null) {
country = countryProp.getPropertyValueAsString();
}
Property poboxProp = address.getPostOfficeAddress();
if (poboxProp != null) {
pobox = poboxProp.getPropertyValueAsString();
}
Property pocodeProp = address.getPostalCode();
if (pocodeProp != null) {
poCode = pocodeProp.getPropertyValueAsString();
}
Property regionProp = address.getState();
if (regionProp != null) {
region = regionProp.getPropertyValueAsString();
}
Property streetProp = address.getStreet();
if (streetProp != null) {
street = streetProp.getPropertyValueAsString();
}
Property extAddressProp = address.getExtendedAddress();
if (extAddressProp != null) {
extAddress = extAddressProp.getPropertyValueAsString();
}
String fieldId = createFieldId(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE, type);
String allFields[] = {city,country,pobox,poCode,region,street};
if (isNull(allFields)) {
// The server did not send this field, just ignore it
return;
} else if (isFieldEmpty(allFields)) {
if(fieldsMap != null) {
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
} else {
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, true);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE);
if (city != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.CITY, city);
}
if (country != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.COUNTRY, country);
}
if (pobox != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.POBOX, pobox);
}
if (poCode != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.POSTCODE, poCode);
}
if (region != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.REGION, region);
}
if (street != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.STREET, street);
}
if (extAddress != null) {
builder = builder.withValue(CommonDataKinds.StructuredPostal.NEIGHBORHOOD, extAddress);
}
builder = builder.withValue(CommonDataKinds.StructuredPostal.TYPE, type);
builder = builder.withValue(CommonDataKinds.StructuredPostal.IS_PRIMARY,
address.isPreferred() ? 1 : 0);
builder = builder.withValue(CommonDataKinds.StructuredPostal.IS_SUPER_PRIMARY,
address.isPreferred() ? 1 : 0);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void prepareEvent(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
PersonalDetail pd = contact.getPersonalDetail();
prepareEvent(contact, pd.getBirthday(),
CommonDataKinds.Event.TYPE_BIRTHDAY, fieldsMap, ops);
prepareEvent(contact, pd.getAnniversary(),
CommonDataKinds.Event.TYPE_ANNIVERSARY, fieldsMap, ops);
}
protected void prepareEvent(Contact contact, String eventDate, int type,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String fieldId = createFieldId(CommonDataKinds.Event.CONTENT_ITEM_TYPE, type);
if (eventDate == null) {
// The server did not send this field, just ignore it
return;
} else if ("".equals(eventDate)) {
if(fieldsMap != null) {
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
} else {
// Insert date separator if needed
if(eventDate.length() == 8) {
StringBuffer date = new StringBuffer(10);
date.append(eventDate.substring(0, 4)).append("-");
date.append(eventDate.substring(4, 6)).append("-");
date.append(eventDate.substring(6));
eventDate = date.toString();
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Event.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Event.START_DATE,
eventDate);
builder = builder.withValue(CommonDataKinds.Event.TYPE, type);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void prepareNote(Contact contact,
HashMap<String, List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String fieldId = createFieldId(CommonDataKinds.Note.CONTENT_ITEM_TYPE, 0);
List<Note> notes = contact.getNotes();
if(notes != null) {
for( Note noteField : notes) {
String note = noteField.getPropertyValueAsString();
if (note != null) {
// The device cannot display \r characters, so we remove them
// here and add them back to outgoing items
note = StringUtil.replaceAll(note, "\r\n", "\n");
note = StringUtil.replaceAll(note, "\r", "\n");
}
if (StringUtil.isNullOrEmpty(note)) {
if(fieldsMap != null) {
// The field is empty, we shall remove it as the server
// wants to emtpy it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
continue;
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Note.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Note.NOTE, note);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
}
protected void prepareWebsite(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
PersonalDetail pd = contact.getPersonalDetail();
if (pd != null) {
List<WebPage> websites = pd.getWebPages();
if (websites != null) {
prepareWebsite(contact, CommonDataKinds.Website.TYPE_HOME,
fieldsMap, websites, ops);
}
}
BusinessDetail bd = contact.getBusinessDetail();
if (bd != null) {
List<WebPage> websites = bd.getWebPages();
if (websites != null) {
prepareWebsite(contact, CommonDataKinds.Website.TYPE_WORK,
fieldsMap, websites, ops);
}
}
}
private void prepareWebsite(Contact contact, int baseType,
HashMap<String,List<Integer>> fieldsMap,
List<WebPage> websites,
List<ContentProviderOperation> ops)
{
for( WebPage website : websites) {
int type = baseType;
if(WebPage.OTHER_WEBPAGE.equals(website.getPropertyType())) {
type = CommonDataKinds.Website.TYPE_OTHER;
}
String url = website.getPropertyValueAsString();
String fieldId = createFieldId(CommonDataKinds.Website.CONTENT_ITEM_TYPE, type);
if (StringUtil.isNullOrEmpty(url)) {
if(fieldsMap != null) {
// The field is empty, we shall remove it as the server
// wants to emtpy it
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
continue;
}
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, true);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Website.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Website.URL, url);
builder = builder.withValue(CommonDataKinds.Website.TYPE, type);
builder = builder.withValue(CommonDataKinds.Website.IS_PRIMARY,
website.isPreferred() ? 1 : 0);
builder = builder.withValue(CommonDataKinds.Website.IS_SUPER_PRIMARY,
website.isPreferred() ? 1 : 0);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void prepareRelation(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
Log.error(TAG_LOG, "Relation fields are not supported");
}
protected void prepareRelation(Contact contact, String relName, int type,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String fieldId = createFieldId(CommonDataKinds.Relation.CONTENT_ITEM_TYPE, type);
if (relName == null) {
// The server did not send this field, just ignore it
return;
} else if ("".equals(relName)) {
if(fieldsMap != null) {
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
} else {
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
CommonDataKinds.Relation.CONTENT_ITEM_TYPE);
builder = builder.withValue(CommonDataKinds.Relation.NAME,
relName);
builder = builder.withValue(CommonDataKinds.Relation.TYPE, type);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void prepareUid(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
prepareSimpleStringField(contact.getUid(),
AdditionalDataKinds.UID.CONTENT_ITEM_TYPE, 0,
AdditionalDataKinds.UID.VALUE,
contact, fieldsMap, ops);
}
protected void prepareTimeZone(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
prepareSimpleStringField(contact.getTimezone(),
AdditionalDataKinds.TimeZone.CONTENT_ITEM_TYPE, 0,
AdditionalDataKinds.TimeZone.VALUE,
contact, fieldsMap, ops);
}
protected void prepareRevision(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
prepareSimpleStringField(contact.getRevision(),
AdditionalDataKinds.Revision.CONTENT_ITEM_TYPE, 0,
AdditionalDataKinds.Revision.VALUE,
contact, fieldsMap, ops);
}
protected void prepareGeo(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
Property geo = contact.getPersonalDetail().getGeo();
if(geo != null) {
prepareSimpleStringField(geo.getPropertyValueAsString(),
AdditionalDataKinds.Geo.CONTENT_ITEM_TYPE, 0,
AdditionalDataKinds.Geo.VALUE,
contact, fieldsMap, ops);
}
}
protected void prepareSimpleStringField(String fieldValue, String mimetype,
int type, String dataColumn, Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String fieldId = createFieldId(mimetype, type);
if(fieldValue == null) {
// The server did not send this field, just ignore it
return;
} else if ("".equals(fieldValue)) {
if(fieldsMap != null) {
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
} else {
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE, mimetype);
builder = builder.withValue(dataColumn, fieldValue);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void prepareExtFields(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
List<XTag> xTags = contact.getXTags();
if(xTags == null) {
return;
}
for(XTag xTag : xTags) {
Property pTag = xTag.getXTag();
String pName = xTag.getXTagValue();
String pValue = pTag.getPropertyValueAsString();
prepareExtField(contact, pName, pValue, fieldsMap, ops);
}
}
protected void prepareExtField(Contact contact, String fieldName,
String fieldValue, HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
String fieldId = createFieldId(AdditionalDataKinds.Ext.CONTENT_ITEM_TYPE, fieldName);
if ("".equals(fieldValue)) {
if(fieldsMap != null) {
prepareRowDeletion(fieldsMap.get(fieldId), ops);
}
return;
} else {
ContentProviderOperation.Builder builder;
builder = prepareBuilder(contact.getId(), fieldId, fieldsMap, ops, false);
builder = builder.withValue(ContactsContract.Data.MIMETYPE,
AdditionalDataKinds.Ext.CONTENT_ITEM_TYPE);
builder = builder.withValue(AdditionalDataKinds.Ext.KEY, fieldName);
builder = builder.withValue(AdditionalDataKinds.Ext.VALUE, fieldValue);
ContentProviderOperation operation = builder.build();
ops.add(operation);
}
}
protected void prepareCustomFields(Contact contact,
HashMap<String,List<Integer>> fieldsMap,
List<ContentProviderOperation> ops)
{
// There is no custom fields to prepare
}
protected String createFieldId(String mimeType, int type) {
return createFieldId(mimeType, Integer.toString(type));
}
protected String createFieldId(String mimeType, String type) {
return createFieldId(new String[] { mimeType, type });
}
protected String createFieldId(Object[] values) {
if(values == null || values.length == 0) {
return "";
}
StringBuffer res = new StringBuffer();
Object value = values[0];
if(value != null) {
res.append(value.toString());
}
for(int i=1; i<values.length; i++) {
value = values[i];
if(value != null) {
res.append("-").append(values[i].toString());
}
}
return res.toString();
}
private boolean isFieldEmpty(String allFields[]) {
boolean empty = true;
for(int i=0;i<allFields.length;++i) {
String field = allFields[i];
if (!StringUtil.isNullOrEmpty(field)) {
empty = false;
break;
}
}
return empty;
}
private boolean isNull(Object objs[]) {
for(int i=0;i<objs.length;++i) {
if (objs[i] != null) {
return false;
}
}
return true;
}
protected void prepareRowDeletion(List<Integer> rows, List<ContentProviderOperation> ops) {
if (rows != null) {
ContentProviderOperation.Builder builder;
for(int rowId: rows) {
Uri uri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, rowId);
uri = addCallerIsSyncAdapterFlag(uri);
builder = ContentProviderOperation.newDelete(uri);
ops.add(builder.build());
}
}
}
protected Uri addCallerIsSyncAdapterFlag(Uri uri) {
if(callerIsSyncAdapter) {
Uri.Builder b = uri.buildUpon();
b.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true");
return b.build();
} else {
return uri;
}
}
/**
* @return
*/
protected Cursor getContactsCursor() {
String cols[] = {ContactsContract.RawContacts._ID};
StringBuffer whereClause = new StringBuffer();
if (accountName != null) {
whereClause.append(ContactsContract.RawContacts.ACCOUNT_NAME).append("='").append(accountName).append("'");
whereClause.append(" AND ");
whereClause.append(ContactsContract.RawContacts.ACCOUNT_TYPE).append("='").append(accountType).append("'");
whereClause.append(" AND ");
}
whereClause.append(ContactsContract.RawContacts.DELETED).append("=").append("0");
Cursor peopleCur = resolver.query(ContactsContract.RawContacts.CONTENT_URI,
cols, whereClause.toString(), null, null);
return peopleCur;
}
protected void deriveFieldFromProperty(com.funambol.syncml.protocol.Property property, Vector supportedFields,
boolean includeBasicProperty) {
if ("PHOTO".equals(property.getPropName())) {
supportedFields.addElement("PHOTO");
} else if ("BEGIN".equals(property.getPropName()) ||
"END".equals(property.getPropName()) ||
"VERSION".equals(property.getPropName())) {
// These are not fields, just VCard metadata, ignore them
} else if ("TEL".equals(property.getPropName())) {
// There is a mismatch here, between the caps and what the client
// does. If we change it we shall update the automatic tests
// accordingly
super.deriveFieldFromProperty(property, supportedFields, false);
} else if ("EMAIL".equals(property.getPropName())) {
// There is a mismatch here, between the caps and what the client
// does. If we change it we shall update the automatic tests
// accordingly
super.deriveFieldFromProperty(property, supportedFields, false);
} else {
super.deriveFieldFromProperty(property, supportedFields, includeBasicProperty);
}
}
private void prepareSourceIdAndDirtyFlagOperation(long contactId, ArrayList<ContentProviderOperation> ops) {
if(Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Refreshing source id and dirty flag for contact: " + contactId);
}
Uri uri = ContentUris.withAppendedId(RAW_CONTACT_URI, contactId);
uri = addCallerIsSyncAdapterFlag(uri);
ContentProviderOperation.Builder builder;
builder = ContentProviderOperation.newUpdate(uri);
builder.withValue(ContactsContract.RawContacts.SOURCE_ID, FUNAMBOL_SOURCE_ID_PREFIX + contactId);
builder.withValue(ContactsContract.RawContacts.DIRTY, 0);
ops.add(builder.build());
}
/**
* Prepare a hard delete query for the contact in the store
* @param rawContactId
*/
private void prepareHardDelete(long rawContactId) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Preparing to hard delete contact: " + rawContactId);
}
// Delete from raw_contacts (related rows in Data table are
// automatically deleted)
Uri uri = addCallerIsSyncAdapterFlag(RAW_CONTACT_URI);
uri = ContentUris.withAppendedId(uri, rawContactId);
ContentProviderOperation.Builder builder;
builder = ContentProviderOperation.newDelete(uri);
ops.add(builder.build());
}
private void commitSingleBatch() throws IOException {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "commitSingleBatch " + ops.size());
}
// Now perform all the operations in one shot
try {
if (ops.size() > 0) {
ContentProviderResult[] res = resolver.applyBatch(ContactsContract.AUTHORITY, ops);
ArrayList<ContentProviderOperation> ops2 = new ArrayList<ContentProviderOperation>();
for(int i=0;i<rawContactIdx.size();++i) {
int idx = rawContactIdx.get(i).intValue();
if (res[idx].uri == null) {
// This item was not properly inserted. Mark this as an error
// (A zero length key is the marker)
if (Log.isLoggable(Log.INFO)) {
Log.info(TAG_LOG, "Cannot find uri for inserted item, will be marked as failed");
}
newKeys.addElement("");
continue;
}
long id = ContentUris.parseId(res[idx].uri);
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "The new contact has id: " + id);
}
if(callerIsSyncAdapter) {
prepareSourceIdAndDirtyFlagOperation(id, ops2);
}
newKeys.addElement("" + id);
}
// Apply all the operations to set the sourceId and the dirty flag
if (ops2.size() > 0) {
if (Log.isLoggable(Log.DEBUG)) {
Log.debug(TAG_LOG, "Clearing dirty flag " + callerIsSyncAdapter);
}
resolver.applyBatch(ContactsContract.AUTHORITY, ops2);
}
}
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot commit to database", e);
throw new IOException("Cannot create contact in db");
} finally {
ops.clear();
rawContactIdx.clear();
}
}
}