blob: 7affb7fc70b287cc8947528336bb13d93d24e406 [file] [log] [blame]
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.exchange.adapter;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.MailboxColumns;
import com.android.exchange.Eas;
import com.android.exchange.EasSyncService;
import com.android.exchange.utility.Base64;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Entity;
import android.content.EntityIterator;
import android.content.OperationApplicationException;
import android.content.ContentProviderOperation.Builder;
import android.content.Entity.NamedContentValues;
import android.database.Cursor;
import android.net.Uri;
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.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* Sync adapter for EAS Contacts
*
*/
public class ContactsSyncAdapter extends AbstractSyncAdapter {
private static final String TAG = "EasContactsSyncAdapter";
private static final String SERVER_ID_SELECTION = RawContacts.SOURCE_ID + "=?";
private static final String[] ID_PROJECTION = new String[] {RawContacts._ID};
private static final String[] GROUP_PROJECTION = new String[] {Groups.SOURCE_ID};
// Note: These constants are likely to change; they are internal to this class now, but
// may end up in the provider.
private static final int TYPE_EMAIL1 = 20;
private static final int TYPE_EMAIL2 = 21;
private static final int TYPE_EMAIL3 = 22;
// We'll split email into two columns, the one that Contacts uses (just for the email address
// portion, and another one (the one defined here) for the display name
//private static final String EMAIL_DISPLAY_NAME = Data.SYNC1;
private static final int TYPE_IM1 = 23;
private static final int TYPE_IM2 = 24;
private static final int TYPE_IM3 = 25;
private static final int TYPE_WORK2 = 26;
private static final int TYPE_HOME2 = 27;
private static final int TYPE_CAR = 28;
private static final int TYPE_COMPANY_MAIN = 29;
private static final int TYPE_MMS = 30;
private static final int TYPE_RADIO = 31;
private static final int TYPE_ASSISTANT = 32;
ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
public ContactsSyncAdapter(Mailbox mailbox, EasSyncService service) {
super(mailbox, service);
}
@Override
public boolean parse(InputStream is, EasSyncService service) throws IOException {
EasContactsSyncParser p = new EasContactsSyncParser(is, service);
return p.parse();
}
// YomiFirstName, YomiLastName, and YomiCompanyName are the names of EAS fields
// Yomi is a shortened form of yomigana, which is a Japanese phonetic rendering.
public static final class Yomi {
private Yomi() {}
/** MIME type used when storing this in data table. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/eas_yomi";
public static final String FIRST_NAME = "data2";
public static final String LAST_NAME = "data3";
public static final String COMPANY_NAME = "data4";
}
public static final class EasChildren {
private EasChildren() {}
/** MIME type used when storing this in data table. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/eas_children";
public static final int MAX_CHILDREN = 8;
public static final String[] ROWS =
new String[] {"data2", "data3", "data4", "data5", "data6", "data7", "data8", "data9"};
}
public static final class EasPersonal {
String anniversary;
String birthday;
String fileAs;
String title;
String spouse;
String webpage;
/** MIME type used when storing this in data table. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/eas_personal";
public static final String ANNIVERSARY = "data2";
public static final String BIRTHDAY = "data3";
public static final String FILE_AS = "data4";
public static final String TITLE = "data5";
public static final String SPOUSE = "data6";
public static final String WEBPAGE = "data7";
boolean hasData() {
return anniversary != null || birthday != null || fileAs != null || title != null
|| spouse != null || webpage != null;
}
}
public static final class EasBusiness {
String assistantName;
String department;
String officeLocation;
String managerName;
String customerId;
String governmentId;
String accountName;
/** MIME type used when storing this in data table. */
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/eas_business";
public static final String ASSISTANT_NAME = "data2";
public static final String DEPARTMENT = "data3";
public static final String OFFICE_LOCATION = "data4";
public static final String MANAGER_NAME = "data5";
public static final String CUSTOMER_ID = "data6";
public static final String GOVERNMENT_ID = "data7";
public static final String ACCOUNT_NAME = "data8";
boolean hasData() {
return assistantName != null || department != null || officeLocation != null
|| managerName != null || customerId != null || governmentId != null
|| accountName != null;
}
}
public static final class Address {
String city;
String country;
String code;
String street;
String state;
boolean hasData() {
return city != null || country != null || code != null || state != null
|| street != null;
}
}
class EasContactsSyncParser extends AbstractSyncParser {
String[] mBindArgument = new String[1];
String mMailboxIdAsString;
Uri mAccountUri;
public EasContactsSyncParser(InputStream in, EasSyncService service) throws IOException {
super(in, service);
mAccountUri = uriWithAccount(RawContacts.CONTENT_URI);
}
@Override
public void wipe() {
// TODO Use the bulk delete when the CP supports it
// mContentResolver.delete(mAccountUri.buildUpon()
// .appendQueryParameter(ContactsContract.RawContacts.DELETE_PERMANENTLY, "true")
// .build(), null, null);
Cursor c = mContentResolver.query(mAccountUri, new String[] {"_id"}, null, null, null);
try {
while (c.moveToNext()) {
long id = c.getLong(0);
mContentResolver.delete(ContentUris
.withAppendedId(mAccountUri, id)
.buildUpon().appendQueryParameter(
ContactsContract.RawContacts.DELETE_PERMANENTLY, "true")
.build(), null, null);
}
} finally {
c.close();
}
}
public void addData(String serverId, ContactOperations ops, Entity entity)
throws IOException {
String firstName = null;
String lastName = null;
String middleName = null;
String suffix = null;
String companyName = null;
String yomiFirstName = null;
String yomiLastName = null;
String yomiCompanyName = null;
String title = null;
Address home = new Address();
Address work = new Address();
Address other = new Address();
EasBusiness business = new EasBusiness();
EasPersonal personal = new EasPersonal();
ArrayList<String> children = new ArrayList<String>();
if (entity == null) {
ops.newContact(serverId);
}
while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case Tags.CONTACTS_FIRST_NAME:
firstName = getValue();
break;
case Tags.CONTACTS_LAST_NAME:
lastName = getValue();
break;
case Tags.CONTACTS_MIDDLE_NAME:
middleName = getValue();
break;
case Tags.CONTACTS_SUFFIX:
suffix = getValue();
break;
case Tags.CONTACTS_COMPANY_NAME:
companyName = getValue();
break;
case Tags.CONTACTS_JOB_TITLE:
title = getValue();
break;
case Tags.CONTACTS_EMAIL1_ADDRESS:
ops.addEmail(entity, TYPE_EMAIL1, getValue());
break;
case Tags.CONTACTS_EMAIL2_ADDRESS:
ops.addEmail(entity, TYPE_EMAIL2, getValue());
break;
case Tags.CONTACTS_EMAIL3_ADDRESS:
ops.addEmail(entity, TYPE_EMAIL3, getValue());
break;
case Tags.CONTACTS_BUSINESS2_TELEPHONE_NUMBER:
ops.addPhone(entity, TYPE_WORK2, getValue());
break;
case Tags.CONTACTS_BUSINESS_TELEPHONE_NUMBER:
ops.addPhone(entity, Phone.TYPE_WORK, getValue());
break;
case Tags.CONTACTS2_MMS:
ops.addPhone(entity, TYPE_MMS, getValue());
break;
case Tags.CONTACTS_BUSINESS_FAX_NUMBER:
ops.addPhone(entity, Phone.TYPE_FAX_WORK, getValue());
break;
case Tags.CONTACTS2_COMPANY_MAIN_PHONE:
ops.addPhone(entity, TYPE_COMPANY_MAIN, getValue());
break;
case Tags.CONTACTS_HOME_FAX_NUMBER:
ops.addPhone(entity, Phone.TYPE_FAX_HOME, getValue());
break;
case Tags.CONTACTS_HOME_TELEPHONE_NUMBER:
ops.addPhone(entity, Phone.TYPE_HOME, getValue());
break;
case Tags.CONTACTS_HOME2_TELEPHONE_NUMBER:
ops.addPhone(entity, TYPE_HOME2, getValue());
break;
case Tags.CONTACTS_MOBILE_TELEPHONE_NUMBER:
ops.addPhone(entity, Phone.TYPE_MOBILE, getValue());
break;
case Tags.CONTACTS_CAR_TELEPHONE_NUMBER:
ops.addPhone(entity, TYPE_CAR, getValue());
break;
case Tags.CONTACTS_RADIO_TELEPHONE_NUMBER:
ops.addPhone(entity, TYPE_RADIO, getValue());
break;
case Tags.CONTACTS_PAGER_NUMBER:
ops.addPhone(entity, Phone.TYPE_PAGER, getValue());
break;
case Tags.CONTACTS_ASSISTANT_TELEPHONE_NUMBER:
ops.addPhone(entity, TYPE_ASSISTANT, getValue());
break;
case Tags.CONTACTS2_IM_ADDRESS:
ops.addIm(entity, TYPE_IM1, getValue());
break;
case Tags.CONTACTS2_IM_ADDRESS_2:
ops.addIm(entity, TYPE_IM2, getValue());
break;
case Tags.CONTACTS2_IM_ADDRESS_3:
ops.addIm(entity, TYPE_IM3, getValue());
break;
case Tags.CONTACTS_BUSINESS_ADDRESS_CITY:
work.city = getValue();
break;
case Tags.CONTACTS_BUSINESS_ADDRESS_COUNTRY:
work.country = getValue();
break;
case Tags.CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE:
work.code = getValue();
break;
case Tags.CONTACTS_BUSINESS_ADDRESS_STATE:
work.state = getValue();
break;
case Tags.CONTACTS_BUSINESS_ADDRESS_STREET:
work.street = getValue();
break;
case Tags.CONTACTS_HOME_ADDRESS_CITY:
home.city = getValue();
break;
case Tags.CONTACTS_HOME_ADDRESS_COUNTRY:
home.country = getValue();
break;
case Tags.CONTACTS_HOME_ADDRESS_POSTAL_CODE:
home.code = getValue();
break;
case Tags.CONTACTS_HOME_ADDRESS_STATE:
home.state = getValue();
break;
case Tags.CONTACTS_HOME_ADDRESS_STREET:
home.street = getValue();
break;
case Tags.CONTACTS_OTHER_ADDRESS_CITY:
other.city = getValue();
break;
case Tags.CONTACTS_OTHER_ADDRESS_COUNTRY:
other.country = getValue();
break;
case Tags.CONTACTS_OTHER_ADDRESS_POSTAL_CODE:
other.code = getValue();
break;
case Tags.CONTACTS_OTHER_ADDRESS_STATE:
other.state = getValue();
break;
case Tags.CONTACTS_OTHER_ADDRESS_STREET:
other.street = getValue();
break;
case Tags.CONTACTS_CHILDREN:
childrenParser(children);
break;
case Tags.CONTACTS_YOMI_COMPANY_NAME:
yomiCompanyName = getValue();
break;
case Tags.CONTACTS_YOMI_FIRST_NAME:
yomiFirstName = getValue();
break;
case Tags.CONTACTS_YOMI_LAST_NAME:
yomiLastName = getValue();
break;
case Tags.CONTACTS2_NICKNAME:
ops.addNickname(entity, getValue());
break;
// EAS Business
case Tags.CONTACTS_ASSISTANT_NAME:
business.assistantName = getValue();
break;
case Tags.CONTACTS_DEPARTMENT:
business.department = getValue();
break;
case Tags.CONTACTS_OFFICE_LOCATION:
business.officeLocation = getValue();
break;
case Tags.CONTACTS2_MANAGER_NAME:
business.managerName = getValue();
break;
case Tags.CONTACTS2_CUSTOMER_ID:
business.customerId = getValue();
break;
case Tags.CONTACTS2_GOVERNMENT_ID:
business.governmentId = getValue();
break;
case Tags.CONTACTS2_ACCOUNT_NAME:
business.accountName = getValue();
break;
// EAS Personal
case Tags.CONTACTS_ANNIVERSARY:
personal.anniversary = getValue();
break;
case Tags.CONTACTS_BIRTHDAY:
personal.birthday = getValue();
break;
case Tags.CONTACTS_FILE_AS:
personal.fileAs = getValue();
break;
case Tags.CONTACTS_TITLE:
personal.title = getValue();
break;
case Tags.CONTACTS_SPOUSE:
personal.spouse = getValue();
break;
case Tags.CONTACTS_WEBPAGE:
personal.webpage = getValue();
break;
case Tags.CONTACTS_PICTURE:
ops.addPhoto(entity, getValue());
break;
case Tags.BASE_BODY:
ops.addNote(entity, bodyParser());
break;
case Tags.CONTACTS_BODY:
ops.addNote(entity, getValue());
break;
// TODO Handle Categories/Category
// If we don't handle this properly, we'll lose the information if/when we
// upload changes to the server!
case Tags.CONTACTS_CATEGORIES:
categoriesParser(ops, entity);
break;
case Tags.CONTACTS_COMPRESSED_RTF:
// We don't use this, and it isn't necessary to upload, so we'll ignore it
skipTag();
break;
default:
skipTag();
}
}
// We must have first name, last name, or company name
String name;
if (firstName != null || lastName != null) {
if (firstName == null) {
name = lastName;
} else if (lastName == null) {
name = firstName;
} else {
name = firstName + ' ' + lastName;
}
} else if (companyName != null) {
name = companyName;
} else {
return;
}
ops.addName(entity, firstName, lastName, middleName, suffix, name);
ops.addYomi(entity, yomiFirstName, yomiLastName, yomiCompanyName);
ops.addBusiness(entity, business);
ops.addPersonal(entity, personal);
if (!children.isEmpty()) {
ops.addChildren(entity, children);
}
if (work.hasData()) {
ops.addPostal(entity, StructuredPostal.TYPE_WORK, work.street, work.city,
work.state, work.country, work.code);
}
if (home.hasData()) {
ops.addPostal(entity, StructuredPostal.TYPE_HOME, home.street, home.city,
home.state, home.country, home.code);
}
if (other.hasData()) {
ops.addPostal(entity, StructuredPostal.TYPE_OTHER, other.street, other.city,
other.state, other.country, other.code);
}
if (companyName != null) {
ops.addOrganization(entity, Organization.TYPE_WORK, companyName, title);
}
if (entity != null) {
// We've been removing rows from the list as they've been found in the xml
// Any that are left must have been deleted on the server
ArrayList<NamedContentValues> ncvList = entity.getSubValues();
for (NamedContentValues ncv: ncvList) {
// These rows need to be deleted...
Uri u = dataUriFromNamedContentValues(ncv);
ops.add(ContentProviderOperation.newDelete(u).build());
}
}
}
private void categoriesParser(ContactOperations ops, Entity entity) throws IOException {
while (nextTag(Tags.CONTACTS_CATEGORIES) != END) {
switch (tag) {
case Tags.CONTACTS_CATEGORY:
ops.addGroup(entity, getValue());
default:
skipTag();
}
}
}
private void childrenParser(ArrayList<String> children) throws IOException {
while (nextTag(Tags.CONTACTS_CHILDREN) != END) {
switch (tag) {
case Tags.CONTACTS_CHILD:
if (children.size() < EasChildren.MAX_CHILDREN) {
children.add(getValue());
}
break;
default:
skipTag();
}
}
}
private String bodyParser() throws IOException {
String body = null;
while (nextTag(Tags.BASE_BODY) != END) {
switch (tag) {
case Tags.BASE_DATA:
body = getValue();
break;
default:
skipTag();
}
}
return body;
}
public void addParser(ContactOperations ops) throws IOException {
String serverId = null;
while (nextTag(Tags.SYNC_ADD) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID: // same as
serverId = getValue();
break;
case Tags.SYNC_APPLICATION_DATA:
addData(serverId, ops, null);
break;
default:
skipTag();
}
}
}
private Cursor getServerIdCursor(String serverId) {
mBindArgument[0] = serverId;
return mContentResolver.query(mAccountUri, ID_PROJECTION, SERVER_ID_SELECTION,
mBindArgument, null);
}
public void deleteParser(ContactOperations ops) throws IOException {
while (nextTag(Tags.SYNC_DELETE) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
String serverId = getValue();
// Find the message in this mailbox with the given serverId
Cursor c = getServerIdCursor(serverId);
try {
if (c.moveToFirst()) {
mService.userLog("Deleting " + serverId);
ops.delete(c.getLong(0));
}
} finally {
c.close();
}
break;
default:
skipTag();
}
}
}
class ServerChange {
long id;
boolean read;
ServerChange(long _id, boolean _read) {
id = _id;
read = _read;
}
}
/**
* Changes are handled row by row, and only changed/new rows are acted upon
* @param ops the array of pending ContactProviderOperations.
* @throws IOException
*/
public void changeParser(ContactOperations ops) throws IOException {
String serverId = null;
Entity entity = null;
while (nextTag(Tags.SYNC_CHANGE) != END) {
switch (tag) {
case Tags.SYNC_SERVER_ID:
serverId = getValue();
Cursor c = getServerIdCursor(serverId);
try {
if (c.moveToFirst()) {
// TODO Handle deleted individual rows...
try {
EntityIterator entityIterator =
mContentResolver.queryEntities(ContentUris
.withAppendedId(RawContacts.CONTENT_URI, c.getLong(0)),
null, null, null);
if (entityIterator.hasNext()) {
entity = entityIterator.next();
}
mService.userLog("Changing contact " + serverId);
} catch (RemoteException e) {
}
}
} finally {
c.close();
}
break;
case Tags.SYNC_APPLICATION_DATA:
addData(serverId, ops, entity);
break;
default:
skipTag();
}
}
}
@Override
public void commandsParser() throws IOException {
ContactOperations ops = new ContactOperations();
while (nextTag(Tags.SYNC_COMMANDS) != END) {
if (tag == Tags.SYNC_ADD) {
addParser(ops);
mService.mChangeCount++;
} else if (tag == Tags.SYNC_DELETE) {
deleteParser(ops);
mService.mChangeCount++;
} else if (tag == Tags.SYNC_CHANGE) {
changeParser(ops);
mService.mChangeCount++;
} else
skipTag();
}
// Execute these all at once...
ops.execute();
if (ops.mResults != null) {
ContentValues cv = new ContentValues();
cv.put(RawContacts.DIRTY, 0);
for (int i = 0; i < ops.mContactIndexCount; i++) {
int index = ops.mContactIndexArray[i];
Uri u = ops.mResults[index].uri;
if (u != null) {
String idString = u.getLastPathSegment();
mService.mContentResolver.update(RawContacts.CONTENT_URI, cv,
RawContacts._ID + "=" + idString, null);
}
}
}
// Update the sync key in the database
mService.userLog("Contacts SyncKey saved as: " + mMailbox.mSyncKey);
ContentValues cv = new ContentValues();
cv.put(MailboxColumns.SYNC_KEY, mMailbox.mSyncKey);
Mailbox.update(mContext, Mailbox.CONTENT_URI, mMailbox.mId, cv);
}
}
private Uri uriWithAccount(Uri uri) {
return uri.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, mService.mAccount.mEmailAddress)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, Eas.ACCOUNT_MANAGER_TYPE)
.build();
}
/**
* SmartBuilder is a wrapper for the Builder class that is used to create/update rows for a
* ContentProvider. It has, in addition to the Builder, ContentValues which, if present,
* represent the current values of that row, that can be compared against current values to
* see whether an update is even necessary. The methods on SmartBuilder are delegated to
* the Builder.
*/
private class SmartBuilder {
Builder builder;
ContentValues cv;
public SmartBuilder(Builder _builder) {
builder = _builder;
}
public SmartBuilder(Builder _builder, NamedContentValues _ncv) {
builder = _builder;
cv = _ncv.values;
}
SmartBuilder withValues(ContentValues values) {
builder.withValues(values);
return this;
}
SmartBuilder withValueBackReference(String key, int previousResult) {
builder.withValueBackReference(key, previousResult);
return this;
}
ContentProviderOperation build() {
return builder.build();
}
SmartBuilder withValue(String key, Object value) {
builder.withValue(key, value);
return this;
}
}
private class ContactOperations extends ArrayList<ContentProviderOperation> {
private static final long serialVersionUID = 1L;
private int mCount = 0;
private int mContactBackValue = mCount;
// Make an array big enough for the PIM window (max items we can get)
private int[] mContactIndexArray =
new int[Integer.parseInt(EasSyncService.PIM_WINDOW_SIZE)];
private int mContactIndexCount = 0;
private ContentProviderResult[] mResults = null;
@Override
public boolean add(ContentProviderOperation op) {
super.add(op);
mCount++;
return true;
}
public void newContact(String serverId) {
Builder builder = ContentProviderOperation
.newInsert(uriWithAccount(RawContacts.CONTENT_URI));
ContentValues values = new ContentValues();
values.put(RawContacts.SOURCE_ID, serverId);
builder.withValues(values);
mContactBackValue = mCount;
mContactIndexArray[mContactIndexCount++] = mCount;
add(builder.build());
}
public void delete(long id) {
add(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(RawContacts.CONTENT_URI, id)
.buildUpon()
.appendQueryParameter(ContactsContract.RawContacts.DELETE_PERMANENTLY,
"true")
.build())
.build());
}
public void execute() {
synchronized (mService.getSynchronizer()) {
if (!mService.isStopped()) {
try {
mService.userLog("Executing " + size() + " CPO's");
mResults = mService.mContext.getContentResolver().applyBatch(
ContactsContract.AUTHORITY, this);
} catch (RemoteException e) {
// There is nothing sensible to be done here
Log.e(TAG, "problem inserting contact during server update", e);
} catch (OperationApplicationException e) {
// There is nothing sensible to be done here
Log.e(TAG, "problem inserting contact during server update", e);
}
}
}
}
/**
* Given the list of NamedContentValues for an entity, a mime type, and a subtype,
* tries to find a match, returning it
* @param list the list of NCV's from the contact entity
* @param contentItemType the mime type we're looking for
* @param type the subtype (e.g. HOME, WORK, etc.)
* @return the matching NCV or null if not found
*/
private NamedContentValues findExistingData(ArrayList<NamedContentValues> list,
String contentItemType, int type, String stringType) {
NamedContentValues result = null;
// Loop through the ncv's, looking for an existing row
for (NamedContentValues namedContentValues: list) {
Uri uri = namedContentValues.uri;
ContentValues cv = namedContentValues.values;
if (Data.CONTENT_URI.equals(uri)) {
String mimeType = cv.getAsString(Data.MIMETYPE);
if (mimeType.equals(contentItemType)) {
if (stringType != null) {
if (cv.getAsString(GroupMembership.GROUP_ROW_ID).equals(stringType)) {
result = namedContentValues;
}
// Note Email.TYPE could be ANY type column; they are all defined in
// the private CommonColumns class in ContactsContract
} else if (type < 0 || cv.getAsInteger(Email.TYPE) == type) {
result = namedContentValues;
}
}
}
}
// TODO Handle deleted items
// If we've found an existing data row, we'll delete it. Any rows left at the
// end should be deleted...
if (result != null) {
list.remove(result);
}
// Return the row found (or null)
return result;
}
/**
* Create a wrapper for a builder (insert or update) that also includes the NCV for
* an existing row of this type. If the SmartBuilder's cv field is not null, then
* it represents the current (old) values of this field. The caller can then check
* whether the field is now different and needs to be updated; if it's not different,
* the caller will simply return and not generate a new CPO. Otherwise, the builder
* should have its content values set, and the built CPO should be added to the
* ContactOperations list.
*
* @param entity the contact entity (or null if this is a new contact)
* @param mimeType the mime type of this row
* @param type the subtype of this row
* @param stringType for groups, the name of the group (type will be ignored), or null
* @return the created SmartBuilder
*/
public SmartBuilder createBuilder(Entity entity, String mimeType, int type) {
return createBuilder(entity, mimeType, type, null);
}
public SmartBuilder createBuilder(Entity entity, String mimeType, int type,
String stringType) {
int contactId = mContactBackValue;
SmartBuilder builder = null;
if (entity != null) {
NamedContentValues ncv =
findExistingData(entity.getSubValues(), mimeType, type, stringType);
if (ncv != null) {
builder = new SmartBuilder(
ContentProviderOperation
.newUpdate(dataUriFromNamedContentValues(ncv)),
ncv);
} else {
contactId = entity.getEntityValues().getAsInteger(RawContacts._ID);
}
}
if (builder == null) {
builder =
new SmartBuilder(ContentProviderOperation.newInsert(Data.CONTENT_URI));
if (entity == null) {
builder.withValueBackReference(Data.RAW_CONTACT_ID, contactId);
} else {
builder.withValue(Data.RAW_CONTACT_ID, contactId);
}
builder.withValue(Data.MIMETYPE, mimeType);
}
// Return the appropriate builder (insert or update)
// Caller will fill in the appropriate values; 4 MIMETYPE is already set
return builder;
}
/**
* Compare a column in a ContentValues with an (old) value, and see if they are the
* same. For this purpose, null and an empty string are considered the same.
* @param cv a ContentValues object, from a NamedContentValues
* @param column a column that might be in the ContentValues
* @param oldValue an old value (or null) to check against
* @return whether the column's value in the ContentValues matches oldValue
*/
private boolean cvCompareString(ContentValues cv, String column, String oldValue) {
if (cv.containsKey(column)) {
if (oldValue != null && cv.getAsString(column).equals(oldValue)) {
return true;
}
} else if (oldValue == null || oldValue.length() == 0) {
return true;
}
return false;
}
public void addEmail(Entity entity, int type, String email) {
SmartBuilder builder = createBuilder(entity, Email.CONTENT_ITEM_TYPE, type);
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
// Can't happen, but belt & suspenders
if (tokens.length == 0) {
return;
}
Rfc822Token token = tokens[0];
String addr = token.getAddress();
String name = token.getName();
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, Email.DATA, addr)
&& cvCompareString(cv, Email.DISPLAY_NAME, name)) {
return;
}
builder.withValue(Email.TYPE, type);
builder.withValue(Email.DATA, addr);
builder.withValue(Email.DISPLAY_NAME, name);
add(builder.build());
}
public void addChildren(Entity entity, ArrayList<String> children) {
SmartBuilder builder = createBuilder(entity, EasChildren.CONTENT_ITEM_TYPE, -1);
int i = 0;
for (String child: children) {
builder.withValue(EasChildren.ROWS[i++], child);
}
add(builder.build());
}
public void addGroup(Entity entity, String group) {
SmartBuilder builder =
createBuilder(entity, GroupMembership.CONTENT_ITEM_TYPE, -1, group);
builder.withValue(GroupMembership.GROUP_SOURCE_ID, group);
add(builder.build());
}
public void addName(Entity entity, String givenName, String familyName, String middleName,
String suffix, String displayName) {
SmartBuilder builder = createBuilder(entity, StructuredName.CONTENT_ITEM_TYPE, -1);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, StructuredName.GIVEN_NAME, givenName) &&
cvCompareString(cv, StructuredName.FAMILY_NAME, familyName) &&
cvCompareString(cv, StructuredName.MIDDLE_NAME, middleName) &&
cvCompareString(cv, StructuredName.SUFFIX, suffix)) {
return;
}
builder.withValue(StructuredName.GIVEN_NAME, givenName);
builder.withValue(StructuredName.FAMILY_NAME, familyName);
builder.withValue(StructuredName.MIDDLE_NAME, middleName);
builder.withValue(StructuredName.SUFFIX, suffix);
add(builder.build());
}
public void addYomi(Entity entity, String firstName, String lastName, String companyName) {
SmartBuilder builder = createBuilder(entity, Yomi.CONTENT_ITEM_TYPE, -1);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, Yomi.FIRST_NAME, firstName) &&
cvCompareString(cv, Yomi.LAST_NAME, lastName) &&
cvCompareString(cv, Yomi.COMPANY_NAME, companyName)) {
return;
}
builder.withValue(Yomi.FIRST_NAME, firstName);
builder.withValue(Yomi.LAST_NAME, lastName);
builder.withValue(Yomi.COMPANY_NAME, companyName);
add(builder.build());
}
public void addPersonal(Entity entity, EasPersonal personal) {
SmartBuilder builder = createBuilder(entity, EasPersonal.CONTENT_ITEM_TYPE, -1);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, EasPersonal.ANNIVERSARY, personal.anniversary) &&
cvCompareString(cv, EasPersonal.BIRTHDAY, personal.birthday) &&
cvCompareString(cv, EasPersonal.FILE_AS , personal.fileAs) &&
cvCompareString(cv, EasPersonal.SPOUSE, personal.spouse) &&
cvCompareString(cv, EasPersonal.TITLE, personal.title) &&
cvCompareString(cv, EasPersonal.WEBPAGE, personal.webpage)) {
return;
}
if (!personal.hasData()) {
return;
}
builder.withValue(EasPersonal.BIRTHDAY, personal.birthday);
builder.withValue(EasPersonal.FILE_AS, personal.fileAs);
builder.withValue(EasPersonal.ANNIVERSARY, personal.anniversary);
builder.withValue(EasPersonal.SPOUSE, personal.spouse);
builder.withValue(EasPersonal.TITLE, personal.title);
builder.withValue(EasPersonal.WEBPAGE, personal.webpage);
add(builder.build());
}
public void addBusiness(Entity entity, EasBusiness business) {
SmartBuilder builder = createBuilder(entity, EasPersonal.CONTENT_ITEM_TYPE, -1);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, EasBusiness.ACCOUNT_NAME, business.accountName) &&
cvCompareString(cv, EasBusiness.ASSISTANT_NAME, business.assistantName) &&
cvCompareString(cv, EasBusiness.CUSTOMER_ID, business.customerId) &&
cvCompareString(cv, EasBusiness.DEPARTMENT, business.department) &&
cvCompareString(cv, EasBusiness.GOVERNMENT_ID, business.governmentId) &&
cvCompareString(cv, EasBusiness.MANAGER_NAME, business.managerName) &&
cvCompareString(cv, EasBusiness.OFFICE_LOCATION, business.officeLocation)) {
return;
}
if (!business.hasData()) {
return;
}
builder.withValue(EasBusiness.ACCOUNT_NAME, business.accountName);
builder.withValue(EasBusiness.ASSISTANT_NAME, business.assistantName);
builder.withValue(EasBusiness.CUSTOMER_ID, business.customerId);
builder.withValue(EasBusiness.DEPARTMENT, business.department);
builder.withValue(EasBusiness.GOVERNMENT_ID, business.governmentId);
builder.withValue(EasBusiness.MANAGER_NAME, business.managerName);
builder.withValue(EasBusiness.OFFICE_LOCATION, business.officeLocation);
add(builder.build());
}
public void addPhoto(Entity entity, String photo) {
SmartBuilder builder = createBuilder(entity, Photo.CONTENT_ITEM_TYPE, -1);
// We're always going to add this; it's not worth trying to figure out whether the
// picture is the same as the one stored.
byte[] pic = Base64.decode(photo);
// Bitmap b = BitmapFactory.decodeByteArray (pic, 0, pic.length);
// if (b == null) {
// mService.userLog("Bitmap creation failed");
// } else {
// mService.userLog("W00t! Bitmap creation worked!");
// }
builder.withValue(Photo.PHOTO, pic);
add(builder.build());
}
public void addPhone(Entity entity, int type, String phone) {
SmartBuilder builder = createBuilder(entity, Phone.CONTENT_ITEM_TYPE, type);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, Phone.NUMBER, phone)) {
return;
}
builder.withValue(Phone.TYPE, type);
builder.withValue(Phone.NUMBER, phone);
add(builder.build());
}
public void addNickname(Entity entity, String name) {
SmartBuilder builder =
createBuilder(entity, Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE_DEFAULT);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, Nickname.NAME, name)) {
return;
}
builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
builder.withValue(Nickname.NAME, name);
add(builder.build());
}
public void addPostal(Entity entity, int type, String street, String city, String state,
String country, String code) {
SmartBuilder builder = createBuilder(entity, StructuredPostal.CONTENT_ITEM_TYPE,
type);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, StructuredPostal.CITY, city) &&
cvCompareString(cv, StructuredPostal.STREET, street) &&
cvCompareString(cv, StructuredPostal.COUNTRY, country) &&
cvCompareString(cv, StructuredPostal.POSTCODE, code) &&
cvCompareString(cv, StructuredPostal.REGION, state)) {
return;
}
builder.withValue(StructuredPostal.TYPE, type);
builder.withValue(StructuredPostal.CITY, city);
builder.withValue(StructuredPostal.STREET, street);
builder.withValue(StructuredPostal.COUNTRY, country);
builder.withValue(StructuredPostal.POSTCODE, code);
builder.withValue(StructuredPostal.REGION, state);
add(builder.build());
}
public void addIm(Entity entity, int type, String account) {
SmartBuilder builder = createBuilder(entity, Im.CONTENT_ITEM_TYPE, type);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, Im.DATA, account)) {
return;
}
builder.withValue(Im.TYPE, type);
builder.withValue(Im.DATA, account);
add(builder.build());
}
public void addOrganization(Entity entity, int type, String company, String title) {
SmartBuilder builder = createBuilder(entity, Organization.CONTENT_ITEM_TYPE, type);
ContentValues cv = builder.cv;
if (cv != null && cvCompareString(cv, Organization.COMPANY, company) &&
cvCompareString(cv, Organization.TITLE, title)) {
return;
}
builder.withValue(Organization.TYPE, type);
builder.withValue(Organization.COMPANY, company);
builder.withValue(Organization.TITLE, title);
add(builder.build());
}
public void addNote(Entity entity, String note) {
SmartBuilder builder = createBuilder(entity, Note.CONTENT_ITEM_TYPE, -1);
ContentValues cv = builder.cv;
if (note != null) {
note = note.replaceAll("\r\n", "\n");
}
if (cv != null && cvCompareString(cv, Note.NOTE, note)) {
return;
}
builder.withValue(Note.NOTE, note);
add(builder.build());
}
}
/**
* Generate the uri for the data row associated with this NamedContentValues object
* @param ncv the NamedContentValues object
* @return a uri that can be used to refer to this row
*/
public Uri dataUriFromNamedContentValues(NamedContentValues ncv) {
long id = ncv.values.getAsLong(RawContacts._ID);
Uri dataUri = ContentUris.withAppendedId(ncv.uri, id);
return dataUri;
}
@Override
public void cleanup(EasSyncService service) {
// Mark the changed contacts dirty = 0
// TODO Put this in a single batch
ContactOperations ops = new ContactOperations();
for (Long id: mUpdatedIdList) {
ops.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(RawContacts.CONTENT_URI, id))
.withValue(RawContacts.DIRTY, 0).build());
}
ops.execute();
}
@Override
public String getCollectionName() {
return "Contacts";
}
private void sendEmail(Serializer s, ContentValues cv) throws IOException {
// Get both parts of the email address (a newly created one in the UI won't have a name)
String addr = cv.getAsString(Email.DATA);
String name = cv.getAsString(Email.DISPLAY_NAME);
// Don't crash if we don't have a name
if (name == null) {
name = "";
}
String value = null;
// If there's no addr, just send an empty address (will delete it on the server)
// Otherwise compose it from name and addr
if (addr != null) {
value = '\"' + name + "\" <" + addr + '>';
}
switch (cv.getAsInteger(Email.TYPE)) {
case TYPE_EMAIL1:
s.data(Tags.CONTACTS_EMAIL1_ADDRESS, value);
break;
case TYPE_EMAIL2:
s.data(Tags.CONTACTS_EMAIL2_ADDRESS, value);
break;
case TYPE_EMAIL3:
s.data(Tags.CONTACTS_EMAIL3_ADDRESS, value);
break;
default:
break;
}
}
private void sendIm(Serializer s, ContentValues cv) throws IOException {
String value = cv.getAsString(Email.DATA);
switch (cv.getAsInteger(Email.TYPE)) {
case TYPE_IM1:
s.data(Tags.CONTACTS2_IM_ADDRESS, value);
break;
case TYPE_IM2:
s.data(Tags.CONTACTS2_IM_ADDRESS_2, value);
break;
case TYPE_IM3:
s.data(Tags.CONTACTS2_IM_ADDRESS_3, value);
break;
default:
break;
}
}
private void sendOnePostal(Serializer s, ContentValues cv, int[] fieldNames)
throws IOException{
if (cv.containsKey(StructuredPostal.CITY)) {
s.data(fieldNames[0], cv.getAsString(StructuredPostal.CITY));
}
if (cv.containsKey(StructuredPostal.COUNTRY)) {
s.data(fieldNames[1], cv.getAsString(StructuredPostal.COUNTRY));
}
if (cv.containsKey(StructuredPostal.POSTCODE)) {
s.data(fieldNames[2], cv.getAsString(StructuredPostal.POSTCODE));
}
if (cv.containsKey(StructuredPostal.REGION)) {
s.data(fieldNames[3], cv.getAsString(StructuredPostal.REGION));
}
if (cv.containsKey(StructuredPostal.STREET)) {
s.data(fieldNames[4], cv.getAsString(StructuredPostal.STREET));
}
}
private void sendStructuredPostal(Serializer s, ContentValues cv) throws IOException {
switch (cv.getAsInteger(StructuredPostal.TYPE)) {
case StructuredPostal.TYPE_HOME:
sendOnePostal(s, cv, new int[] {Tags.CONTACTS_HOME_ADDRESS_CITY,
Tags.CONTACTS_HOME_ADDRESS_COUNTRY,
Tags.CONTACTS_HOME_ADDRESS_POSTAL_CODE,
Tags.CONTACTS_HOME_ADDRESS_STATE,
Tags.CONTACTS_HOME_ADDRESS_STREET});
break;
case StructuredPostal.TYPE_WORK:
sendOnePostal(s, cv, new int[] {Tags.CONTACTS_BUSINESS_ADDRESS_CITY,
Tags.CONTACTS_BUSINESS_ADDRESS_COUNTRY,
Tags.CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE,
Tags.CONTACTS_BUSINESS_ADDRESS_STATE,
Tags.CONTACTS_BUSINESS_ADDRESS_STREET});
break;
case StructuredPostal.TYPE_OTHER:
sendOnePostal(s, cv, new int[] {Tags.CONTACTS_HOME_ADDRESS_CITY,
Tags.CONTACTS_OTHER_ADDRESS_COUNTRY,
Tags.CONTACTS_OTHER_ADDRESS_POSTAL_CODE,
Tags.CONTACTS_OTHER_ADDRESS_STATE,
Tags.CONTACTS_OTHER_ADDRESS_STREET});
break;
default:
break;
}
}
private void sendStructuredName(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(StructuredName.FAMILY_NAME)) {
s.data(Tags.CONTACTS_LAST_NAME, cv.getAsString(StructuredName.FAMILY_NAME));
}
if (cv.containsKey(StructuredName.GIVEN_NAME)) {
s.data(Tags.CONTACTS_FIRST_NAME, cv.getAsString(StructuredName.GIVEN_NAME));
}
if (cv.containsKey(StructuredName.MIDDLE_NAME)) {
s.data(Tags.CONTACTS_MIDDLE_NAME, cv.getAsString(StructuredName.MIDDLE_NAME));
}
if (cv.containsKey(StructuredName.SUFFIX)) {
s.data(Tags.CONTACTS_SUFFIX, cv.getAsString(StructuredName.SUFFIX));
}
}
private void sendBusiness(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(EasBusiness.ACCOUNT_NAME)) {
s.data(Tags.CONTACTS2_ACCOUNT_NAME, cv.getAsString(EasBusiness.ACCOUNT_NAME));
}
if (cv.containsKey(EasBusiness.ASSISTANT_NAME)) {
s.data(Tags.CONTACTS_ASSISTANT_NAME, cv.getAsString(EasBusiness.ASSISTANT_NAME));
}
if (cv.containsKey(EasBusiness.CUSTOMER_ID)) {
s.data(Tags.CONTACTS2_CUSTOMER_ID, cv.getAsString(EasBusiness.CUSTOMER_ID));
}
if (cv.containsKey(EasBusiness.DEPARTMENT)) {
s.data(Tags.CONTACTS_DEPARTMENT, cv.getAsString(EasBusiness.DEPARTMENT));
}
if (cv.containsKey(EasBusiness.GOVERNMENT_ID)) {
s.data(Tags.CONTACTS2_GOVERNMENT_ID, cv.getAsString(EasBusiness.GOVERNMENT_ID));
}
if (cv.containsKey(EasBusiness.MANAGER_NAME)) {
s.data(Tags.CONTACTS2_MANAGER_NAME, cv.getAsString(EasBusiness.MANAGER_NAME));
}
if (cv.containsKey(EasBusiness.OFFICE_LOCATION)) {
s.data(Tags.CONTACTS_OFFICE_LOCATION, cv.getAsString(EasBusiness.OFFICE_LOCATION));
}
}
private void sendPersonal(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(EasPersonal.ANNIVERSARY)) {
s.data(Tags.CONTACTS_ANNIVERSARY, cv.getAsString(EasPersonal.ANNIVERSARY));
}
if (cv.containsKey(EasPersonal.BIRTHDAY)) {
s.data(Tags.CONTACTS_BIRTHDAY, cv.getAsString(EasPersonal.BIRTHDAY));
}
if (cv.containsKey(EasPersonal.FILE_AS)) {
s.data(Tags.CONTACTS_FILE_AS, cv.getAsString(EasPersonal.FILE_AS));
}
if (cv.containsKey(EasPersonal.SPOUSE)) {
s.data(Tags.CONTACTS_SPOUSE, cv.getAsString(EasPersonal.SPOUSE));
}
if (cv.containsKey(EasPersonal.TITLE)) {
s.data(Tags.CONTACTS_TITLE, cv.getAsString(EasPersonal.TITLE));
}
if (cv.containsKey(EasPersonal.WEBPAGE)) {
s.data(Tags.CONTACTS_WEBPAGE, cv.getAsString(EasPersonal.WEBPAGE));
}
}
private void sendYomi(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(Yomi.FIRST_NAME)) {
s.data(Tags.CONTACTS_YOMI_FIRST_NAME, cv.getAsString(Yomi.FIRST_NAME));
}
if (cv.containsKey(Yomi.LAST_NAME)) {
s.data(Tags.CONTACTS_YOMI_LAST_NAME, cv.getAsString(Yomi.LAST_NAME));
}
if (cv.containsKey(Yomi.COMPANY_NAME)) {
s.data(Tags.CONTACTS_YOMI_COMPANY_NAME, cv.getAsString(Yomi.COMPANY_NAME));
}
}
private void sendOrganization(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(Organization.TITLE)) {
s.data(Tags.CONTACTS_JOB_TITLE, cv.getAsString(Organization.TITLE));
}
if (cv.containsKey(Organization.COMPANY)) {
s.data(Tags.CONTACTS_COMPANY_NAME, cv.getAsString(Organization.COMPANY));
}
}
private void sendNickname(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(Nickname.NAME)) {
s.data(Tags.CONTACTS2_NICKNAME, cv.getAsString(Nickname.NAME));
}
}
private void sendNote(Serializer s, ContentValues cv) throws IOException {
if (cv.containsKey(Note.NOTE)) {
// EAS won't accept note data with raw newline characters
String note = cv.getAsString(Note.NOTE).replaceAll("\n", "\r\n");
// Format of upsync data depends on protocol version
if (mService.mProtocolVersionDouble >= 12.0) {
s.start(Tags.BASE_BODY);
s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_TEXT).data(Tags.BASE_DATA, note);
s.end();
} else {
s.data(Tags.CONTACTS_BODY, note);
}
}
}
private void sendChildren(Serializer s, ContentValues cv) throws IOException {
boolean first = true;
for (int i = 0; i < EasChildren.MAX_CHILDREN; i++) {
String row = EasChildren.ROWS[i];
if (cv.containsKey(row)) {
if (first) {
s.start(Tags.CONTACTS_CHILDREN);
first = false;
}
s.data(Tags.CONTACTS_CHILD, cv.getAsString(row));
}
}
if (!first) {
s.end();
}
}
private void sendPhone(Serializer s, ContentValues cv) throws IOException {
String value = cv.getAsString(Phone.NUMBER);
switch (cv.getAsInteger(Phone.TYPE)) {
case TYPE_WORK2:
s.data(Tags.CONTACTS_BUSINESS2_TELEPHONE_NUMBER, value);
break;
case Phone.TYPE_WORK:
s.data(Tags.CONTACTS_BUSINESS_TELEPHONE_NUMBER, value);
break;
case TYPE_MMS:
s.data(Tags.CONTACTS2_MMS, value);
break;
case Phone.TYPE_FAX_WORK:
s.data(Tags.CONTACTS_BUSINESS_FAX_NUMBER, value);
break;
case TYPE_COMPANY_MAIN:
s.data(Tags.CONTACTS2_COMPANY_MAIN_PHONE, value);
break;
case Phone.TYPE_HOME:
s.data(Tags.CONTACTS_HOME_TELEPHONE_NUMBER, value);
break;
case TYPE_HOME2:
s.data(Tags.CONTACTS_HOME2_TELEPHONE_NUMBER, value);
break;
case Phone.TYPE_MOBILE:
s.data(Tags.CONTACTS_MOBILE_TELEPHONE_NUMBER, value);
break;
case TYPE_CAR:
s.data(Tags.CONTACTS_CAR_TELEPHONE_NUMBER, value);
break;
case Phone.TYPE_PAGER:
s.data(Tags.CONTACTS_PAGER_NUMBER, value);
break;
case TYPE_RADIO:
s.data(Tags.CONTACTS_RADIO_TELEPHONE_NUMBER, value);
break;
case Phone.TYPE_FAX_HOME:
s.data(Tags.CONTACTS_HOME_FAX_NUMBER, value);
break;
case TYPE_EMAIL2:
s.data(Tags.CONTACTS_EMAIL2_ADDRESS, value);
break;
case TYPE_EMAIL3:
s.data(Tags.CONTACTS_EMAIL3_ADDRESS, value);
break;
default:
break;
}
}
@Override
public boolean sendLocalChanges(Serializer s, EasSyncService service) throws IOException {
// First, let's find Contacts that have changed.
ContentResolver cr = service.mContentResolver;
Uri uri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, service.mAccount.mEmailAddress)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, Eas.ACCOUNT_MANAGER_TYPE)
.build();
if (service.mMailbox.mSyncKey.equals("0")) {
return false;
}
try {
// Get them all atomically
EntityIterator ei = cr.queryEntities(uri, RawContacts.DIRTY + "=1", null, null);
try {
boolean first = true;
while (ei.hasNext()) {
Entity entity = ei.next();
// For each of these entities, create the change commands
ContentValues entityValues = entity.getEntityValues();
String serverId = entityValues.getAsString(RawContacts.SOURCE_ID);
ArrayList<Integer> groupIds = new ArrayList<Integer>();
if (first) {
s.start(Tags.SYNC_COMMANDS);
first = false;
}
s.start(Tags.SYNC_CHANGE).data(Tags.SYNC_SERVER_ID, serverId)
.start(Tags.SYNC_APPLICATION_DATA);
// Write out the data here
for (NamedContentValues ncv: entity.getSubValues()) {
ContentValues cv = ncv.values;
String mimeType = cv.getAsString(Data.MIMETYPE);
if (mimeType.equals(Email.CONTENT_ITEM_TYPE)) {
sendEmail(s, cv);
} else if (mimeType.equals(Nickname.CONTENT_ITEM_TYPE)) {
sendNickname(s, cv);
} else if (mimeType.equals(EasChildren.CONTENT_ITEM_TYPE)) {
sendChildren(s, cv);
} else if (mimeType.equals(EasBusiness.CONTENT_ITEM_TYPE)) {
sendBusiness(s, cv);
} else if (mimeType.equals(EasPersonal.CONTENT_ITEM_TYPE)) {
sendPersonal(s, cv);
} else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
sendPhone(s, cv);
} else if (mimeType.equals(Yomi.CONTENT_ITEM_TYPE)) {
sendYomi(s, cv);
} else if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
sendStructuredName(s, cv);
} else if (mimeType.equals(StructuredPostal.CONTENT_ITEM_TYPE)) {
sendStructuredPostal(s, cv);
} else if (mimeType.equals(Organization.CONTENT_ITEM_TYPE)) {
sendOrganization(s, cv);
} else if (mimeType.equals(Im.CONTENT_ITEM_TYPE)) {
sendIm(s, cv);
} else if (mimeType.equals(GroupMembership.CONTENT_ITEM_TYPE)) {
// We must gather these, and send them together (below)
groupIds.add(cv.getAsInteger(GroupMembership.GROUP_ROW_ID));
} else if (mimeType.equals(Note.CONTENT_ITEM_TYPE)) {
sendNote(s, cv);
} else if (mimeType.equals(Photo.CONTENT_ITEM_TYPE)) {
// For now, the user can change the photo, but the change won't be
// uploaded.
} else {
mService.userLog("Contacts upsync, unknown data: " + mimeType);
}
}
// Now, we'll send up groups, if any
if (!groupIds.isEmpty()) {
boolean groupFirst = true;
for (int id: groupIds) {
// Since we get id's from the provider, we need to find their names
Cursor c = cr.query(ContentUris.withAppendedId(Groups.CONTENT_URI, id),
GROUP_PROJECTION, null, null, null);
try {
// Presumably, this should always succeed, but ...
if (c.moveToFirst()) {
if (groupFirst) {
s.start(Tags.CONTACTS_CATEGORIES);
groupFirst = false;
}
s.data(Tags.CONTACTS_CATEGORY, c.getString(0));
}
} finally {
c.close();
}
}
if (!groupFirst) {
s.end();
}
}
s.end().end(); // ApplicationData & Change
mUpdatedIdList.add(entityValues.getAsLong(RawContacts._ID));
}
if (!first) {
s.end(); // Commands
}
} finally {
ei.close();
}
} catch (RemoteException e) {
Log.e(TAG, "Could not read dirty contacts.");
}
return false;
}
}