/*
* 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.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.accounts.AccountManager;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.text.TextUtils;
import android.util.SparseArray;
import com.vodafone360.people.R;
import com.vodafone360.people.datatypes.VCardHelper;
import com.vodafone360.people.datatypes.VCardHelper.Name;
import com.vodafone360.people.datatypes.VCardHelper.Organisation;
import com.vodafone360.people.datatypes.VCardHelper.PostalAddress;
import com.vodafone360.people.service.SyncAdapter;
import com.vodafone360.people.utils.CursorUtils;
import com.vodafone360.people.utils.LogUtils;
import com.vodafone360.people.utils.VersionUtils;
/**
* The implementation of the NativeContactsApi for the Android 2.X platform.
*/
public class NativeContactsApi2 extends NativeContactsApi {
/**
* Convenience Projection to fetch only a Raw Contact's ID and Native
* Account Type
*/
private static final String[] CONTACTID_PROJECTION = new String[] {
RawContacts._ID, RawContacts.ACCOUNT_TYPE
};
/**
* Raw ID Column for the CONTACTID_PROJECTION Projection
*/
private static final int CONTACTID_PROJECTION_RAW_ID = 0;
/**
* Account Type Column for the CONTACTID_PROJECTION Projection
*/
private static final int CONTACTID_PROJECTION_ACCOUNT_TYPE = 1;
/**
* Group ID Projection
*/
private static final String[] GROUPID_PROJECTION = new String[] {
Groups._ID
};
/**
* Vendor specific account. Only used in 2.x API.
*/
private static Account sPhoneAccount = null;
/**
* Regular expression for a date that can be handled by the People Client at
* present. Matches the following cases: N-n-n n-n-N Where: - 'n'
* corresponds to one or two digits - 'N' corresponds to two or 4 digits
*/
public static final String COMPLETE_DATE_REGEX =
"(\\d{1,4}-\\d{1,2}-\\d{1,2}|\\d{1,2}-\\d{1,2}-\\d{1,4})";
/**
* 'My Contacts' System group where clause
*/
private static final String MY_CONTACTS_GROUP_WHERE_CLAUSE = Groups.SYSTEM_ID + "=\"Contacts\"";
/**
* Selection where clause for a NULL Account
*/
private static final String NULL_ACCOUNT_WHERE_CLAUSE = RawContacts.ACCOUNT_NAME
+ " IS NULL AND " + RawContacts.ACCOUNT_TYPE + " IS NULL";
/**
* Selection where clause for an Organization detail.
*/
private static final String ORGANIZATION_DETAIL_WHERE_CLAUSE = RawContacts.Data.MIMETYPE
+ "=\"" + Organization.CONTENT_ITEM_TYPE + "\"";
/**
* The list of Uri that need to be listened to for contacts changes on
* native side.
*/
private static final Uri[] sUri = {
ContactsContract.RawContacts.CONTENT_URI, ContactsContract.Data.CONTENT_URI
};
/**
* Holds mapping from a NAB type (MIME) to a VCard Key
*/
private static final HashMap<String, Integer> sFromNabContentTypeToKeyMap;
/**
* Holds mapping from a VCard Key to a NAB type (MIME)
*/
private static final SparseArray<String> sFromKeyToNabContentTypeArray;
static {
sFromNabContentTypeToKeyMap = new HashMap<String, Integer>(9, 1);
sFromNabContentTypeToKeyMap.put(StructuredName.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_NAME);
sFromNabContentTypeToKeyMap.put(Nickname.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_NICKNAME);
sFromNabContentTypeToKeyMap.put(Phone.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_PHONE);
sFromNabContentTypeToKeyMap.put(Email.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_EMAIL);
sFromNabContentTypeToKeyMap.put(StructuredPostal.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_ADDRESS);
sFromNabContentTypeToKeyMap.put(Organization.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_ORG);
sFromNabContentTypeToKeyMap.put(Website.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_URL);
sFromNabContentTypeToKeyMap.put(Note.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_NOTE);
sFromNabContentTypeToKeyMap.put(Event.CONTENT_ITEM_TYPE, ContactChange.KEY_VCARD_DATE);
// sFromNabContentTypeToKeyMap.put(Photo.CONTENT_ITEM_TYPE, ContactChange.KEY_PHOTO);
sFromKeyToNabContentTypeArray = new SparseArray<String>(10);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_NAME, StructuredName.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_NICKNAME, Nickname.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_PHONE, Phone.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_EMAIL, Email.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_ADDRESS, StructuredPostal.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_ORG, Organization.CONTENT_ITEM_TYPE);
// Special case: VCARD_TITLE maps to the same NAB type as
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_TITLE, Organization.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_URL, Website.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_NOTE, Note.CONTENT_ITEM_TYPE);
sFromKeyToNabContentTypeArray.append(ContactChange.KEY_VCARD_DATE, Event.CONTENT_ITEM_TYPE);
// sFromKeyToNabContentTypeArray.append(ContactChange.KEY_PHOTO, Photo.CONTENT_ITEM_TYPE);
}
/**
* The observer registered by the upper layer.
*/
private ContactsObserver mAbstractedObserver;
/**
* Content values used for writing to NAB.
*/
private final ContentValues mValues = new ContentValues();
/**
* The content observers that listens for native contacts changes.
*/
private final ContentObserver[] mContentObservers = new ContentObserver[sUri.length];
/**
* Array of row ID in the groups table to the "My Contacts" System Group.
* The reason for this being an array is because there may be multiple
* "My Contacts" groups (Platform Bug!?).
*/
private long[] mMyContactsGroupRowIds = null;
/**
* Arguably another example where Organization and Title force us into extra
* effort. We use this value to pass the correct detail ID when an 'add
* detail' is done for one the two although the other is already present.
* Make sure to reset this value for every UpdateContact operation
*/
private long mExistingOrganizationId = ContactChange.INVALID_ID;
/**
* Flag to check if we have already read a Birthday detail
*/
private boolean mHaveReadBirthday = false;
/**
* Yield value for our ContentProviderOperations.
*/
private boolean mYield = true;
/**
* Batch used for Contact Writing operations.
*/
private BatchOperation mBatch = new BatchOperation();
/**
* Inner class for applying batches. TODO: Move to own class if batches
* become supported in other areas
*/
private class BatchOperation {
// List for storing the batch mOperations
ArrayList<ContentProviderOperation> mOperations;
/**
* Default constructor
*/
public BatchOperation() {
mOperations = new ArrayList<ContentProviderOperation>();
}
/**
* Current size of the batch
*
* @return Size of the batch
*/
public int size() {
return mOperations.size();
}
/**
* Adds a new operation to the batch
*
* @param cpo The
*/
public void add(ContentProviderOperation cpo) {
mOperations.add(cpo);
}
/**
* Clears all operations in the batch Effectively resets the batch.
*/
public void clear() {
mOperations.clear();
}
public ContentProviderResult[] execute() {
ContentProviderResult[] resultArray = null;
if (mOperations.size() > 0) {
// Apply the mOperations to the content provider
try {
resultArray = mCr.applyBatch(ContactsContract.AUTHORITY, mOperations);
} catch (final OperationApplicationException e1) {
LogUtils.logE("storing contact data failed", e1);
} catch (final RemoteException e2) {
LogUtils.logE("storing contact data failed", e2);
}
mOperations.clear();
}
return resultArray;
}
}
/**
* Convenience interface to map the generic DATA column names to the People
* profile detail column names.
*/
private interface PeopleProfileColumns {
/**
* 360 People profile MIME Type
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/com.vodafone360.people.profile";
/**
* 360 People Contact's profile ID (Corresponds to Contact's Internal
* Contact ID)
*/
public static final String DATA_PID = Data.DATA1;
/**
* 360 People Contact profile Summary
*/
public static final String DATA_SUMMARY = Data.DATA2;
/**
* 360 People Contact profile detail
*/
public static final String DATA_DETAIL = Data.DATA3;
}
/**
* This class holds a native content observer that will be notified in case
* of changes on the registered URI.
*/
private class NativeContentObserver extends ContentObserver {
public NativeContentObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
LogUtils.logI("NativeContactsApi2.NativeContentObserver.onChange(" + selfChange + ")");
mAbstractedObserver.onChange();
}
}
/**
* @see NativeContactsApi#registerObserver(ContactsObserver)
*/
@Override
public void registerObserver(ContactsObserver observer) {
LogUtils.logI("NativeContactsApi2.registerObserver()");
if (mAbstractedObserver != null) {
throw new RuntimeException(
"Only one observer at a time supported... Please unregister first.");
}
mAbstractedObserver = observer;
final ContentResolver cr = mContext.getContentResolver();
for (int i = 0; i < mContentObservers.length; i++) {
mContentObservers[i] = new NativeContentObserver();
cr.registerContentObserver(sUri[i], true, mContentObservers[i]);
}
}
/**
* @see NativeContactsApi#unregisterObserver(ContactsObserver)
*/
@Override
public void unregisterObserver() {
LogUtils.logI("NativeContactsApi2.unregisterObserver()");
if (mCr != null) {
mAbstractedObserver = null;
for (int i = 0; i < mContentObservers.length; i++) {
if (mContentObservers[i] != null) {
mCr.unregisterContentObserver(mContentObservers[i]);
mContentObservers[i] = null;
}
}
}
}
/**
* @see NativeContactsApi#initialize()
*/
@Override
protected void initialize() {
// perform here any one time initialization
sPhoneAccount = getVendorSpecificAccount();
}
/**
* @see NativeContactsApi#getAccounts()
*/
@Override
public Account[] getAccounts() {
AccountManager accountManager = AccountManager.get(mContext);
final android.accounts.Account[] accounts2xApi = accountManager.getAccounts();
Account[] accounts = null;
if (accounts2xApi.length > 0) {
accounts = new Account[accounts2xApi.length];
for (int i = 0; i < accounts2xApi.length; i++) {
accounts[i] = new Account(accounts2xApi[i].name, accounts2xApi[i].type);
}
}
return accounts;
}
/**
* Reads vendor specific accounts from settings and through accountmanager.
* Some phones with custom ui like sense have additional account that we
* have to take care of. This method tryes to read them
*
* @return Account object if vendor specific account is found, null
* otherwise
*/
public Account getVendorSpecificAccount() {
// first read the settings
String[] PROJECTION = {
Settings.ACCOUNT_NAME, Settings.ACCOUNT_TYPE, Settings.UNGROUPED_VISIBLE
};
// Get a cursor with all people
Cursor cursor = mCr.query(Settings.CONTENT_URI, PROJECTION, null, null, null);
// Got no cursor? Return with null!
if (null == cursor) {
return null;
}
try {
String[] values = new String[cursor.getCount()];
for (int i = 0; i < values.length; i++) {
cursor.moveToNext();
if (isVendorSpecificAccount(cursor.getString(1))) {
return new Account(cursor.getString(0), cursor.getString(1));
}
}
} catch (Exception exc) {
return null;
}
CursorUtils.closeCursor(cursor);
// nothing found in the settings? try accountmanager
Account[] accounts = getAccounts();
if (accounts == null) {
return null;
}
for (Account account : accounts) {
if (isVendorSpecificAccount(account.getType())) {
return account;
}
}
return null;
}
/**
* @see NativeContactsApi2#getAccountsByType(String)
*/
@Override
public Account[] getAccountsByType(int type) {
switch (type) {
// people and google type lead to the same block the difference is
// then handled inside in two if statements.
// Otherwise we would have a lot of redundant code,
case PEOPLE_ACCOUNT_TYPE:
case GOOGLE_ACCOUNT_TYPE: {
AccountManager accountMan = AccountManager.get(mContext);
android.accounts.Account[] accounts2xApi = null;
// For people account set the people account type string...
if (PEOPLE_ACCOUNT_TYPE == type) {
accounts2xApi = accountMan.getAccountsByType(PEOPLE_ACCOUNT_TYPE_STRING);
}
// .. and for google the same only with google string.
if (GOOGLE_ACCOUNT_TYPE == type) {
accounts2xApi = accountMan.getAccountsByType(GOOGLE_ACCOUNT_TYPE_STRING);
}
final int numAccounts = accounts2xApi.length;
if (numAccounts > 0) {
Account[] accounts = new Account[numAccounts];
for (int i = 0; i < numAccounts; i++) {
accounts[i] = new Account(accounts2xApi[i].name, accounts2xApi[i].type);
}
return accounts;
} else {
return null;
}
}
case PHONE_ACCOUNT_TYPE: {
if (sPhoneAccount == null) {
return null;
}
return new Account[] {
sPhoneAccount
};
}
default:
return null;
}
}
/**
* @see NativeContactsApi#addPeopleAccount(String)
*/
@Override
public boolean addPeopleAccount(String username) {
boolean isAdded = false;
try {
android.accounts.Account account = new android.accounts.Account(username,
PEOPLE_ACCOUNT_TYPE_STRING);
AccountManager accountMan = AccountManager.get(mContext);
isAdded = accountMan.addAccountExplicitly(account, null, null);
if (isAdded) {
if (VersionUtils.isHtcSenseDevice(mContext)) {
createSettingsEntryForAccount(username);
requestSyncAdapterInitialization(account);
}
// Need to do our Sync Adapter initialization logic here
SyncAdapter.initialize(account, ContactsContract.AUTHORITY);
}
} catch (Exception ex) {
LogUtils.logE("People Account creation failed because of exception:\n", ex);
}
// Commented out 'My Contacts' Group Row IDs call as it is not currently used
// if (isAdded) {
// // Updating MyContacts Group IDs here for now because it needs to be
// // done one time just before first time sync.
// // In the future, this code may change if we modify
// // the way we retrieve contact IDs.
// fetchMyContactsGroupRowIds();
// }
return isAdded;
}
/**
* @see NativeContactsApi#isPeopleAccountCreated()
*/
@Override
public boolean isPeopleAccountCreated() {
return getPeopleAccount() != null;
}
/**
* @see NativeContactsApi#removePeopleAccount()
*/
@Override
public void removePeopleAccount() {
AccountManager accountMan = AccountManager.get(mContext);
android.accounts.Account[] accounts = accountMan
.getAccountsByType(PEOPLE_ACCOUNT_TYPE_STRING);
if (accounts != null && accounts.length > 0) {
accountMan.removeAccount(accounts[0], null, null);
}
}
/**
* @see NativeContactsApi#getContactIds(Account)
*/
@Override
public long[] getContactIds(Account account) {
// Need to construct a where clause
if (account != null) {
final StringBuffer clauseBuffer = new StringBuffer();
clauseBuffer.append(RawContacts.ACCOUNT_NAME);
clauseBuffer.append("=\"");
clauseBuffer.append(account.getName());
clauseBuffer.append("\" AND ");
clauseBuffer.append(RawContacts.ACCOUNT_TYPE);
clauseBuffer.append("=\"");
clauseBuffer.append(account.getType());
clauseBuffer.append('\"');
return getContactIds(clauseBuffer.toString());
} else {
return getContactIds(NULL_ACCOUNT_WHERE_CLAUSE);
}
}
/**
* @see NativeContactsApi#getContact(long)
*/
@Override
public ContactChange[] getContact(long nabContactId) {
// Reset the boolean flags
mHaveReadOrganization = false;
mHaveReadBirthday = false;
final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, nabContactId);
final Uri dataUri = Uri.withAppendedPath(rawContactUri, RawContacts.Data.CONTENT_DIRECTORY);
final Cursor cursor = mCr.query(dataUri, null, null, null, null);
try {
if (cursor != null && cursor.getCount() > 0) {
final List<ContactChange> ccList = new ArrayList<ContactChange>();
while (cursor.moveToNext()) {
readDetail(cursor, ccList, nabContactId);
}
return ccList.toArray(new ContactChange[ccList.size()]);
}
} finally {
CursorUtils.closeCursor(cursor);
}
return null;
}
/**
* @see NativeContactsApi#addContact(Account, ContactChange[])
*/
@Override
public ContactChange[] addContact(Account account, ContactChange[] ccList) {
// Make sure to reset all the member variables we need
mYield = true;
mMarkedOrganizationIndex = mMarkedTitleIndex = -1;
mValues.clear();
mBatch.clear();
mValues.put(RawContacts.ACCOUNT_TYPE, account.getType());
mValues.put(RawContacts.ACCOUNT_NAME, account.getName());
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(
addCallerIsSyncAdapterParameter(RawContacts.CONTENT_URI)).withYieldAllowed(mYield)
.withValues(mValues);
mBatch.add(builder.build());
// according to the calling method ccList here has at least 1 element, ccList[0] is not null
final int ccListSize = ccList.length;
for (int i = 0; i < ccListSize; i++) {
final ContactChange cc = ccList[i];
if (cc != null) {
final int key = cc.getKey();
if (key == ContactChange.KEY_VCARD_ORG && mMarkedOrganizationIndex < 0) {
// Mark for later writing
mMarkedOrganizationIndex = i;
continue;
}
if (key == ContactChange.KEY_VCARD_TITLE && mMarkedTitleIndex < 0) {
// Mark for later writing
mMarkedTitleIndex = i;
continue;
}
putDetail(cc);
addValuesToBatch(ContactChange.INVALID_ID);
}
}
// Special case for the organization/title if it was flagged in the
// putDetail call
putOrganization(ccList);
addValuesToBatch(ContactChange.INVALID_ID);
// Add 360 profile detail
addProfileAction(ccList[0].getInternalContactId());
// Execute the batch and Generate ID changes from it
return executeNewContactBatch(ccList);
}
/**
* @see NativeContactsApi#updateContact(ContactChange[])
*/
@Override
public ContactChange[] updateContact(ContactChange[] ccList) {
if (ccList == null || ccList.length == 0) {
LogUtils.logW("NativeContactsApi2.updateContact() nothing to update - empty ccList!");
return null;
}
// Make sure to reset all the member variables we need
mYield = true;
mBatch.clear();
mValues.clear();
mMarkedOrganizationIndex = mMarkedTitleIndex = -1;
mExistingOrganizationId = ContactChange.INVALID_ID;
final long nabContactId = ccList[0].getNabContactId();
if (nabContactId == ContactChange.INVALID_ID) {
LogUtils
.logW("NativeContactsApi2.updateContact() Ignoring update request because of invalid NAB Contact ID. Internal Contact ID is "
+ ccList[0].getInternalContactId());
return null;
}
final int ccListSize = ccList.length;
for (int i = 0; i < ccListSize; i++) {
final ContactChange cc = ccList[i];
final int key = cc.getKey();
if (key == ContactChange.KEY_VCARD_ORG && mMarkedOrganizationIndex < 0) {
// Mark for later writing
mMarkedOrganizationIndex = i;
continue;
}
if (key == ContactChange.KEY_VCARD_TITLE && mMarkedTitleIndex < 0) {
// Mark for later writing
mMarkedTitleIndex = i;
continue;
}
switch (cc.getType()) {
case ContactChange.TYPE_ADD_DETAIL:
putDetail(cc);
addValuesToBatch(nabContactId); // not a new contact
break;
case ContactChange.TYPE_UPDATE_DETAIL:
if (cc.getNabDetailId() != ContactChange.INVALID_ID) {
putDetail(cc);
addUpdateValuesToBatch(cc.getNabDetailId());
} else {
LogUtils
.logW("NativeContactsApi2.updateContact() Ignoring update to detail for "
+ cc.getKeyToString()
+ " because of invalid NAB Detail ID. Internal Contact is "
+ cc.getInternalContactId()
+ ", Internal Detail Id is "
+ cc.getInternalDetailId());
}
break;
case ContactChange.TYPE_DELETE_DETAIL:
if (cc.getNabDetailId() != ContactChange.INVALID_ID) {
addDeleteDetailToBatch(cc.getNabDetailId());
} else {
LogUtils
.logW("NativeContactsApi2.updateContact() Ignoring detail deletion for "
+ cc.getKeyToString()
+ " because of invalid NAB Detail ID. Internal Contact is "
+ cc.getInternalContactId()
+ ", Internal Detail Id is "
+ cc.getInternalDetailId());
}
break;
default:
break;
}
}
updateOrganization(ccList, nabContactId);
// Execute the batch and Generate ID changes from it
return executeUpdateContactBatch(ccList);
}
/**
* @see NativeContactsApi#removeContact(long)
*/
@Override
public void removeContact(long nabContactId) {
mCr.delete(addCallerIsSyncAdapterParameter(ContentUris.withAppendedId(
RawContacts.CONTENT_URI, nabContactId)), null, null);
}
/**
* @see NativeContactsApi#getMasterSyncAutomatically
*/
@Override
public boolean getMasterSyncAutomatically() {
return ContentResolver.getMasterSyncAutomatically();
}
/**
* @see NativeContactsApi#setSyncable(boolean)
*/
@Override
public void setSyncable(boolean syncable) {
android.accounts.Account account = getPeopleAccount();
if(account != null) {
ContentResolver.
setIsSyncable(account, ContactsContract.AUTHORITY, syncable ? 1 : 0);
}
}
/**
* @see NativeContactsApi#setSyncAutomatically(boolean)
*/
@Override
public void setSyncAutomatically(boolean syncAutomatically) {
android.accounts.Account account = getPeopleAccount();
if(account != null) {
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, syncAutomatically);
if(syncAutomatically) {
// Kick start sync
ContentResolver.requestSync(account, ContactsContract.AUTHORITY, new Bundle());
} else {
// Cancel ongoing just in case
ContentResolver.cancelSync(account, ContactsContract.AUTHORITY);
}
}
}
/**
* @see NativeContactsApi#isKeySupported(int)
*/
@Override
public boolean isKeySupported(int key) {
// Only supported if in the SparseArray
return sFromKeyToNabContentTypeArray.indexOfKey(key) >= 0;
}
/**
* Inner (private) method for getting contact ids. Allows a cleaner
* separation the public API method.
*
* @param selection The where clause for the operation
* @return An array containing the Contact IDs that have been found
*/
private long[] getContactIds(final String selection) {
// Store ids in a temporary array because of possible null values
long[] tempIds = null;
int idCount = 0;
final Cursor cursor = mCr.query(RawContacts.CONTENT_URI, CONTACTID_PROJECTION, selection,
null, null);
if (cursor == null) {
return null;
}
try {
final int cursorCount = cursor.getCount();
if (cursorCount > 0) {
tempIds = new long[cursorCount];
while (cursor.moveToNext()) {
final long id = cursor.getLong(CONTACTID_PROJECTION_RAW_ID);
final String accountType = cursor.getString(CONTACTID_PROJECTION_ACCOUNT_TYPE);
// TODO: Remove hardcoding (if statement)
if (TextUtils.isEmpty(accountType)
|| accountType.equals(NativeContactsApi.PEOPLE_ACCOUNT_TYPE_STRING)
|| isVendorSpecificAccount(accountType)) {
//PAND-2125 || isContactInMyContactsGroup(id)) {
tempIds[idCount] = id;
idCount++;
}
}
}
} finally {
CursorUtils.closeCursor(cursor);
}
if (idCount > 0) {
// Here we copy the array to strip any eventual nulls at the end of
// the tempIds array
final long[] ids = new long[idCount];
System.arraycopy(tempIds, 0, ids, 0, idCount);
return ids;
}
return null;
}
/**
* Fetches the IDs corresponding to the "My Contacts" System Group The
* reason why we return an array is because there may be multiple
* "My Contacts" groups.
*
* @return Array containing all IDs of the "My Contacts" group or null if
* none exist
*/
private void fetchMyContactsGroupRowIds() {
final Cursor cursor = mCr.query(Groups.CONTENT_URI, GROUPID_PROJECTION,
MY_CONTACTS_GROUP_WHERE_CLAUSE, null, null);
if (cursor == null) {
return;
}
try {
final int count = cursor.getCount();
if (count > 0) {
mMyContactsGroupRowIds = new long[count];
for (int i = 0; i < count; i++) {
cursor.moveToNext();
mMyContactsGroupRowIds[i] = cursor.getLong(0);
}
}
} finally {
CursorUtils.closeCursor(cursor);
}
}
/**
* Checks if a Contact is in the "My Contacts" System Group
*
* @param nabContactId ID of the Contact to check
* @return true if the Contact is in the Group, false if not
*/
/**
* 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.
*/
/*
private boolean isContactInMyContactsGroup(long nabContactId) {
boolean belongs = false;
if (mMyContactsGroupRowIds != null) {
final Uri dataUri = Uri.withAppendedPath(ContentUris.withAppendedId(
RawContacts.CONTENT_URI, nabContactId), RawContacts.Data.CONTENT_DIRECTORY);
// build query containing row ID values
final StringBuilder sb = new StringBuilder();
sb.append(MY_CONTACTS_MULTI_GROUP_MEMBERSHIP);
final int rowIdCount = mMyContactsGroupRowIds.length;
for (int i = 0; i < rowIdCount; i++) {
sb.append(mMyContactsGroupRowIds[i]);
if (i < rowIdCount - 1) {
sb.append(',');
}
}
sb.append(')');
final Cursor cursor = mCr.query(dataUri, null, sb.toString(), null, null);
try {
belongs = cursor != null && cursor.getCount() > 0;
} finally {
CursorUtils.closeCursor(cursor);
}
}
return belongs;
}
*/
/**
* Reads a Contact Detail from a Cursor into the supplied Contact Change
* List.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to read Detail into
* @param nabContactId NAB ID of the Contact
*/
private void readDetail(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final String mimetype = CursorUtils.getString(cursor, Data.MIMETYPE);
final Integer key = sFromNabContentTypeToKeyMap.get(mimetype);
if (key != null) {
switch (key.intValue()) {
case ContactChange.KEY_VCARD_NAME:
readName(cursor, ccList, nabContactId);
break;
case ContactChange.KEY_VCARD_NICKNAME:
readNickname(cursor, ccList, nabContactId);
break;
case ContactChange.KEY_VCARD_PHONE:
readPhone(cursor, ccList, nabContactId);
break;
case ContactChange.KEY_VCARD_EMAIL:
readEmail(cursor, ccList, nabContactId);
break;
case ContactChange.KEY_VCARD_ADDRESS:
readAddress(cursor, ccList, nabContactId);
break;
case ContactChange.KEY_VCARD_ORG:
// Only one Organization can be read (CAB limitation!)
if (!mHaveReadOrganization) {
readOrganization(cursor, ccList, nabContactId);
}
break;
case ContactChange.KEY_VCARD_URL:
readWebsite(cursor, ccList, nabContactId);
break;
case ContactChange.KEY_VCARD_DATE:
if (!mHaveReadBirthday) {
readBirthday(cursor, ccList, nabContactId);
}
break;
default:
// The default case is also a valid key
final String value = CursorUtils.getString(cursor, Data.DATA1);
if (!TextUtils.isEmpty(value)) {
final long nabDetailId = CursorUtils.getLong(cursor, Data._ID);
final ContactChange cc = new ContactChange(key, value,
ContactChange.FLAG_NONE);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
break;
}
}
}
/**
* Reads an name detail as a {@link ContactChange} from the provided cursor.
* For this type of detail we need to use a VCARD (semicolon separated)
* value.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readName(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
// Using display name only to check if there is a valid name to read
final String displayName = CursorUtils.getString(cursor, StructuredName.DISPLAY_NAME);
if (!TextUtils.isEmpty(displayName)) {
final long nabDetailId = CursorUtils.getLong(cursor, StructuredName._ID);
// VCard Helper data type (CAB)
final Name name = new Name();
// NAB: Given name -> CAB: First name
name.firstname = CursorUtils.getString(cursor, StructuredName.GIVEN_NAME);
// NAB: Family name -> CAB: Surname
name.surname = CursorUtils.getString(cursor, StructuredName.FAMILY_NAME);
// NAB: Prefix -> CAB: Title
name.title = CursorUtils.getString(cursor, StructuredName.PREFIX);
// NAB: Middle name -> CAB: Middle name
name.midname = CursorUtils.getString(cursor, StructuredName.MIDDLE_NAME);
// NAB: Suffix -> CAB: Suffixes
name.suffixes = CursorUtils.getString(cursor, StructuredName.SUFFIX);
// NOTE: Ignoring Phonetics (DATA7, DATA8 and DATA9)!
// TODO: Need to get middle name and concatenate into value
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_NAME, VCardHelper
.makeName(name), ContactChange.FLAG_NONE);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
}
/**
* Reads an nickname detail as a {@link ContactChange} from the provided
* cursor.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readNickname(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final String value = CursorUtils.getString(cursor, Nickname.NAME);
if (!TextUtils.isEmpty(value)) {
final long nabDetailId = CursorUtils.getLong(cursor, Nickname._ID);
/*
* TODO: Decide what to do with nickname: Can only have one in VF360
* but NAB Allows more than one!
*/
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_NICKNAME, value,
ContactChange.FLAG_NONE);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
}
/**
* Reads an phone detail as a {@link ContactChange} from the provided
* cursor.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readPhone(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final String value = CursorUtils.getString(cursor, Phone.NUMBER);
if (!TextUtils.isEmpty(value)) {
final long nabDetailId = CursorUtils.getLong(cursor, Phone._ID);
final int type = CursorUtils.getInt(cursor, Phone.TYPE);
int flags = mapFromNabPhoneType(type);
final boolean isPrimary = CursorUtils.getInt(cursor, Phone.IS_PRIMARY) != 0;
if (isPrimary) {
flags |= ContactChange.FLAG_PREFERRED;
}
// assuming raw value is good enough for us
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_PHONE, value, flags);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
}
/**
* Reads an email detail as a {@link ContactChange} from the provided
* cursor.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readEmail(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final String value = CursorUtils.getString(cursor, Email.DATA);
if (!TextUtils.isEmpty(value)) {
final long nabDetailId = CursorUtils.getLong(cursor, Email._ID);
final int type = CursorUtils.getInt(cursor, Email.TYPE);
int flags = mapFromNabEmailType(type);
final boolean isPrimary = CursorUtils.getInt(cursor, Email.IS_PRIMARY) != 0;
if (isPrimary) {
flags |= ContactChange.FLAG_PREFERRED;
}
// assuming raw value is good enough for us
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_EMAIL, value, flags);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
}
/**
* Reads an address detail as a {@link ContactChange} from the provided
* cursor. For this type of detail we need to use a VCARD (semicolon
* separated) value.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readAddress(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
// Using formatted address only to check if there is a valid address to
// read
final String formattedAddress = CursorUtils.getString(cursor,
StructuredPostal.FORMATTED_ADDRESS);
if (!TextUtils.isEmpty(formattedAddress)) {
final long nabDetailId = CursorUtils.getLong(cursor, StructuredPostal._ID);
final int type = CursorUtils.getInt(cursor, StructuredPostal.TYPE);
int flags = mapFromNabAddressType(type);
final boolean isPrimary = CursorUtils.getInt(cursor, StructuredPostal.IS_PRIMARY) != 0;
if (isPrimary) {
flags |= ContactChange.FLAG_PREFERRED;
}
// VCard Helper data type (CAB)
final PostalAddress address = new PostalAddress();
// NAB: Street -> CAB: AddressLine1
address.addressLine1 = CursorUtils.getString(cursor, StructuredPostal.STREET);
// NAB: PO Box -> CAB: postOfficeBox
address.postOfficeBox = CursorUtils.getString(cursor, StructuredPostal.POBOX);
// NAB: Neighborhood -> CAB: AddressLine2
address.addressLine2 = CursorUtils.getString(cursor, StructuredPostal.NEIGHBORHOOD);
// NAB: City -> CAB: City
address.city = CursorUtils.getString(cursor, StructuredPostal.CITY);
// NAB: Region -> CAB: County
address.county = CursorUtils.getString(cursor, StructuredPostal.REGION);
// NAB: Post code -> CAB: Post code
address.postCode = CursorUtils.getString(cursor, StructuredPostal.POSTCODE);
// NAB: Country -> CAB: Country
address.country = CursorUtils.getString(cursor, StructuredPostal.COUNTRY);
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_ADDRESS, VCardHelper
.makePostalAddress(address), flags);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
}
/**
* Reads an organization detail as a {@link ContactChange} from the provided
* cursor. For this type of detail we need to use a VCARD (semicolon
* separated) value. In reality two different changes may be read if a title
* is also present.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readOrganization(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final int type = CursorUtils.getInt(cursor, Organization.TYPE);
int flags = mapFromNabOrganizationType(type);
final boolean isPrimary = CursorUtils.getInt(cursor, Organization.IS_PRIMARY) != 0;
if (isPrimary) {
flags |= ContactChange.FLAG_PREFERRED;
}
final long nabDetailId = CursorUtils.getLong(cursor, Organization._ID);
if (!mHaveReadOrganization) {
// VCard Helper data type (CAB)
final Organisation organization = new Organisation();
// Company
organization.name = CursorUtils.getString(cursor, Organization.COMPANY);
// Department
final String department = CursorUtils.getString(cursor, Organization.DEPARTMENT);
if (!TextUtils.isEmpty(department)) {
organization.unitNames.add(department);
}
if ((organization.unitNames != null && organization.unitNames.size() > 0)
|| !TextUtils.isEmpty(organization.name)) {
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_ORG, VCardHelper
.makeOrg(organization), flags);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
mHaveReadOrganization = true;
}
// Title
final String title = CursorUtils.getString(cursor, Organization.TITLE);
if (!TextUtils.isEmpty(title)) {
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_TITLE, title,
flags);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
mHaveReadOrganization = true;
}
}
}
/**
* Reads an Website detail as a {@link ContactChange} from the provided
* cursor.
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readWebsite(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final String url = CursorUtils.getString(cursor, Website.URL);
if (!TextUtils.isEmpty(url)) {
final long nabDetailId = CursorUtils.getLong(cursor, Website._ID);
final int type = CursorUtils.getInt(cursor, Website.TYPE);
int flags = mapFromNabWebsiteType(type);
final boolean isPrimary = CursorUtils.getInt(cursor, Website.IS_PRIMARY) != 0;
if (isPrimary) {
flags |= ContactChange.FLAG_PREFERRED;
}
final ContactChange cc = new ContactChange(ContactChange.KEY_VCARD_URL, url, flags);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
}
}
/**
* Reads an Birthday detail as a {@link ContactChange} from the provided
* cursor. Note that since the Android Type is the Generic "Event", it may
* be the case that nothing is read if this is not actually a Birthday
*
* @param cursor Cursor to read from
* @param ccList List of Contact Changes to add read detail data
* @param nabContactId ID of the NAB Contact
*/
private void readBirthday(Cursor cursor, List<ContactChange> ccList, long nabContactId) {
final int type = CursorUtils.getInt(cursor, Event.TYPE);
if (type != Event.TYPE_BIRTHDAY) {
// Not a Birthday, return
return;
}
final String date = CursorUtils.getString(cursor, Event.START_DATE);
if(TextUtils.isEmpty(date)) {
// Empty date, do nothing
return;
}
// Ignoring birthdays without year, day and month!
// FIXME: Remove this check when/if the backend becomes able to
// handle incomplete birthdays
Matcher matcher = Pattern.compile(NativeContactsApi2.COMPLETE_DATE_REGEX).matcher(date);
if(!matcher.find()) {
// No matching date, log and return
LogUtils.logD("NativeContactsApi2.readBirthday() - Unsupported '"+
date+"' Date skipped for NAB Contact ID:"+nabContactId);
return;
}
final long nabDetailId = CursorUtils.getLong(cursor, Event._ID);
final ContactChange cc = new ContactChange(
ContactChange.KEY_VCARD_DATE,
matcher.group(),
ContactChange.FLAG_BIRTHDAY);
cc.setNabContactId(nabContactId);
cc.setNabDetailId(nabDetailId);
ccList.add(cc);
mHaveReadBirthday = true;
}
/**
* Adds current values to the batch.
*
* @param nabContactId The existing NAB Contact ID if it is an update or an
* invalid id if a new contact
*/
private void addValuesToBatch(long nabContactId) {
// Add to batch
if (mValues.size() > 0) {
final boolean isNewContact = nabContactId == ContactChange.INVALID_ID;
if (!isNewContact) {
// Updating a Contact, need to add the ID to the Values
mValues.put(Data.RAW_CONTACT_ID, nabContactId);
}
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(
addCallerIsSyncAdapterParameter(Data.CONTENT_URI)).withYieldAllowed(mYield)
.withValues(mValues);
if (isNewContact) {
// New Contact needs Back Reference
builder.withValueBackReference(Data.RAW_CONTACT_ID, 0);
}
mYield = false;
mBatch.add(builder.build());
}
}
/**
* Adds current update values to the batch.
*
* @param nabDetailId The NAB ID of the detail to update
*/
private void addUpdateValuesToBatch(long nabDetailId) {
if (mValues.size() > 0) {
final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, nabDetailId);
ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(
addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(mYield).withValues(
mValues);
mYield = false;
mBatch.add(builder.build());
}
}
/**
* Adds a delete detail operation to the batch.
*
* @param nabDetailId The NAB Id of the detail to delete
*/
private void addDeleteDetailToBatch(long nabDetailId) {
final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, nabDetailId);
ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(
addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(mYield);
mYield = false;
mBatch.add(builder.build());
}
/**
* Adds the profile action detail to a (assumed) pending new Contact Batch
* operation.
*
* @param internalContactId The Internal Contact ID used as the Profile ID
*/
private void addProfileAction(long internalContactId) {
mValues.clear();
mValues.put(Data.MIMETYPE, PeopleProfileColumns.CONTENT_ITEM_TYPE);
mValues.put(PeopleProfileColumns.DATA_PID, internalContactId);
mValues.put(PeopleProfileColumns.DATA_SUMMARY, mContext
.getString(R.string.android_contact_profile_summary));
mValues.put(PeopleProfileColumns.DATA_DETAIL, mContext
.getString(R.string.android_contact_profile_detail));
addValuesToBatch(ContactChange.INVALID_ID);
}
// PRESENCE TEXT NOT USED
// /**
// * Returns the Data id for a sample SyncAdapter contact's profile row, or
// 0
// * if the sample SyncAdapter user isn't found.
// *
// * @param resolver a content resolver
// * @param userId the sample SyncAdapter user ID to lookup
// * @return the profile Data row id, or 0 if not found
// */
// private long lookupProfile(long internalContactId) {
// long profileId = -1;
// final Cursor c =
// mCr.query(Data.CONTENT_URI, ProfileQuery.PROJECTION,
// ProfileQuery.SELECTION, new String[] {String.valueOf(internalContactId)},
// null);
// try {
// if (c != null && c.moveToFirst()) {
// profileId = c.getLong(ProfileQuery.COLUMN_ID);
// }
// } finally {
// if (c != null) {
// c.close();
// }
// }
// return profileId;
// }
//
// /**
// * Constants for a query to find a contact given a sample SyncAdapter user
// * ID.
// */
// private interface ProfileQuery {
// public final static String[] PROJECTION = new String[] {Data._ID};
//
// public final static int COLUMN_ID = 0;
//
// public static final String SELECTION =
// Data.MIMETYPE + "='" + PeopleProfileColumns.CONTENT_ITEM_TYPE
// + "' AND " + PeopleProfileColumns.DATA_PID + "=?";
// }
//
// private void addPresence(long internalContactId, String presenceText) {
// long profileId = lookupProfile(internalContactId);
// if(profileId > -1) {
// mValues.clear();
// mValues.put(StatusUpdates.DATA_ID, profileId);
// mValues.put(StatusUpdates.STATUS, presenceText);
// mValues.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
// mValues.put(StatusUpdates.IM_ACCOUNT, LoginPreferences.getUsername());
// mValues.put(StatusUpdates.STATUS_ICON, R.drawable.pt_launcher_icon);
// mValues.put(StatusUpdates.STATUS_RES_PACKAGE, mContext.getPackageName());
//
// ContentProviderOperation.Builder builder =
// ContentProviderOperation.newInsert(
// addCallerIsSyncAdapterParameter(StatusUpdates.CONTENT_URI)).
// withYieldAllowed(mYield).withValues(mValues);
// BatchOperation batch = new BatchOperation();
// batch.add(builder.build());
// batch.execute();
// }
// }
/**
* Put values for a detail from a {@link ContactChange} into pending values.
*
* @param cc {@link ContactChange} to read values from
*/
private void putDetail(ContactChange cc) {
mValues.clear();
switch (cc.getKey()) {
case ContactChange.KEY_VCARD_PHONE:
putPhone(cc);
break;
case ContactChange.KEY_VCARD_EMAIL:
putEmail(cc);
break;
case ContactChange.KEY_VCARD_NAME:
putName(cc);
break;
case ContactChange.KEY_VCARD_NICKNAME:
putNickname(cc);
break;
case ContactChange.KEY_VCARD_ADDRESS:
putAddress(cc);
break;
case ContactChange.KEY_VCARD_URL:
putWebsite(cc);
break;
case ContactChange.KEY_VCARD_NOTE:
putNote(cc);
break;
case ContactChange.KEY_VCARD_DATE:
// Date only means Birthday currently
putBirthday(cc);
break;
default:
break;
}
}
/**
* Put Name detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putName(ContactChange cc) {
final Name name = VCardHelper.getName(cc.getValue());
if (name == null) {
// Nothing to do
return;
}
mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
mValues.put(StructuredName.GIVEN_NAME, name.firstname);
mValues.put(StructuredName.FAMILY_NAME, name.surname);
mValues.put(StructuredName.PREFIX, name.title);
mValues.put(StructuredName.MIDDLE_NAME, name.midname);
mValues.put(StructuredName.SUFFIX, name.suffixes);
}
/**
* Put Nickname detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putNickname(ContactChange cc) {
mValues.put(Nickname.NAME, cc.getValue());
mValues.put(Nickname.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
}
/**
* Put Phone detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putPhone(ContactChange cc) {
mValues.put(Phone.NUMBER, cc.getValue());
final int flags = cc.getFlags();
mValues.put(Phone.TYPE, mapToNabPhoneType(flags));
mValues.put(Phone.IS_PRIMARY, flags & ContactChange.FLAG_PREFERRED);
mValues.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
}
/**
* Put Email detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putEmail(ContactChange cc) {
mValues.put(Email.DATA, cc.getValue());
final int flags = cc.getFlags();
mValues.put(Email.TYPE, mapToNabEmailType(flags));
mValues.put(Email.IS_PRIMARY, flags & ContactChange.FLAG_PREFERRED);
mValues.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
}
/**
* Put Address detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putAddress(ContactChange cc) {
final PostalAddress address = VCardHelper.getPostalAddress(cc.getValue());
if (address == null) {
// Nothing to do
return;
}
mValues.put(StructuredPostal.STREET, address.addressLine1);
mValues.put(StructuredPostal.POBOX, address.postOfficeBox);
mValues.put(StructuredPostal.NEIGHBORHOOD, address.addressLine2);
mValues.put(StructuredPostal.CITY, address.city);
mValues.put(StructuredPostal.REGION, address.county);
mValues.put(StructuredPostal.POSTCODE, address.postCode);
mValues.put(StructuredPostal.COUNTRY, address.country);
final int flags = cc.getFlags();
mValues.put(StructuredPostal.TYPE, mapToNabAddressType(flags));
mValues.put(StructuredPostal.IS_PRIMARY, flags & ContactChange.FLAG_PREFERRED);
mValues.put(StructuredPostal.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
}
/**
* Put Website detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putWebsite(ContactChange cc) {
mValues.put(Website.URL, cc.getValue());
final int flags = cc.getFlags();
mValues.put(Website.TYPE, mapToNabWebsiteType(flags));
mValues.put(Website.IS_PRIMARY, flags & ContactChange.FLAG_PREFERRED);
mValues.put(Website.MIMETYPE, Website.CONTENT_ITEM_TYPE);
}
/**
* Put Note detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putNote(ContactChange cc) {
mValues.put(Note.NOTE, cc.getValue());
mValues.put(Note.MIMETYPE, Note.CONTENT_ITEM_TYPE);
}
/**
* Put Birthday detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putBirthday(ContactChange cc) {
if ((cc.getFlags() & ContactChange.FLAG_BIRTHDAY) == ContactChange.FLAG_BIRTHDAY) {
mValues.put(Event.START_DATE, cc.getValue());
mValues.put(Event.TYPE, Event.TYPE_BIRTHDAY);
mValues.put(Event.MIMETYPE, Event.CONTENT_ITEM_TYPE);
}
}
// PHOTO NOT USED
// /**
// * Do a GET request and retrieve up to maxBytes bytes
// *
// * @param url
// * @param maxBytes
// * @return
// * @throws IOException
// */
// public static byte[] doGetAndReturnBytes(URL url, int maxBytes) throws
// IOException {
// HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// conn.setRequestMethod("GET");
// InputStream istr = null;
// try {
// int rc = conn.getResponseCode();
// if (rc != 200) {
// throw new IOException("code " + rc + " '" + conn.getResponseMessage() +
// "'");
// }
// istr = new BufferedInputStream(conn.getInputStream(), 512);
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// copy(istr, baos, maxBytes);
// return baos.toByteArray();
// } finally {
// if (istr != null) {
// istr.close();
// }
// }
// }
//
// /**
// * Copy maxBytes from an input stream to an output stream.
// * @param in
// * @param out
// * @param maxBytes
// * @return
// * @throws IOException
// */
// private static int copy(InputStream in, OutputStream out, int maxBytes)
// throws IOException {
// byte[] buf = new byte[512];
// int bytesRead = 1;
// int totalBytes = 0;
// while (bytesRead > 0) {
// bytesRead = in.read(buf, 0, Math.min(512, maxBytes - totalBytes));
// if (bytesRead > 0) {
// out.write(buf, 0, bytesRead);
// totalBytes += bytesRead;
// }
// }
// return totalBytes;
// }
//
// /**
// * Put Photo detail into the values
// * @param cc {@link ContactChange} to read values from
// */
// private void putPhoto(ContactChange cc) {
// try {
// // File file = new File(cc.getValue());
// // InputStream is = new FileInputStream(file);
// // byte[] bytes = new byte[(int) file.length()];
// // is.read(bytes);
// // is.close();
// final URL url = new URL(cc.getValue());
// byte[] bytes = doGetAndReturnBytes(url, 1024 * 100);
// mValues.put(Photo.PHOTO, bytes);
// mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
// } catch(Exception ex) {
// LogUtils.logE("Unable to put Photo detail because of exception:"+ex);
// }
// }
/**
* Put Organization detail into the values
*
* @param cc {@link ContactChange} to read values from
*/
private void putOrganization(ContactChange[] ccList) {
mValues.clear();
int flags = ContactChange.FLAG_NONE;
if (mMarkedOrganizationIndex > -1) {
final ContactChange cc = ccList[mMarkedOrganizationIndex];
flags |= cc.getFlags();
final Organisation organization = VCardHelper.getOrg(cc.getValue());
if (organization != null) {
mValues.put(Organization.COMPANY, organization.name);
if (organization.unitNames.size() > 0) {
// Only considering one unit name (department) as that's all
// we support
mValues.put(Organization.DEPARTMENT, organization.unitNames.get(0));
} else {
mValues.putNull(Organization.DEPARTMENT);
}
}
}
if (mMarkedTitleIndex > -1) {
final ContactChange cc = ccList[mMarkedTitleIndex];
flags |= cc.getFlags();
// No need to check for empty values as there is only one
mValues.put(Organization.TITLE, cc.getValue());
}
if (mValues.size() > 0) {
mValues.put(Organization.LABEL, (String)null);
mValues.put(Organization.TYPE, mapToNabOrganizationType(flags));
mValues.put(Organization.IS_PRIMARY, flags & ContactChange.FLAG_PREFERRED);
mValues.put(Organization.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
}
}
/**
* Updates the Organization detail in the context of a Contact Update
* operation. The end of result of this is that the Organization may be
* inserted, updated or deleted depending on the update data. For example,
* if the title is deleted but there is also a company name then the
* Organization is just updated. However, if there was no company name then
* the detail should be deleted altogether.
*
* @param ccList {@link ContactChange} list where Organization and Title may
* be found
* @param nabContactId The NAB ID of the Contact
*/
private void updateOrganization(ContactChange[] ccList, long nabContactId) {
if (mMarkedOrganizationIndex < 0 && mMarkedTitleIndex < 0) {
// no organization or title to update - do nothing
return;
}
// First we check if there is an existing Organization detail in NAB
final Uri uri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
nabContactId), RawContacts.Data.CONTENT_DIRECTORY);
Cursor cursor = mCr.query(uri, null, ORGANIZATION_DETAIL_WHERE_CLAUSE, null,
RawContacts.Data._ID);
String company = null;
String department = null;
String title = null;
int flags = ContactChange.FLAG_NONE;
try {
if (cursor != null && cursor.moveToNext()) {
// Found an organization detail
company = CursorUtils.getString(cursor, Organization.COMPANY);
department = CursorUtils.getString(cursor, Organization.DEPARTMENT);
title = CursorUtils.getString(cursor, Organization.TITLE);
flags = mapFromNabOrganizationType(CursorUtils.getInt(cursor, Organization.TYPE));
final boolean isPrimary = CursorUtils.getInt(cursor, Organization.IS_PRIMARY) > 0;
if (isPrimary) {
flags |= ContactChange.FLAG_PREFERRED;
}
mExistingOrganizationId = CursorUtils.getLong(cursor, Organization._ID);
}
} finally {
CursorUtils.closeCursor(cursor);
cursor = null; // make it a candidate for the GC
}
if (mMarkedOrganizationIndex >= 0) {
// Have an Organization (Company + Department) to update
final ContactChange cc = ccList[mMarkedOrganizationIndex];
if (cc.getType() != ContactChange.TYPE_DELETE_DETAIL) {
final String value = cc.getValue();
if (value != null) {
final Organisation organization = VCardHelper.getOrg(value);
company = organization.name;
if (organization.unitNames.size() > 0) {
department = organization.unitNames.get(0);
}
}
flags = cc.getFlags();
} else { // Delete case
company = null;
department = null;
}
}
if (mMarkedTitleIndex >= 0) {
// Have a Title to update
final ContactChange cc = ccList[mMarkedTitleIndex];
title = cc.getValue();
if (cc.getType() != ContactChange.TYPE_UPDATE_DETAIL) {
flags = cc.getFlags();
}
}
if (company != null || department != null || title != null) {
/*
* If any of the above are present we assume a insert or update is
* needed.
*/
mValues.clear();
mValues.put(Organization.LABEL, (String)null);
mValues.put(Organization.COMPANY, company);
mValues.put(Organization.DEPARTMENT, department);
mValues.put(Organization.TITLE, title);
mValues.put(Organization.TYPE, mapToNabOrganizationType(flags));
mValues.put(Organization.IS_PRIMARY, flags & ContactChange.FLAG_PREFERRED);
mValues.put(Organization.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
if (mExistingOrganizationId != ContactChange.INVALID_ID) {
// update is needed
addUpdateValuesToBatch(mExistingOrganizationId);
} else {
// insert is needed
addValuesToBatch(nabContactId); // not a new contact
}
} else if (mExistingOrganizationId != ContactChange.INVALID_ID) {
/*
* Had an Organization but now all values are null, delete is in
* order.
*/
addDeleteDetailToBatch(mExistingOrganizationId);
}
}
/**
* Executes a pending Batch Operation related to adding a new Contact.
*
* @param ccList The original {@link ContactChange} for the new Contact
* @return {@link ContactChange} array containing new IDs (may contain some
* null elements)
*/
private ContactChange[] executeNewContactBatch(ContactChange[] ccList) {
if (mBatch.size() == 0) {
// Nothing to execute
LogUtils.logW("NativeContactsApi2.executeNewContactBatch() - the batch is empty, probably none of the changes are supported");
return null;
}
final ContentProviderResult[] results = mBatch.execute();
if (results == null || results.length == 0) {
LogUtils.logE("NativeContactsApi2.executeNewContactBatch()"
+ "Batch execution result is null or empty!");
return null;
}
// -1 because we skip the Profile detail
final int resultsSize = results.length - 1;
if (results[0].uri == null) {
// Contact was not created
LogUtils.logE("NativeContactsApi2.executeNewContactBatch()"
+ "NAB Contact ID not found for created contact");
return null;
}
final long nabContactId = ContentUris.parseId(results[0].uri);
final ContactChange[] idChangeList = new ContactChange[ccList.length + 1];
// Update NAB Contact ID CC
idChangeList[0] = ContactChange.createIdsChange(ccList[0],
ContactChange.TYPE_UPDATE_NAB_CONTACT_ID);
idChangeList[0].setNabContactId(nabContactId);
// Start after contact id in the results index
int resultsIndex = 1, ccListIndex = 0;
final boolean haveOrganization = mMarkedOrganizationIndex != -1 || mMarkedTitleIndex != -1;
while (resultsIndex < resultsSize) {
if (ccListIndex == mMarkedOrganizationIndex || ccListIndex == mMarkedTitleIndex) {
ccListIndex++;
continue;
}
if (results[resultsIndex].uri == null) {
throw new RuntimeException("NativeContactsApi2.executeNewContactBatch()"
+ "Unexpected null URI for NAB Contact:" + nabContactId);
}
if (resultsIndex == resultsSize - 1 && haveOrganization) {
// for readability we leave Organization/Title for outside the
// loop
break;
}
final ContactChange cc = ccList[ccListIndex];
// Check if the key is one that is supported in the 2.X NAB
if (sFromKeyToNabContentTypeArray.indexOfKey(cc.getKey()) >= 0) {
final long nabDetailId = ContentUris.parseId(results[resultsIndex].uri);
final ContactChange idChange = ContactChange.createIdsChange(ccList[ccListIndex],
ContactChange.TYPE_UPDATE_NAB_DETAIL_ID);
idChange.setNabContactId(nabContactId);
idChange.setNabDetailId(nabDetailId);
idChangeList[ccListIndex + 1] = idChange;
resultsIndex++;
}
ccListIndex++;
}
if (haveOrganization) {
final long nabDetailId = ContentUris.parseId(results[resultsIndex].uri);
if (mMarkedOrganizationIndex > -1) {
final ContactChange idChange = ContactChange.createIdsChange(
ccList[mMarkedOrganizationIndex], ContactChange.TYPE_UPDATE_NAB_DETAIL_ID);
idChange.setNabContactId(nabContactId);
idChange.setNabDetailId(nabDetailId);
idChangeList[mMarkedOrganizationIndex + 1] = idChange;
}
if (mMarkedTitleIndex > -1) {
final ContactChange idChange = ContactChange.createIdsChange(
ccList[mMarkedTitleIndex], ContactChange.TYPE_UPDATE_NAB_DETAIL_ID);
idChange.setNabContactId(nabContactId);
idChange.setNabDetailId(nabDetailId);
idChangeList[mMarkedTitleIndex + 1] = idChange;
}
}
return idChangeList;
}
/**
* Executes a pending Batch Operation related to updating a Contact.
*
* @param ccList The original {@link ContactChange} for the Contact update
* @return {@link ContactChange} array containing new IDs (may contain some
* null elements)
*/
private ContactChange[] executeUpdateContactBatch(ContactChange[] ccList) {
if (mBatch.size() == 0) {
// Nothing to execute
LogUtils.logW("NativeContactsApi2.executeUpdateContactBatch() - the batch is empty, probably none of the changes are supported");
return null;
}
final ContentProviderResult[] results = mBatch.execute();
final int resultsSize = results.length;
if (results == null || resultsSize == 0) {
// Assuming this can happen in case of no added details
LogUtils.logE("NativeContactsApi2.executeUpdateContactBatch()"
+ "Batch execution result is null or empty!");
return null;
}
// Start after contact id in the results index
int resultsIndex = 0, ccListIndex = 0;
final boolean haveOrganization = mMarkedOrganizationIndex != -1 || mMarkedTitleIndex != -1;
final ContactChange[] idChangeList = new ContactChange[ccList.length];
while (resultsIndex < resultsSize) {
if (ccListIndex == mMarkedOrganizationIndex || ccListIndex == mMarkedTitleIndex) {
ccListIndex++;
continue;
}
if (results[resultsIndex].uri == null) {
// Assuming detail was updated or deleted (not added)
resultsIndex++;
continue;
}
if (resultsIndex == resultsSize - 1 && haveOrganization) {
// for readability we leave Organization/Title for outside the
// loop
break;
}
final ContactChange cc = ccList[ccListIndex];
// Check if the key is one that is supported in the 2.X NAB
if (sFromKeyToNabContentTypeArray.indexOfKey(cc.getKey()) >= 0
&& cc.getType() == ContactChange.TYPE_ADD_DETAIL) {
final long nabDetailId = ContentUris.parseId(results[resultsIndex].uri);
final ContactChange idChange = ContactChange.createIdsChange(ccList[ccListIndex],
ContactChange.TYPE_UPDATE_NAB_DETAIL_ID);
idChange.setNabDetailId(nabDetailId);
idChangeList[ccListIndex] = idChange;
resultsIndex++;
}
ccListIndex++;
}
if (haveOrganization) {
long nabDetailId = ContactChange.INVALID_ID;
if (mExistingOrganizationId != nabDetailId) {
nabDetailId = mExistingOrganizationId;
} else if (results[resultsIndex].uri != null) {
nabDetailId = ContentUris.parseId(results[resultsIndex].uri);
} else {
throw new RuntimeException("NativeContactsApi2.executeUpdateContactBatch()"
+ "Unexpected null Organization URI for NAB Contact:"
+ ccList[0].getNabContactId());
}
if (mMarkedOrganizationIndex > -1
&& ccList[mMarkedOrganizationIndex].getType() == ContactChange.TYPE_ADD_DETAIL) {
final ContactChange idChange = ContactChange.createIdsChange(
ccList[mMarkedOrganizationIndex], ContactChange.TYPE_UPDATE_NAB_DETAIL_ID);
idChange.setNabDetailId(nabDetailId);
idChangeList[mMarkedOrganizationIndex] = idChange;
}
if (mMarkedTitleIndex > -1
&& ccList[mMarkedTitleIndex].getType() == ContactChange.TYPE_ADD_DETAIL) {
final ContactChange idChange = ContactChange.createIdsChange(
ccList[mMarkedTitleIndex], ContactChange.TYPE_UPDATE_NAB_DETAIL_ID);
idChange.setNabDetailId(nabDetailId);
idChangeList[mMarkedTitleIndex] = idChange;
}
}
return idChangeList;
}
/**
* Static utility method that adds the Sync Adapter flag to the provided URI
*
* @param uri URI to add the flag to
* @return URI with the flag
*/
private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
return uri.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
/**
* Utility method used to create an entry in the ContactsContract.Settings
* database table for the 360 People Account. This method only exists to
* compensate for HTC devices such as Legend or Desire that don't create the
* entry automatically like the Nexus One.
*
* @param username The username of the 360 People Account.
*/
private void createSettingsEntryForAccount(String username) {
mValues.clear();
mValues.put(Settings.ACCOUNT_NAME, username);
mValues.put(Settings.ACCOUNT_TYPE, PEOPLE_ACCOUNT_TYPE_STRING);
mValues.put(Settings.UNGROUPED_VISIBLE, true);
mValues.put(Settings.SHOULD_SYNC, false); // TODO Unsupported for now
mCr.insert(Settings.CONTENT_URI, mValues);
}
/**
* Requests the SyncAdapter to perform a sync with initialization sequence.
*
* @param account the account to be initialized
*/
private void requestSyncAdapterInitialization(android.accounts.Account account) {
final Bundle bundle = new Bundle();
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle);
}
/**
* Maps a phone type from the native value to the {@link ContactChange}
* flags.
*
* @param nabType Given native phone number type
* @return {@link ContactChange} flags
*/
private static int mapFromNabPhoneType(int nabType) {
switch (nabType) {
case Phone.TYPE_HOME:
return ContactChange.FLAG_HOME;
case Phone.TYPE_MOBILE:
return ContactChange.FLAG_CELL;
case Phone.TYPE_WORK:
return ContactChange.FLAG_WORK;
case Phone.TYPE_WORK_MOBILE:
return ContactChange.FLAGS_WORK_CELL;
case Phone.TYPE_FAX_WORK:
return ContactChange.FLAGS_WORK_FAX;
case Phone.TYPE_FAX_HOME:
return ContactChange.FLAGS_HOME_FAX;
case Phone.TYPE_OTHER_FAX:
return ContactChange.FLAG_FAX;
}
return ContactChange.FLAG_NONE;
}
/**
* Maps {@link ContactChange} flags into the native phone type.
*
* @param flags {@link ContactChange} flags
* @return Native phone type
*/
private static int mapToNabPhoneType(int flags) {
if ((flags & ContactChange.FLAGS_WORK_CELL) == ContactChange.FLAGS_WORK_CELL) {
return Phone.TYPE_WORK_MOBILE;
}
if ((flags & ContactChange.FLAGS_HOME_FAX) == ContactChange.FLAGS_HOME_FAX) {
return Phone.TYPE_FAX_HOME;
}
if ((flags & ContactChange.FLAGS_WORK_FAX) == ContactChange.FLAGS_WORK_FAX) {
return Phone.TYPE_FAX_WORK;
}
if ((flags & ContactChange.FLAG_HOME) == ContactChange.FLAG_HOME) {
return Phone.TYPE_HOME;
}
if ((flags & ContactChange.FLAG_WORK) == ContactChange.FLAG_WORK) {
return Phone.TYPE_WORK;
}
if ((flags & ContactChange.FLAG_CELL) == ContactChange.FLAG_CELL) {
return Phone.TYPE_MOBILE;
}
if ((flags & ContactChange.FLAG_FAX) == ContactChange.FLAG_FAX) {
return Phone.TYPE_OTHER_FAX;
}
return Phone.TYPE_OTHER;
}
/**
* Maps a email type from the native value into the {@link ContactChange}
* flags
*
* @param nabType Native email type
* @return {@link ContactChange} flags
*/
private static int mapFromNabEmailType(int nabType) {
switch (nabType) {
case Email.TYPE_HOME:
return ContactChange.FLAG_HOME;
case Email.TYPE_MOBILE:
return ContactChange.FLAG_CELL;
case Email.TYPE_WORK:
return ContactChange.FLAG_WORK;
}
return ContactChange.FLAG_NONE;
}
/**
* Maps {@link ContactChange} flags into the native email type.
*
* @param flags {@link ContactChange} flags
* @return Native email type
*/
private static int mapToNabEmailType(int flags) {
if ((flags & ContactChange.FLAG_HOME) == ContactChange.FLAG_HOME) {
return Email.TYPE_HOME;
}
if ((flags & ContactChange.FLAG_WORK) == ContactChange.FLAG_WORK) {
return Email.TYPE_WORK;
}
return Email.TYPE_OTHER;
}
/**
* Maps a address type from the native value into the {@link ContactChange}
* flags
*
* @param nabType Native email type
* @return {@link ContactChange} flags
*/
private static int mapFromNabAddressType(int nabType) {
switch (nabType) {
case StructuredPostal.TYPE_HOME:
return ContactChange.FLAG_HOME;
case StructuredPostal.TYPE_WORK:
return ContactChange.FLAG_WORK;
}
return ContactChange.FLAG_NONE;
}
/**
* Maps {@link ContactChange} flags into the native address type.
*
* @param flags {@link ContactChange} flags
* @return Native address type
*/
private static int mapToNabAddressType(int flags) {
if ((flags & ContactChange.FLAG_HOME) == ContactChange.FLAG_HOME) {
return StructuredPostal.TYPE_HOME;
}
if ((flags & ContactChange.FLAG_WORK) == ContactChange.FLAG_WORK) {
return StructuredPostal.TYPE_WORK;
}
return StructuredPostal.TYPE_OTHER;
}
/**
* Maps a organization type from the native value into the
* {@link ContactChange} flags
*
* @param nabType Given native organization type
* @return {@link ContactChange} flags
*/
private static int mapFromNabOrganizationType(int nabType) {
if (nabType == Organization.TYPE_WORK) {
return ContactChange.FLAG_WORK;
}
return ContactChange.FLAG_NONE;
}
/**
* Maps {@link ContactChange} flags into the native organization type.
*
* @param flags {@link ContactChange} flags
* @return Native Organization type
*/
private static int mapToNabOrganizationType(int flags) {
if ((flags & ContactChange.FLAG_WORK) == ContactChange.FLAG_WORK) {
return Organization.TYPE_WORK;
}
return Organization.TYPE_OTHER;
}
/**
* Maps a Website type from the native value into the {@link ContactChange}
* flags
*
* @param nabType Native email type
* @return {@link ContactChange} flags
*/
private static int mapFromNabWebsiteType(int nabType) {
switch (nabType) {
case Website.TYPE_HOME:
return ContactChange.FLAG_HOME;
case Website.TYPE_WORK:
return ContactChange.FLAG_WORK;
}
return ContactChange.FLAG_NONE;
}
/**
* Maps {@link ContactChange} flags into the native Website type.
*
* @param flags {@link ContactChange} flags
* @return Native Website type
*/
private static int mapToNabWebsiteType(int flags) {
if ((flags & ContactChange.FLAG_HOME) == ContactChange.FLAG_HOME) {
return Website.TYPE_HOME;
}
if ((flags & ContactChange.FLAG_WORK) == ContactChange.FLAG_WORK) {
return Website.TYPE_WORK;
}
return Website.TYPE_OTHER;
}
/**
* Gets the first People Account found on the device or
* null if none is found.
* @return The Android People account found or null
*/
private android.accounts.Account getPeopleAccount() {
android.accounts.Account[] accounts =
AccountManager.get(mContext).getAccountsByType(PEOPLE_ACCOUNT_TYPE_STRING);
if(accounts != null && accounts.length > 0) {
return accounts[0];
}
return null;
}
}