| package com.android.exchange.adapter; |
| |
| import android.content.ContentProviderOperation; |
| import android.content.ContentProviderOperation.Builder; |
| import android.content.ContentProviderResult; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Entity; |
| import android.content.Entity.NamedContentValues; |
| import android.content.EntityIterator; |
| import android.content.OperationApplicationException; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.CommonDataKinds.Email; |
| import android.provider.ContactsContract.CommonDataKinds.Event; |
| 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.Relation; |
| import android.provider.ContactsContract.CommonDataKinds.StructuredName; |
| import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; |
| import android.provider.ContactsContract.CommonDataKinds.Website; |
| import android.provider.ContactsContract.Data; |
| import android.provider.ContactsContract.RawContacts; |
| import android.provider.ContactsContract.SyncState; |
| import android.provider.SyncStateContract; |
| import android.text.util.Rfc822Token; |
| import android.text.util.Rfc822Tokenizer; |
| import android.util.Base64; |
| |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.Mailbox; |
| import com.android.emailcommon.utility.Utility; |
| import com.android.exchange.Eas; |
| import com.android.exchange.service.EasContactsSyncHandler; |
| import com.android.exchange.utility.CalendarUtilities; |
| import com.android.mail.utils.LogUtils; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.GregorianCalendar; |
| import java.util.TimeZone; |
| |
| public class ContactsSyncParser extends AbstractSyncParser { |
| private static final String TAG = "ContactsSyncParser"; |
| |
| private static final String SERVER_ID_SELECTION = RawContacts.SOURCE_ID + "=?"; |
| private static final String CLIENT_ID_SELECTION = RawContacts.SYNC1 + "=?"; |
| private static final String[] ID_PROJECTION = new String[] {RawContacts._ID}; |
| |
| private static final ArrayList<NamedContentValues> EMPTY_ARRAY_NAMEDCONTENTVALUES |
| = new ArrayList<NamedContentValues>(); |
| |
| private static final String FOUND_DATA_ROW = "com.android.exchange.FOUND_ROW"; |
| |
| private static final int MAX_IM_ROWS = 3; |
| private static final int MAX_EMAIL_ROWS = 3; |
| private static final int MAX_PHONE_ROWS = 2; |
| private static final String COMMON_DATA_ROW = Im.DATA; // Could have been Email.DATA, etc. |
| private static final String COMMON_TYPE_ROW = Phone.TYPE; // Could have been any typed row |
| |
| String[] mBindArgument = new String[1]; |
| ContactOperations ops = new ContactOperations(); |
| private final android.accounts.Account mAccountManagerAccount; |
| private final Uri mAccountUri; |
| private boolean mGroupsUsed = false; |
| |
| public ContactsSyncParser(final Context context, final ContentResolver resolver, |
| final InputStream in, final Mailbox mailbox, final Account account, |
| final android.accounts.Account accountManagerAccount) throws IOException { |
| super(context, resolver, in, mailbox, account); |
| mAccountManagerAccount = accountManagerAccount; |
| mAccountUri = uriWithAccountAndIsSyncAdapter(RawContacts.CONTENT_URI, |
| mAccount.mEmailAddress); |
| } |
| |
| public boolean isGroupsUsed() { |
| return mGroupsUsed; |
| } |
| |
| public void addData(String serverId, ContactOperations ops, Entity entity) |
| throws IOException { |
| String prefix = null; |
| 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; |
| String department = null; |
| String officeLocation = 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>(); |
| ArrayList<UntypedRow> emails = new ArrayList<UntypedRow>(); |
| ArrayList<UntypedRow> ims = new ArrayList<UntypedRow>(); |
| ArrayList<UntypedRow> homePhones = new ArrayList<UntypedRow>(); |
| ArrayList<UntypedRow> workPhones = new ArrayList<UntypedRow>(); |
| if (entity == null) { |
| ops.newContact(serverId, mAccount.mEmailAddress); |
| } |
| |
| 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: |
| case Tags.CONTACTS_EMAIL2_ADDRESS: |
| case Tags.CONTACTS_EMAIL3_ADDRESS: |
| emails.add(new EmailRow(getValue())); |
| break; |
| case Tags.CONTACTS_BUSINESS2_TELEPHONE_NUMBER: |
| case Tags.CONTACTS_BUSINESS_TELEPHONE_NUMBER: |
| workPhones.add(new PhoneRow(getValue(), Phone.TYPE_WORK)); |
| break; |
| case Tags.CONTACTS2_MMS: |
| ops.addPhone(entity, Phone.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, Phone.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: |
| case Tags.CONTACTS_HOME2_TELEPHONE_NUMBER: |
| homePhones.add(new PhoneRow(getValue(), Phone.TYPE_HOME)); |
| break; |
| case Tags.CONTACTS_MOBILE_TELEPHONE_NUMBER: |
| ops.addPhone(entity, Phone.TYPE_MOBILE, getValue()); |
| break; |
| case Tags.CONTACTS_CAR_TELEPHONE_NUMBER: |
| ops.addPhone(entity, Phone.TYPE_CAR, getValue()); |
| break; |
| case Tags.CONTACTS_RADIO_TELEPHONE_NUMBER: |
| ops.addPhone(entity, Phone.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, Phone.TYPE_ASSISTANT, getValue()); |
| break; |
| case Tags.CONTACTS2_IM_ADDRESS: |
| case Tags.CONTACTS2_IM_ADDRESS_2: |
| case Tags.CONTACTS2_IM_ADDRESS_3: |
| ims.add(new ImRow(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; |
| |
| case Tags.CONTACTS_ASSISTANT_NAME: |
| ops.addRelation(entity, Relation.TYPE_ASSISTANT, getValue()); |
| break; |
| case Tags.CONTACTS2_MANAGER_NAME: |
| ops.addRelation(entity, Relation.TYPE_MANAGER, getValue()); |
| break; |
| case Tags.CONTACTS_SPOUSE: |
| ops.addRelation(entity, Relation.TYPE_SPOUSE, getValue()); |
| break; |
| case Tags.CONTACTS_DEPARTMENT: |
| department = getValue(); |
| break; |
| case Tags.CONTACTS_TITLE: |
| prefix = getValue(); |
| break; |
| |
| // EAS Business |
| case Tags.CONTACTS_OFFICE_LOCATION: |
| officeLocation = 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: |
| ops.addBirthday(entity, getValue()); |
| break; |
| case Tags.CONTACTS_WEBPAGE: |
| ops.addWebpage(entity, 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; |
| |
| case Tags.CONTACTS_CATEGORIES: |
| mGroupsUsed = true; |
| categoriesParser(ops, entity); |
| break; |
| |
| default: |
| skipTag(); |
| } |
| } |
| |
| ops.addName(entity, prefix, firstName, lastName, middleName, suffix, |
| yomiFirstName, yomiLastName); |
| ops.addBusiness(entity, business); |
| ops.addPersonal(entity, personal); |
| |
| ops.addUntyped(entity, emails, Email.CONTENT_ITEM_TYPE, -1, MAX_EMAIL_ROWS); |
| ops.addUntyped(entity, ims, Im.CONTENT_ITEM_TYPE, -1, MAX_IM_ROWS); |
| ops.addUntyped(entity, homePhones, Phone.CONTENT_ITEM_TYPE, Phone.TYPE_HOME, |
| MAX_PHONE_ROWS); |
| ops.addUntyped(entity, workPhones, Phone.CONTENT_ITEM_TYPE, Phone.TYPE_WORK, |
| MAX_PHONE_ROWS); |
| |
| 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, department, |
| yomiCompanyName, officeLocation); |
| } |
| |
| 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(addCallerIsSyncAdapterParameter(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()); |
| break; |
| 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); |
| } |
| |
| private Cursor getClientIdCursor(String clientId) { |
| mBindArgument[0] = clientId; |
| return mContentResolver.query(mAccountUri, ID_PROJECTION, CLIENT_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()) { |
| 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... |
| Uri uri = ContentUris.withAppendedId( |
| RawContacts.CONTENT_URI, c.getLong(0)); |
| uri = Uri.withAppendedPath( |
| uri, RawContacts.Entity.CONTENT_DIRECTORY); |
| EntityIterator entityIterator = RawContacts.newEntityIterator( |
| mContentResolver.query(uri, null, null, null, null)); |
| if (entityIterator.hasNext()) { |
| entity = entityIterator.next(); |
| } |
| userLog("Changing contact ", serverId); |
| } |
| } finally { |
| c.close(); |
| } |
| break; |
| case Tags.SYNC_APPLICATION_DATA: |
| addData(serverId, ops, entity); |
| break; |
| default: |
| skipTag(); |
| } |
| } |
| } |
| |
| @Override |
| public void commandsParser() throws IOException { |
| while (nextTag(Tags.SYNC_COMMANDS) != END) { |
| if (tag == Tags.SYNC_ADD) { |
| addParser(ops); |
| } else if (tag == Tags.SYNC_DELETE) { |
| deleteParser(ops); |
| } else if (tag == Tags.SYNC_CHANGE) { |
| changeParser(ops); |
| } else |
| skipTag(); |
| } |
| } |
| |
| @Override |
| public void commit() throws IOException { |
| // Save the syncKey here, using the Helper provider by Contacts provider |
| userLog("Contacts SyncKey saved as: ", mMailbox.mSyncKey); |
| ops.add(SyncStateContract.Helpers.newSetOperation(SyncState.CONTENT_URI, |
| mAccountManagerAccount, mMailbox.mSyncKey.getBytes())); |
| |
| // Execute these all at once... |
| ops.execute(mContext); |
| |
| 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(); |
| mContentResolver.update( |
| addCallerIsSyncAdapterParameter(RawContacts.CONTENT_URI), cv, |
| RawContacts._ID + "=" + idString, null); |
| } |
| } |
| } |
| } |
| |
| public void addResponsesParser() throws IOException { |
| String serverId = null; |
| String clientId = null; |
| ContentValues cv = new ContentValues(); |
| while (nextTag(Tags.SYNC_ADD) != END) { |
| switch (tag) { |
| case Tags.SYNC_SERVER_ID: |
| serverId = getValue(); |
| break; |
| case Tags.SYNC_CLIENT_ID: |
| clientId = getValue(); |
| break; |
| case Tags.SYNC_STATUS: |
| getValue(); |
| break; |
| default: |
| skipTag(); |
| } |
| } |
| |
| // This is theoretically impossible, but... |
| if (clientId == null || serverId == null) return; |
| |
| Cursor c = getClientIdCursor(clientId); |
| try { |
| if (c.moveToFirst()) { |
| cv.put(RawContacts.SOURCE_ID, serverId); |
| cv.put(RawContacts.DIRTY, 0); |
| ops.add(ContentProviderOperation.newUpdate( |
| ContentUris.withAppendedId( |
| addCallerIsSyncAdapterParameter(RawContacts.CONTENT_URI), |
| c.getLong(0))) |
| .withValues(cv) |
| .build()); |
| userLog("New contact " + clientId + " was given serverId: " + serverId); |
| } |
| } finally { |
| c.close(); |
| } |
| } |
| |
| public void changeResponsesParser() throws IOException { |
| String serverId = null; |
| String status = null; |
| while (nextTag(Tags.SYNC_CHANGE) != END) { |
| switch (tag) { |
| case Tags.SYNC_SERVER_ID: |
| serverId = getValue(); |
| break; |
| case Tags.SYNC_STATUS: |
| status = getValue(); |
| break; |
| default: |
| skipTag(); |
| } |
| } |
| if (serverId != null && status != null) { |
| userLog("Changed contact " + serverId + " failed with status: " + status); |
| } |
| } |
| |
| |
| @Override |
| public void responsesParser() throws IOException { |
| // Handle server responses here (for Add and Change) |
| while (nextTag(Tags.SYNC_RESPONSES) != END) { |
| if (tag == Tags.SYNC_ADD) { |
| addResponsesParser(); |
| } else if (tag == Tags.SYNC_CHANGE) { |
| changeResponsesParser(); |
| } else |
| skipTag(); |
| } |
| } |
| |
| private static Uri uriWithAccountAndIsSyncAdapter(final Uri uri, final String emailAddress) { |
| return uri.buildUpon() |
| .appendQueryParameter(RawContacts.ACCOUNT_NAME, emailAddress) |
| .appendQueryParameter(RawContacts.ACCOUNT_TYPE, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE) |
| .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") |
| .build(); |
| } |
| |
| static Uri addCallerIsSyncAdapterParameter(Uri uri) { |
| return uri.buildUpon() |
| .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") |
| .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 static Uri dataUriFromNamedContentValues(NamedContentValues ncv) { |
| long id = ncv.values.getAsLong(RawContacts._ID); |
| Uri dataUri = ContentUris.withAppendedId(ncv.uri, id); |
| return dataUri; |
| } |
| |
| 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 fileAs; |
| |
| /** 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 FILE_AS = "data4"; |
| |
| boolean hasData() { |
| return anniversary != null || fileAs != null; |
| } |
| } |
| |
| public static final class EasBusiness { |
| 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 CUSTOMER_ID = "data6"; |
| public static final String GOVERNMENT_ID = "data7"; |
| public static final String ACCOUNT_NAME = "data8"; |
| |
| boolean hasData() { |
| return 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; |
| } |
| } |
| |
| interface UntypedRow { |
| public void addValues(RowBuilder builder); |
| public boolean isSameAs(int type, String value); |
| } |
| |
| static class EmailRow implements UntypedRow { |
| String email; |
| String displayName; |
| |
| public EmailRow(String _email) { |
| Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(_email); |
| // Can't happen, but belt & suspenders |
| if (tokens.length == 0) { |
| email = ""; |
| displayName = ""; |
| } else { |
| Rfc822Token token = tokens[0]; |
| email = token.getAddress(); |
| displayName = token.getName(); |
| } |
| } |
| |
| @Override |
| public void addValues(RowBuilder builder) { |
| builder.withValue(Email.DATA, email); |
| builder.withValue(Email.DISPLAY_NAME, displayName); |
| } |
| |
| @Override |
| public boolean isSameAs(int type, String value) { |
| return email.equalsIgnoreCase(value); |
| } |
| } |
| |
| static class ImRow implements UntypedRow { |
| String im; |
| |
| public ImRow(String _im) { |
| im = _im; |
| } |
| |
| @Override |
| public void addValues(RowBuilder builder) { |
| builder.withValue(Im.DATA, im); |
| } |
| |
| @Override |
| public boolean isSameAs(int type, String value) { |
| return im.equalsIgnoreCase(value); |
| } |
| } |
| |
| static class PhoneRow implements UntypedRow { |
| String phone; |
| int type; |
| |
| public PhoneRow(String _phone, int _type) { |
| phone = _phone; |
| type = _type; |
| } |
| |
| @Override |
| public void addValues(RowBuilder builder) { |
| builder.withValue(Im.DATA, phone); |
| builder.withValue(Phone.TYPE, type); |
| } |
| |
| @Override |
| public boolean isSameAs(int _type, String value) { |
| return type == _type && phone.equalsIgnoreCase(value); |
| } |
| } |
| |
| /** |
| * RowBuilder 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 static class RowBuilder { |
| Builder builder; |
| ContentValues cv; |
| |
| public RowBuilder(Builder _builder) { |
| builder = _builder; |
| } |
| |
| public RowBuilder(Builder _builder, NamedContentValues _ncv) { |
| builder = _builder; |
| cv = _ncv.values; |
| } |
| |
| RowBuilder withValueBackReference(String key, int previousResult) { |
| builder.withValueBackReference(key, previousResult); |
| return this; |
| } |
| |
| ContentProviderOperation build() { |
| return builder.build(); |
| } |
| |
| RowBuilder withValue(String key, Object value) { |
| builder.withValue(key, value); |
| return this; |
| } |
| } |
| public static 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 final int[] mContactIndexArray = |
| new int[EasContactsSyncHandler.PIM_WINDOW_SIZE_CONTACTS]; |
| private int mContactIndexCount = 0; |
| private ContentProviderResult[] mResults = null; |
| |
| @Override |
| public boolean add(ContentProviderOperation op) { |
| super.add(op); |
| mCount++; |
| return true; |
| } |
| |
| public void newContact(final String serverId, final String emailAddress) { |
| Builder builder = ContentProviderOperation.newInsert( |
| uriWithAccountAndIsSyncAdapter(RawContacts.CONTENT_URI, emailAddress)); |
| 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.CALLER_IS_SYNCADAPTER, "true") |
| .build()) |
| .build()); |
| } |
| |
| public void execute(final Context context) { |
| try { |
| if (!isEmpty()) { |
| mResults = context.getContentResolver().applyBatch( |
| ContactsContract.AUTHORITY, this); |
| } |
| } catch (RemoteException e) { |
| // There is nothing sensible to be done here |
| LogUtils.e(TAG, "problem inserting contact during server update", e); |
| } catch (OperationApplicationException e) { |
| // There is nothing sensible to be done here |
| LogUtils.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 static NamedContentValues findTypedData(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 |
| // We'll accept either type < 0 (don't care), cv doesn't have a type, |
| // or the types are equal |
| } else if (type < 0 || !cv.containsKey(Email.TYPE) || |
| cv.getAsInteger(Email.TYPE) == type) { |
| result = namedContentValues; |
| } |
| } |
| } |
| } |
| |
| // 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; |
| } |
| |
| /** |
| * Given the list of NamedContentValues for an entity and a mime type |
| * gather all of the matching NCV's, returning them |
| * @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 NCVs |
| */ |
| private static ArrayList<NamedContentValues> findUntypedData( |
| ArrayList<NamedContentValues> list, int type, String contentItemType) { |
| ArrayList<NamedContentValues> result = new ArrayList<NamedContentValues>(); |
| |
| // 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 (type != -1) { |
| int subtype = cv.getAsInteger(Phone.TYPE); |
| if (type != subtype) { |
| continue; |
| } |
| } |
| result.add(namedContentValues); |
| } |
| } |
| } |
| |
| // If we've found an existing data row, we'll delete it. Any rows left at the |
| // end should be deleted... |
| for (NamedContentValues values : result) { |
| list.remove(values); |
| } |
| |
| // 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 RowBuilder createBuilder(Entity entity, String mimeType, int type, |
| String stringType) { |
| RowBuilder builder = null; |
| |
| if (entity != null) { |
| NamedContentValues ncv = |
| findTypedData(entity.getSubValues(), mimeType, type, stringType); |
| if (ncv != null) { |
| builder = new RowBuilder( |
| ContentProviderOperation |
| .newUpdate(addCallerIsSyncAdapterParameter( |
| dataUriFromNamedContentValues(ncv))), |
| ncv); |
| } |
| } |
| |
| if (builder == null) { |
| builder = newRowBuilder(entity, mimeType); |
| } |
| |
| // Return the appropriate builder (insert or update) |
| // Caller will fill in the appropriate values; 4 MIMETYPE is already set |
| return builder; |
| } |
| |
| private RowBuilder typedRowBuilder(Entity entity, String mimeType, int type) { |
| return createBuilder(entity, mimeType, type, null); |
| } |
| |
| private RowBuilder untypedRowBuilder(Entity entity, String mimeType) { |
| return createBuilder(entity, mimeType, -1, null); |
| } |
| |
| private RowBuilder newRowBuilder(Entity entity, String mimeType) { |
| // This is a new row; first get the contactId |
| // If the Contact is new, use the saved back value; otherwise the value in the entity |
| int contactId = mContactBackValue; |
| if (entity != null) { |
| contactId = entity.getEntityValues().getAsInteger(RawContacts._ID); |
| } |
| |
| // Create an insert operation with the proper contactId reference |
| RowBuilder builder = |
| new RowBuilder(ContentProviderOperation.newInsert( |
| addCallerIsSyncAdapterParameter(Data.CONTENT_URI))); |
| if (entity == null) { |
| builder.withValueBackReference(Data.RAW_CONTACT_ID, contactId); |
| } else { |
| builder.withValue(Data.RAW_CONTACT_ID, contactId); |
| } |
| |
| // Set the mime type of the row |
| builder.withValue(Data.MIMETYPE, mimeType); |
| 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 static 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 addChildren(Entity entity, ArrayList<String> children) { |
| RowBuilder builder = untypedRowBuilder(entity, EasChildren.CONTENT_ITEM_TYPE); |
| int i = 0; |
| for (String child: children) { |
| builder.withValue(EasChildren.ROWS[i++], child); |
| } |
| add(builder.build()); |
| } |
| |
| public void addGroup(Entity entity, String group) { |
| RowBuilder builder = |
| createBuilder(entity, GroupMembership.CONTENT_ITEM_TYPE, -1, group); |
| builder.withValue(GroupMembership.GROUP_SOURCE_ID, group); |
| add(builder.build()); |
| } |
| |
| public void addBirthday(Entity entity, String birthday) { |
| RowBuilder builder = |
| typedRowBuilder(entity, Event.CONTENT_ITEM_TYPE, Event.TYPE_BIRTHDAY); |
| ContentValues cv = builder.cv; |
| if (cv != null && cvCompareString(cv, Event.START_DATE, birthday)) { |
| return; |
| } |
| long millis = Utility.parseEmailDateTimeToMillis(birthday); |
| GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); |
| cal.setTimeInMillis(millis); |
| if (cal.get(GregorianCalendar.HOUR_OF_DAY) >= 12) { |
| cal.add(GregorianCalendar.DATE, 1); |
| } |
| String realBirthday = CalendarUtilities.calendarToBirthdayString(cal); |
| builder.withValue(Event.START_DATE, realBirthday); |
| builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY); |
| add(builder.build()); |
| } |
| |
| public void addName(Entity entity, String prefix, String givenName, String familyName, |
| String middleName, String suffix, String yomiFirstName, String yomiLastName) { |
| RowBuilder builder = untypedRowBuilder(entity, StructuredName.CONTENT_ITEM_TYPE); |
| 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.PREFIX, prefix) && |
| cvCompareString(cv, StructuredName.PHONETIC_GIVEN_NAME, yomiFirstName) && |
| cvCompareString(cv, StructuredName.PHONETIC_FAMILY_NAME, yomiLastName) && |
| 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); |
| builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, yomiFirstName); |
| builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, yomiLastName); |
| builder.withValue(StructuredName.PREFIX, prefix); |
| add(builder.build()); |
| } |
| |
| public void addPersonal(Entity entity, EasPersonal personal) { |
| RowBuilder builder = untypedRowBuilder(entity, EasPersonal.CONTENT_ITEM_TYPE); |
| ContentValues cv = builder.cv; |
| if (cv != null && cvCompareString(cv, EasPersonal.ANNIVERSARY, personal.anniversary) && |
| cvCompareString(cv, EasPersonal.FILE_AS , personal.fileAs)) { |
| return; |
| } |
| if (!personal.hasData()) { |
| return; |
| } |
| builder.withValue(EasPersonal.FILE_AS, personal.fileAs); |
| builder.withValue(EasPersonal.ANNIVERSARY, personal.anniversary); |
| add(builder.build()); |
| } |
| |
| public void addBusiness(Entity entity, EasBusiness business) { |
| RowBuilder builder = untypedRowBuilder(entity, EasBusiness.CONTENT_ITEM_TYPE); |
| ContentValues cv = builder.cv; |
| if (cv != null && cvCompareString(cv, EasBusiness.ACCOUNT_NAME, business.accountName) && |
| cvCompareString(cv, EasBusiness.CUSTOMER_ID, business.customerId) && |
| cvCompareString(cv, EasBusiness.GOVERNMENT_ID, business.governmentId)) { |
| return; |
| } |
| if (!business.hasData()) { |
| return; |
| } |
| builder.withValue(EasBusiness.ACCOUNT_NAME, business.accountName); |
| builder.withValue(EasBusiness.CUSTOMER_ID, business.customerId); |
| builder.withValue(EasBusiness.GOVERNMENT_ID, business.governmentId); |
| add(builder.build()); |
| } |
| |
| public void addPhoto(Entity entity, String photo) { |
| RowBuilder builder = untypedRowBuilder(entity, Photo.CONTENT_ITEM_TYPE); |
| // 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, Base64.DEFAULT); |
| builder.withValue(Photo.PHOTO, pic); |
| add(builder.build()); |
| } |
| |
| public void addPhone(Entity entity, int type, String phone) { |
| RowBuilder builder = typedRowBuilder(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 addWebpage(Entity entity, String url) { |
| RowBuilder builder = untypedRowBuilder(entity, Website.CONTENT_ITEM_TYPE); |
| ContentValues cv = builder.cv; |
| if (cv != null && cvCompareString(cv, Website.URL, url)) { |
| return; |
| } |
| builder.withValue(Website.TYPE, Website.TYPE_WORK); |
| builder.withValue(Website.URL, url); |
| add(builder.build()); |
| } |
| |
| public void addRelation(Entity entity, int type, String value) { |
| RowBuilder builder = typedRowBuilder(entity, Relation.CONTENT_ITEM_TYPE, type); |
| ContentValues cv = builder.cv; |
| if (cv != null && cvCompareString(cv, Relation.DATA, value)) { |
| return; |
| } |
| builder.withValue(Relation.TYPE, type); |
| builder.withValue(Relation.DATA, value); |
| add(builder.build()); |
| } |
| |
| public void addNickname(Entity entity, String name) { |
| RowBuilder builder = |
| typedRowBuilder(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) { |
| RowBuilder builder = typedRowBuilder(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()); |
| } |
| |
| /** |
| * We now are dealing with up to maxRows typeless rows of mimeType data. We need to try to |
| * match them with existing rows; if there's a match, everything's great. Otherwise, we |
| * either need to add a new row for the data, or we have to replace an existing one |
| * that no longer matches. This is similar to the way Emails are handled. |
| */ |
| public void addUntyped(Entity entity, ArrayList<UntypedRow> rows, String mimeType, |
| int type, int maxRows) { |
| // Make a list of all same type rows in the existing entity |
| ArrayList<NamedContentValues> oldValues = EMPTY_ARRAY_NAMEDCONTENTVALUES; |
| ArrayList<NamedContentValues> entityValues = EMPTY_ARRAY_NAMEDCONTENTVALUES; |
| if (entity != null) { |
| oldValues = findUntypedData(entityValues, type, mimeType); |
| entityValues = entity.getSubValues(); |
| } |
| |
| // These will be rows needing replacement with new values |
| ArrayList<UntypedRow> rowsToReplace = new ArrayList<UntypedRow>(); |
| |
| // The count of existing rows |
| int numRows = oldValues.size(); |
| for (UntypedRow row: rows) { |
| boolean found = false; |
| // If we already have this row, mark it |
| for (NamedContentValues ncv: oldValues) { |
| ContentValues cv = ncv.values; |
| String data = cv.getAsString(COMMON_DATA_ROW); |
| int rowType = -1; |
| if (cv.containsKey(COMMON_TYPE_ROW)) { |
| rowType = cv.getAsInteger(COMMON_TYPE_ROW); |
| } |
| if (row.isSameAs(rowType, data)) { |
| cv.put(FOUND_DATA_ROW, true); |
| // Remove this to indicate it's still being used |
| entityValues.remove(ncv); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| // If we don't, there are two possibilities |
| if (numRows < maxRows) { |
| // If there are available rows, add a new one |
| RowBuilder builder = newRowBuilder(entity, mimeType); |
| row.addValues(builder); |
| add(builder.build()); |
| numRows++; |
| } else { |
| // Otherwise, say we need to replace a row with this |
| rowsToReplace.add(row); |
| } |
| } |
| } |
| |
| // Go through rows needing replacement |
| for (UntypedRow row: rowsToReplace) { |
| for (NamedContentValues ncv: oldValues) { |
| ContentValues cv = ncv.values; |
| // Find a row that hasn't been used (i.e. doesn't match current rows) |
| if (!cv.containsKey(FOUND_DATA_ROW)) { |
| // And update it |
| RowBuilder builder = new RowBuilder( |
| ContentProviderOperation |
| .newUpdate(addCallerIsSyncAdapterParameter( |
| dataUriFromNamedContentValues(ncv))), |
| ncv); |
| row.addValues(builder); |
| add(builder.build()); |
| } |
| } |
| } |
| } |
| |
| public void addOrganization(Entity entity, int type, String company, String title, |
| String department, String yomiCompanyName, String officeLocation) { |
| RowBuilder builder = typedRowBuilder(entity, Organization.CONTENT_ITEM_TYPE, type); |
| ContentValues cv = builder.cv; |
| if (cv != null && cvCompareString(cv, Organization.COMPANY, company) && |
| cvCompareString(cv, Organization.PHONETIC_NAME, yomiCompanyName) && |
| cvCompareString(cv, Organization.DEPARTMENT, department) && |
| cvCompareString(cv, Organization.TITLE, title) && |
| cvCompareString(cv, Organization.OFFICE_LOCATION, officeLocation)) { |
| return; |
| } |
| builder.withValue(Organization.TYPE, type); |
| builder.withValue(Organization.COMPANY, company); |
| builder.withValue(Organization.TITLE, title); |
| builder.withValue(Organization.DEPARTMENT, department); |
| builder.withValue(Organization.PHONETIC_NAME, yomiCompanyName); |
| builder.withValue(Organization.OFFICE_LOCATION, officeLocation); |
| add(builder.build()); |
| } |
| |
| public void addNote(Entity entity, String note) { |
| RowBuilder builder = typedRowBuilder(entity, Note.CONTENT_ITEM_TYPE, -1); |
| ContentValues cv = builder.cv; |
| if (note == null) return; |
| note = note.replaceAll("\r\n", "\n"); |
| if (cv != null && cvCompareString(cv, Note.NOTE, note)) { |
| return; |
| } |
| |
| // Reject notes with nothing in them. Often, we get something from Outlook when |
| // nothing was ever entered. Sigh. |
| int len = note.length(); |
| int i = 0; |
| for (; i < len; i++) { |
| char c = note.charAt(i); |
| if (!Character.isWhitespace(c)) { |
| break; |
| } |
| } |
| if (i == len) return; |
| |
| builder.withValue(Note.NOTE, note); |
| add(builder.build()); |
| } |
| } |
| |
| @Override |
| protected void wipe() { |
| LogUtils.w(TAG, "Wiping contacts for account %d", mAccount.mId); |
| EasContactsSyncHandler.wipeAccountFromContentProvider(mContext, |
| mAccount.mEmailAddress); |
| } |
| } |