/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* src/com/vodafone360/people/VODAFONE.LICENSE.txt or
* http://github.com/360/360-Engine-for-Android
* See the License for the specific language governing permissions and
* limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2010 Vodafone Sales & Services Ltd. All rights reserved.
* Use is subject to license terms.
*/
package com.vodafone360.people.engine.contactsync;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import android.text.TextUtils;
import com.vodafone360.people.datatypes.VCardHelper;
import com.vodafone360.people.engine.contactsync.NativeContactsApi.Account;
import com.vodafone360.people.utils.DynamicArrayLong;
import com.vodafone360.people.utils.LogUtils;
import com.vodafone360.people.utils.VersionUtils;
/**
* The NativeImporter class is responsible for importing contacts from the
* native address book to the people database. To do so, it compares the native
* address book and the people database to find new changes and update the
* people database accordingly. Usage: - instantiate the class - call the tick()
* method until isDone() returns true - current progress can be obtained via
* getPosition() / getCount() - check the getResult() to get a status (OK,
* KO...)
*/
public class NativeImporter {
/**
* The undefined result when the NativeImporter has not been run yet.
*
* @see #getResult()
*/
public final static int RESULT_UNDEFINED = -1;
/**
* The ok result when the NativeImporter has finished successfully.
*
* @see #getResult()
*/
public final static int RESULT_OK = 0;
/**
* The undefined result when the NativeImporter has not been run yet.
*
* @see #getResult()
*/
public final static int RESULT_ERROR = 1;
/**
* Number of contacts to be processed "per tick". This is the start value
* and is adapted to get as close as possible to TARGET_TIME_PER_TICK
*
* @see #tick()
*/
private final static float CONTACTS_PER_TICK_START = 2.0F;
/**
* Ideal processing time per tick in ms
*/
private final static float TARGET_TIME_PER_TICK = 200.0F;
/**
* Contacts to process in one tick. This float will be rounded.
*/
private float mContactsPerTick = CONTACTS_PER_TICK_START;
/**
* Handler to the People Contacts API.
*/
private PeopleContactsApi mPeopleContactsApi;
/**
* Handler to the Native Contacts API.
*/
private NativeContactsApi mNativeContactsApi;
/**
* Internal state representing the task to perform: gets the list of native
* contacts ids and people contacts ids.
*/
private final static int STATE_GET_IDS_LISTS = 0;
/**
* Internal state representing the task to perform: iterates through the
* list of ids to find differences (i.e. added contact, modified contact,
* deleted contact).
*/
private final static int STATE_ITERATE_THROUGH_IDS = 1;
/**
* Internal state representing the task to perform: process the list of
* deleted contacts (i.e. delete the contacts from the people database).
* TODO: remove this state
*/
private final static int STATE_PROCESS_DELETED = 2;
/**
* Internal state representing the task to perform: final state, nothing
* else to perform.
*/
private final static int STATE_DONE = 3;
/**
* The current state.
*/
private int mState = STATE_GET_IDS_LISTS;
/**
* The list of native ids from the native side.
*/
private long[] mNativeContactsIds;
/**
* The list of native ids from the people side.
*/
private long[] mPeopleNativeContactsIds;
/**
* The total count of ids to process (native database + people database).
*/
private int mTotalIds = 0;
/**
* The current count of processed ids.
*/
private int mProcessedIds = 0;
/**
* The index of the current native id.
*
* @see #mNativeContactsIds
*/
private int mCurrentNativeIndex = 0;
/**
* The index in the current people id.
*
* @see #mPeopleNativeContactsIds
*/
private int mCurrentPeopleIndex = 0;
/**
* The index of the current deleted people id.
*
* @see #mDeletedIds
*/
private int mCurrentDeletedIndex = 0;
/**
* Array to store the people ids of contacts to delete.
*/
private DynamicArrayLong mDeletedIds = new DynamicArrayLong(10);
/**
* The result status.
*/
private int mResult = RESULT_UNDEFINED;
/**
* Instance of a ContactChange comparator.
*/
private NCCComparator mNCCC = new NCCComparator();
/**
* The array of accounts from where to import the native contacts. Note: if
* null, will import from the platform default account.
*/
private Account[] mAccounts = null;
/**
* Boolean that tracks if the first time Import for 2.X is ongoing.
*/
private boolean mIsFirstImportOn2X = false;
/**
* Constructor.
*
* @param pca handler to the People contacts Api
* @param nca handler to the Native contacts Api
* @param firstTimeImport true if we import from native for the first time,
* false if not
*/
public NativeImporter(PeopleContactsApi pca, NativeContactsApi nca, boolean firstTimeImport) {
mPeopleContactsApi = pca;
mNativeContactsApi = nca;
initAccounts(firstTimeImport);
}
/**
* Sets the accounts used to import the native contacs.
*/
private void initAccounts(boolean firstTimeImport) {
/**
* In case of Android 2.X, the accounts used are different depending if
* it's first time sync or not. On Android 1.X, we can just ignore the
* accounts logic as not supported by the platform. At first time sync,
* we need to import the native contacts from all the Google accounts.
* These native contacts are then stored in the 360 People account and
* native changes will be only detected from the 360 People account.
*/
if (VersionUtils.is2XPlatform()) {
// account to import from: 360 account if created, all the google
// accounts otherwise
if (firstTimeImport) {
ArrayList<Account> accountList = new ArrayList<Account>();
/**
* PAND-2125
* The decision has been made to stop the import of Google contacts on 2.x devices.
* From now on, we will only import native addressbook contacts.
*/
/*
Account googleAccounts[] = mNativeContactsApi.getAccountsByType(NativeContactsApi.GOOGLE_ACCOUNT_TYPE);
if (googleAccounts!=null ){
for (Account account : googleAccounts) {
accountList.add(account);
}
}
*/
Account phoneAccounts[] = mNativeContactsApi.getAccountsByType(NativeContactsApi.PHONE_ACCOUNT_TYPE);
if (phoneAccounts!=null){
for (Account account : phoneAccounts) {
accountList.add(account);
}
}
mAccounts = accountList.toArray(new Account[0]);
} else {
mAccounts = mNativeContactsApi
.getAccountsByType(NativeContactsApi.PEOPLE_ACCOUNT_TYPE);
}
if (firstTimeImport)
mIsFirstImportOn2X = true;
}
}
/**
* Sets the internal state to DONE with the provided result status.
*
* @param result the result status to set
*/
private void complete(int result) {
mState = STATE_DONE;
mResult = result;
}
/**
* Tick method to call each time there is time for processing the native
* contacts import. Note: the method will block for some time to process a
* certain amount of contact then will return. It will have to be called
* until it returns true meaning that the import is over.
*
* @return true when the import task is finished, false if not
*/
public boolean tick() {
long startTime = System.currentTimeMillis();
switch (mState) {
case STATE_GET_IDS_LISTS:
getIdsLists();
break;
case STATE_ITERATE_THROUGH_IDS:
iterateThroughNativeIds();
break;
case STATE_PROCESS_DELETED:
processDeleted();
break;
}
long processingTime = System.currentTimeMillis() - startTime;
float factor = TARGET_TIME_PER_TICK / processingTime;
mContactsPerTick = Math.max(1.0F, mContactsPerTick * factor);
LogUtils.logD("NativeImporter.tick(): Tick took " + processingTime + "ms, applying factor "+ factor + ". Contacts per tick: " + mContactsPerTick);
return isDone();
}
/**
* Returns the import state.
*
* @return true if the import is finished, false if not
*/
public boolean isDone() {
return mState == STATE_DONE;
}
/**
* Gets the import result.
*
* @see #RESULT_OK
* @see #RESULT_ERROR
* @see #RESULT_UNDEFINED
* @return the import result
*/
public int getResult() {
return mResult;
}
/**
* Gets the current position in the list of ids. This can be used to track
* the current progress.
*
* @see #getCount()
* @return the last processed id position in the list of ids
*/
public int getPosition() {
return mProcessedIds;
}
/**
* Gets the total number of ids to process.
*
* @return the number of ids to process
*/
public int getCount() {
return mTotalIds;
}
/**
* Gets the list of native and people contacts ids.
*/
private void getIdsLists() {
LogUtils.logD("NativeImporter.getIdsLists()");
// Get the list of native ids for the contacts
if (mAccounts == null || 0 == mAccounts.length) {
// default account
LogUtils.logD("NativeImporter.getIdsLists() - using default account");
mNativeContactsIds = mNativeContactsApi.getContactIds(null);
} else if (mAccounts.length == 1) {
// one account
LogUtils.logD("NativeImporter.getIdsLists() - one account found: " + mAccounts[0]);
mNativeContactsIds = mNativeContactsApi.getContactIds(mAccounts[0]);
} else {
// we need to merge the ids from different accounts and sort them
final DynamicArrayLong allIds = new DynamicArrayLong();
LogUtils.logD("NativeImporter.getIdsLists() - more than one account found.");
for (int i = 0; i < mAccounts.length; i++) {
LogUtils.logD("NativeImporter.getIdsLists() - account=" + mAccounts[i]);
final long[] ids = mNativeContactsApi.getContactIds(mAccounts[i]);
if (ids != null) {
allIds.add(ids);
}
}
mNativeContactsIds = allIds.toArray();
// sort the ids
// TODO: as the arrays to merge are sorted, consider merging while
// keeping the sorting
// which is faster than sorting them afterwards
if (mNativeContactsIds != null) {
Arrays.sort(mNativeContactsIds);
}
}
// check if we have some work to do
if (mNativeContactsIds == null) {
complete(RESULT_OK);
return;
}
// Get a list of native ids for the contacts we have in the People
// database
mPeopleNativeContactsIds = mPeopleContactsApi.getNativeContactsIds();
mTotalIds = mNativeContactsIds.length;
if (mPeopleNativeContactsIds != null) {
mTotalIds += mPeopleNativeContactsIds.length;
}
mState = STATE_ITERATE_THROUGH_IDS;
}
/**
* Iterates through the list of native and People ids to detect changes.
*/
private void iterateThroughNativeIds() {
LogUtils.logD("NativeImporter.iterateThroughNativeIds()");
final int limit = Math.min(mNativeContactsIds.length, mCurrentNativeIndex
+ Math.round(mContactsPerTick));
// TODO: remove the deleted state / queuing to deleted ids array and
// loop with while (mProcessedIds < limit)
while (mCurrentNativeIndex < limit) {
if (mPeopleNativeContactsIds == null) {
// no native contacts on people side, just add it
LogUtils.logD("NativeImporter.iterateThroughNativeIds(): found a new contact");
addNewContact(mNativeContactsIds[mCurrentNativeIndex]);
mProcessedIds++;
} else {
// both ids lists are ordered by ascending ids so
// every people ids that are before the current native ids
// are simply deleted contacts
while ((mCurrentPeopleIndex < mPeopleNativeContactsIds.length)
&& (mPeopleNativeContactsIds[mCurrentPeopleIndex] < mNativeContactsIds[mCurrentNativeIndex])) {
LogUtils
.logD("NativeImporter.iterateThroughNativeIds(): found a contact to delete");
mDeletedIds.add(mPeopleNativeContactsIds[mCurrentPeopleIndex++]);
}
if (mCurrentPeopleIndex == mPeopleNativeContactsIds.length
|| mPeopleNativeContactsIds[mCurrentPeopleIndex] > mNativeContactsIds[mCurrentNativeIndex]) {
// has to be a new contact
LogUtils.logD("NativeImporter.iterateThroughNativeIds(): found a new contact");
addNewContact(mNativeContactsIds[mCurrentNativeIndex]);
mProcessedIds++;
} else {
// has to be an existing contact or one that will be deleted
LogUtils
.logD("NativeImporter.iterateThroughNativeIds(): check existing contact");
checkExistingContact(mNativeContactsIds[mCurrentNativeIndex]);
mProcessedIds++;
mCurrentPeopleIndex++;
}
}
mCurrentNativeIndex++;
}
// check if we are done with ids list from native
if (mCurrentNativeIndex == mNativeContactsIds.length) {
// we've gone through the native list, any remaining ids from the
// people list are deleted ones
if (mPeopleNativeContactsIds != null) {
while (mCurrentPeopleIndex < mPeopleNativeContactsIds.length) {
LogUtils
.logD("NativeImporter.iterateThroughNativeIds(): found a contact to delete");
mDeletedIds.add(mPeopleNativeContactsIds[mCurrentPeopleIndex++]);
}
}
if (mDeletedIds.size() != 0) {
// Some deleted contacts to handle
mState = STATE_PROCESS_DELETED;
} else {
// Nothing else to do
complete(RESULT_OK);
}
}
}
/**
* Deletes the contacts that were added the deleted array.
*/
private void processDeleted() {
LogUtils.logD("NativeImporter.processDeleted()");
final int limit = Math.min(mDeletedIds.size(), mCurrentDeletedIndex
+ Math.round(mContactsPerTick));
while (mCurrentDeletedIndex < limit) {
// we now delete the contacts on people client side
// on the 2.X platform, the contact deletion has to be synced back
// to native once completed because the
// contact is still there on native, just marked as deleted and
// waiting for its explicit removal
mPeopleContactsApi.deleteNativeContact(mDeletedIds.get(mCurrentDeletedIndex++),
VersionUtils.is2XPlatform());
mProcessedIds++;
}
if (mCurrentDeletedIndex == mDeletedIds.size()) {
complete(RESULT_OK);
}
}
/**
* Adds a new contact to the people database.
*/
private void addNewContact(long nativeId) {
// get the contact data
final ContactChange[] contactChanges = mNativeContactsApi.getContact(nativeId);
if (contactChanges != null) {
if (mIsFirstImportOn2X) {
// Override the nativeContactId with an invalid id if we are on
// 2.X
// and we are doing a first time import because the native id
// does not correspond
// to the id from the 360 People account where we will export.
removeNativeIds(contactChanges);
} else {
// Force a nativeDetailId to details that have none so that the
// comparison
// later can be made (see computeDelta method).
forceNabDetailId(contactChanges);
}
// add the contact to the People database
if (!mPeopleContactsApi.addNativeContact(contactChanges)) {
// TODO: Handle the error case !!! Well how should we handle it:
// fail for all remaining contacts or skip the failing contact?
LogUtils
.logE("NativeImporter.addNewContact() - failed to import native contact id="
+ nativeId);
}
}
}
/**
* Check changes between an existing contact on both native and people
* database.
*
* @param nativeId the native id of the contact to check
*/
private void checkExistingContact(long nativeId) {
// get the native version of that contact
final ContactChange[] nativeContact = mNativeContactsApi.getContact(nativeId);
// get the people version of that contact
final ContactChange[] peopleContact = mPeopleContactsApi.getContact((int)nativeId);
if (peopleContact == null) {
// we shouldn't be in that situation but nothing to do about it
// this means that there were some changes in the meantime between
// getting the ids list and now
return;
} else if (nativeContact == null) {
LogUtils
.logD("NativeImporter.checkExistingContact(): found a contact marked as deleted");
// this is a 2.X specific case meaning that the contact is marked as
// deleted on native side
// and waiting for the "syncAdapter" to perform a real delete
mDeletedIds.add(nativeId);
} else {
// general case, find the delta
final ContactChange[] delta = computeDelta(peopleContact, nativeContact);
if (delta != null) {
// update CAB with delta changes
mPeopleContactsApi.updateNativeContact(delta);
}
}
}
/**
* Native ContactChange Comparator class. This class compares ContactChange
* and tells which one is greater depending on the key and then the native
* detail id.
*/
private static class NCCComparator implements Comparator<ContactChange> {
@Override
public int compare(ContactChange change1, ContactChange change2) {
// an integer < 0 if object1 is less than object2, 0 if they are
// equal, and > 0 if object1 is greater than object2.
final int key1 = change1.getKey();
final int key2 = change2.getKey();
if (key1 < key2) {
return -1;
} else if (key1 > key2) {
return 1;
} else {
// the keys are identical, check the native ids
final long id1 = change1.getNabDetailId();
final long id2 = change2.getNabDetailId();
if (id1 < id2) {
return -1;
} else if (id1 > id2) {
return 1;
} else {
return 0;
}
}
}
}
/**
* Sorts the provided array of ContactChange by key and native id. Note: the
* method will rearrange the provided array.
*
* @param changes the ContactChange array to sort
*/
private void sortContactChanges(ContactChange[] changes) {
if ((changes != null) && (changes.length > 1)) {
Arrays.sort(changes, mNCCC);
}
}
/**
* Computes the difference between the provided arrays of ContactChange. The
* delta are the changes to apply to the master ContactChange array to make
* it similar to the new changes ContactChange array. (i.e. delete details,
* add details or modify details) NOTE: to help the GC, some provided
* ContactChange may be modified and returned by the method instead of
* creating new ones.
*
* @param masterChanges the master array of ContactChange (i.e. the original
* one)
* @param newChanges the new ContactChange array (i.e. contains the new
* version of a contact)
* @return null if there are no changes or the contact is being deleted, an
* array for ContactChange to apply to the master contact if
* differences where found
*/
private ContactChange[] computeDelta(ContactChange[] masterChanges, ContactChange[] newChanges) {
LogUtils.logD("NativeImporter.computeDelta()");
// set the native contact id to details that don't have a native detail
// id for the comparison as
// this is also done on people database side
forceNabDetailId(newChanges);
// sort the changes by key then native detail id
sortContactChanges(masterChanges);
sortContactChanges(newChanges);
// if the master contact is being deleted, ignore new changes
if ((masterChanges[0].getType() & ContactChange.TYPE_DELETE_CONTACT) == ContactChange.TYPE_DELETE_CONTACT) {
return null;
}
// details comparison, skip deleted master details
final ContactChange[] deltaChanges = new ContactChange[masterChanges.length
+ newChanges.length];
int deltaIndex = 0;
int masterIndex = 0;
int newIndex = 0;
while (newIndex < newChanges.length) {
while ((masterIndex < masterChanges.length)
&& (mNCCC.compare(masterChanges[masterIndex], newChanges[newIndex]) < 0)) {
final ContactChange masterChange = masterChanges[masterIndex];
if ((masterChange.getType() & ContactChange.TYPE_DELETE_DETAIL) != ContactChange.TYPE_DELETE_DETAIL
&& (masterChange.getNabDetailId() != ContactChange.INVALID_ID)
&& (isContactChangeKeySupported(masterChange))) {
// this detail does not exist anymore, is not being deleted
// and was synced to native
// check if it can be deleted (or has to be updated)
setDetailForDeleteOrUpdate(masterChanges, masterIndex);
deltaChanges[deltaIndex++] = masterChanges[masterIndex];
}
masterIndex++;
}
if ((masterIndex < masterChanges.length)
&& (mNCCC.compare(newChanges[newIndex], masterChanges[masterIndex]) == 0)) {
// similar key and id, check for differences at value level and
// flags
final ContactChange masterDetail = masterChanges[masterIndex];
final ContactChange newDetail = newChanges[newIndex];
boolean different = false;
if (masterDetail.getFlags() != newDetail.getFlags()) {
different = true;
}
if (!areContactChangeValuesEqualsPlusFix(masterChanges, masterIndex, newChanges,
newIndex)) {
different = true;
}
if (different) {
// found a detail to update
LogUtils.logD("NativeImporter.computeDelta() - found a detail to update");
newDetail.setType(ContactChange.TYPE_UPDATE_DETAIL);
newDetail.setInternalContactId(masterDetail.getInternalContactId());
newDetail.setInternalDetailId(masterDetail.getInternalDetailId());
deltaChanges[deltaIndex++] = newDetail;
}
masterIndex++;
} else {
LogUtils.logD("NativeImporter.computeDelta() - found a detail to add");
// this is a new detail
newChanges[newIndex].setType(ContactChange.TYPE_ADD_DETAIL);
newChanges[newIndex].setInternalContactId(masterChanges[0].getInternalContactId());
deltaChanges[deltaIndex++] = newChanges[newIndex];
}
newIndex++;
}
while (masterIndex < masterChanges.length) {
final ContactChange masterChange = masterChanges[masterIndex];
if ((masterChange.getType() & ContactChange.TYPE_DELETE_DETAIL) != ContactChange.TYPE_DELETE_DETAIL
&& (masterChange.getNabDetailId() != ContactChange.INVALID_ID)
&& (isContactChangeKeySupported(masterChange))) {
// this detail does not exist anymore, is not being deleted and
// was synced to native
// check if it can be deleted (or has to be updated)
setDetailForDeleteOrUpdate(masterChanges, masterIndex);
deltaChanges[deltaIndex++] = masterChanges[masterIndex];
}
masterIndex++;
}
if (deltaIndex == 0) {
// the contact has not changed
return null;
} else if (deltaChanges.length == deltaIndex) {
// give the detail changes
return deltaChanges;
} else {
// give the detail changes but need to trim
final ContactChange[] trim = new ContactChange[deltaIndex];
System.arraycopy(deltaChanges, 0, trim, 0, deltaIndex);
return trim;
}
}
/**
* Removes the native ids in case of a first time import on the Android 2.X
* platform.
*
* @param contact the contact to clear from native ids
*/
private void removeNativeIds(ContactChange[] contact) {
if (contact != null) {
final int count = contact.length;
for (int i = 0; i < count; i++) {
final ContactChange change = contact[i];
change.setNabContactId(ContactChange.INVALID_ID);
change.setNabDetailId(ContactChange.INVALID_ID);
}
}
}
/**
* Forces a native detail id onto a detail that does not have one by setting
* it to be the native contact id.
*
* @param contact the contact to fix
*/
private void forceNabDetailId(ContactChange[] contact) {
if (contact != null && contact.length > 0) {
final long nativeContactId = contact[0].getNabContactId();
final int count = contact.length;
for (int i = 0; i < count; i++) {
final ContactChange change = contact[i];
if (change.getNabDetailId() == ContactChange.INVALID_ID) {
change.setNabDetailId(nativeContactId);
}
}
}
}
/*
* Below this point, all the defined methods where added to support the
* specific case of KEY_VCARD_ORG on Android 1.X platform. KEY_VCARD_ORG is
* defined as followed: "company;department1;department2;...;departmentX" It
* is a special case because this VCard key on its own is not fully
* supported on the Android 1.X platform: only "company" information is.
*/
/**
* Tells whether or not a master change is equal to a new change and
* performs some modifications on the provided new change in the specific
* case of KEY_VCARD_ORG on Android 1.X.
*
* @param masterChanges the array of master changes
* @param masterIndex the index of the change within the array of master
* changes
* @param newChanges the array of new changes
* @param newIndex the index of the change within the array of new changes
* @return true if the changes are equals, false otherwise
*/
private boolean areContactChangeValuesEqualsPlusFix(ContactChange[] masterChanges,
int masterIndex, ContactChange[] newChanges, int newIndex) {
final ContactChange masterChange = masterChanges[masterIndex];
final ContactChange newChange = newChanges[newIndex];
if (VersionUtils.is2XPlatform() || masterChange.getKey() != ContactChange.KEY_VCARD_ORG) {
// general case
return TextUtils.equals(masterChange.getValue(), newChange.getValue());
} else {
// this is a special case of Android 1.X where we have to parse the
// value
// in case of Organization key
final String peopleCompValue = VCardHelper.parseCompanyFromOrganization(masterChange
.getValue());
final String nativeCompValue = VCardHelper.parseCompanyFromOrganization(newChange
.getValue());
// on 1.X, we only need to compare the company name as
// department is not supported by the platform
final boolean areEquals = TextUtils.equals(peopleCompValue, nativeCompValue);
if (!areEquals) {
// there is a difference so master change will be updated
// we need to preserve the master department values if any
final String masterDepartments = VCardHelper
.splitDepartmentsFromOrganization(masterChange.getValue());
final ContactChange fixedNewChange = newChange.copyWithNewValue(nativeCompValue
+ masterDepartments);
newChanges[newIndex] = fixedNewChange;
}
return areEquals;
}
}
/**
* Sets a detail as to be deleted or updated. Note: in the general case, the
* detail has to be deleted but in the specific situation of KEY_VCARD_ORG
* and Android 1.X, it may have to be updated instead
*
* @param contactChanges the array of ContactChange
* @param index the index of the ContactChange to set in the array of
* ContactChange
*/
private void setDetailForDeleteOrUpdate(ContactChange[] contactChanges, int index) {
final ContactChange contactChange = contactChanges[index];
if (VersionUtils.is2XPlatform() || contactChange.getKey() != ContactChange.KEY_VCARD_ORG) {
// general case, just set the details as deleted
contactChange.setType(ContactChange.TYPE_DELETE_DETAIL);
LogUtils
.logD("NativeImporter.checkDetailForDeleteOrUpdate() - found a detail to delete");
} else {
// this is a special case of Android 1.X
// we have to set this change to an update if the change
// contains departments
final String masterDepartments = VCardHelper
.splitDepartmentsFromOrganization(contactChange.getValue());
if (!VCardHelper.isEmptyVCardValue(masterDepartments)) {
// delete the company name and keep the departments
contactChange.setType(ContactChange.TYPE_UPDATE_DETAIL);
contactChanges[index] = contactChange.copyWithNewValue(masterDepartments);
LogUtils
.logD("NativeImporter.checkDetailForDeleteOrUpdate() - found a detail to update");
} else {
contactChange.setType(ContactChange.TYPE_DELETE_DETAIL);
LogUtils
.logD("NativeImporter.checkDetailForDeleteOrUpdate() - found a detail to delete");
}
}
}
/**
* Determines whether or not a ContactChange key is supported on native
* side. Note: it also handle the non generic case of KEY_VCARD_ORG on
* Android 1.X
*
* @param change the ContactChange to check
* @return true if supported, false if not
*/
private boolean isContactChangeKeySupported(ContactChange change) {
final boolean isSupported = mNativeContactsApi.isKeySupported(change.getKey());
if (VersionUtils.is2XPlatform() || change.getKey() != ContactChange.KEY_VCARD_ORG) {
return isSupported;
} else if (isSupported) {
// KEY_VCARD_ORG has the following value:
// "company;department1;department2..."
// in case of KEY_VCARD_ORG on Android 1.X, we have to check the
// support of the key
// at the company level of the VCard value because the departments
// are not supported
// so if there is only the department, we have to return false
// instead of true.
final String changeCompValue = VCardHelper.parseCompanyFromOrganization(change
.getValue());
if (!VCardHelper.isEmptyVCardValue(changeCompValue)) {
return true;
}
}
return false;
}
}