diff options
author | Yorke Lee <yorkelee@google.com> | 2013-10-28 11:05:43 -0700 |
---|---|---|
committer | Yorke Lee <yorkelee@google.com> | 2013-11-05 14:06:06 -0800 |
commit | 5ade0bb1757b216ace2f50d2357409bf9876a07a (patch) | |
tree | 9e215d8283340d536a64cf3e4f421d063c0cff3f /tests | |
parent | 4315d80b53b324c7f74b994e8c579101ae3577c1 (diff) | |
download | android_packages_apps_ContactsCommon-5ade0bb1757b216ace2f50d2357409bf9876a07a.tar.gz android_packages_apps_ContactsCommon-5ade0bb1757b216ace2f50d2357409bf9876a07a.tar.bz2 android_packages_apps_ContactsCommon-5ade0bb1757b216ace2f50d2357409bf9876a07a.zip |
Move ContactLoader related code to ContactsCommon
This CL simply moves classes from Contacts into ContactsCommon.
This is needed so that Dialer can use ContactLoader related code
for b/11294679. A ContactLoader will also be needed in the future
to allow InCallUI to download hi-res photos while in call.
Bug: 11294679
Change-Id: If56a60aed2003ac7b8fcedac7ce4f1a7503bce94
Diffstat (limited to 'tests')
8 files changed, 2875 insertions, 0 deletions
diff --git a/tests/Android.mk b/tests/Android.mk index 8ecf594b..d18e2a66 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -5,6 +5,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := com.android.contacts.common.test # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/tests/src/com/android/contacts/common/ContactsUtilsTests.java b/tests/src/com/android/contacts/common/ContactsUtilsTests.java new file mode 100644 index 00000000..c0df3dd4 --- /dev/null +++ b/tests/src/com/android/contacts/common/ContactsUtilsTests.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009 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 src.com.android.contacts.common; + +import android.content.Intent; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.contacts.common.ContactsUtils; +import com.android.contacts.common.MoreContactUtils; + +/** + * Tests for {@link ContactsUtils}. + */ +@SmallTest +public class ContactsUtilsTests extends AndroidTestCase { + + public void testIsGraphicNull() throws Exception { + assertFalse(ContactsUtils.isGraphic(null)); + } + + public void testIsGraphicEmpty() throws Exception { + assertFalse(ContactsUtils.isGraphic("")); + } + + public void testIsGraphicSpaces() throws Exception { + assertFalse(ContactsUtils.isGraphic(" ")); + } + + public void testIsGraphicPunctuation() throws Exception { + assertTrue(ContactsUtils.isGraphic(".")); + } + + public void testAreObjectsEqual() throws Exception { + assertTrue("null:null", ContactsUtils.areObjectsEqual(null, null)); + assertTrue("1:1", ContactsUtils.areObjectsEqual(1, 1)); + + assertFalse("null:1", ContactsUtils.areObjectsEqual(null, 1)); + assertFalse("1:null", ContactsUtils.areObjectsEqual(1, null)); + assertFalse("1:2", ContactsUtils.areObjectsEqual(1, 2)); + } + + public void testAreIntentActionEqual() throws Exception { + assertTrue("1", ContactsUtils.areIntentActionEqual(null, null)); + assertTrue("1", ContactsUtils.areIntentActionEqual(new Intent("a"), new Intent("a"))); + + assertFalse("11", ContactsUtils.areIntentActionEqual(new Intent("a"), null)); + assertFalse("12", ContactsUtils.areIntentActionEqual(null, new Intent("a"))); + + assertFalse("21", ContactsUtils.areIntentActionEqual(new Intent("a"), new Intent())); + assertFalse("22", ContactsUtils.areIntentActionEqual(new Intent(), new Intent("b"))); + assertFalse("23", ContactsUtils.areIntentActionEqual(new Intent("a"), new Intent("b"))); + } +} diff --git a/tests/src/com/android/contacts/common/RawContactDeltaListTests.java b/tests/src/com/android/contacts/common/RawContactDeltaListTests.java new file mode 100644 index 00000000..7f05e694 --- /dev/null +++ b/tests/src/com/android/contacts/common/RawContactDeltaListTests.java @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2009 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.contacts.common; + +import static android.content.ContentProviderOperation.TYPE_ASSERT; +import static android.content.ContentProviderOperation.TYPE_DELETE; +import static android.content.ContentProviderOperation.TYPE_INSERT; +import static android.content.ContentProviderOperation.TYPE_UPDATE; + +import android.content.ContentProviderOperation; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.ContactsContract.AggregationExceptions; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.contacts.common.RawContactModifierTests.MockContactsSource; +import com.android.contacts.common.model.RawContact; +import com.android.contacts.common.model.RawContactDelta; +import com.android.contacts.common.model.ValuesDelta; +import com.android.contacts.common.model.RawContactDeltaList; +import com.android.contacts.common.model.RawContactModifier; +import com.android.contacts.common.model.account.AccountType; +import com.google.common.collect.Lists; + + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Tests for {@link RawContactDeltaList} which focus on "diff" operations that should + * create {@link AggregationExceptions} in certain cases. + */ +@LargeTest +public class RawContactDeltaListTests extends AndroidTestCase { + public static final String TAG = RawContactDeltaListTests.class.getSimpleName(); + + private static final long CONTACT_FIRST = 1; + private static final long CONTACT_SECOND = 2; + + public static final long CONTACT_BOB = 10; + public static final long CONTACT_MARY = 11; + + public static final long PHONE_RED = 20; + public static final long PHONE_GREEN = 21; + public static final long PHONE_BLUE = 22; + + public static final long EMAIL_YELLOW = 25; + + public static final long VER_FIRST = 100; + public static final long VER_SECOND = 200; + + public static final String TEST_PHONE = "555-1212"; + public static final String TEST_ACCOUNT = "org.example.test"; + + public RawContactDeltaListTests() { + super(); + } + + @Override + public void setUp() { + mContext = getContext(); + } + + /** + * Build a {@link AccountType} that has various odd constraints for + * testing purposes. + */ + protected AccountType getAccountType() { + return new MockContactsSource(); + } + + static ContentValues getValues(ContentProviderOperation operation) + throws NoSuchFieldException, IllegalAccessException { + final Field field = ContentProviderOperation.class.getDeclaredField("mValues"); + field.setAccessible(true); + return (ContentValues) field.get(operation); + } + + static RawContactDelta getUpdate(Context context, long rawContactId) { + final RawContact before = RawContactDeltaTests.getRawContact(context, rawContactId, + RawContactDeltaTests.TEST_PHONE_ID); + return RawContactDelta.fromBefore(before); + } + + static RawContactDelta getInsert() { + final ContentValues after = new ContentValues(); + after.put(RawContacts.ACCOUNT_NAME, RawContactDeltaTests.TEST_ACCOUNT_NAME); + after.put(RawContacts.SEND_TO_VOICEMAIL, 1); + + final ValuesDelta values = ValuesDelta.fromAfter(after); + return new RawContactDelta(values); + } + + static RawContactDeltaList buildSet(RawContactDelta... deltas) { + final RawContactDeltaList set = new RawContactDeltaList(); + Collections.addAll(set, deltas); + return set; + } + + static RawContactDelta buildBeforeEntity(Context context, long rawContactId, long version, + ContentValues... entries) { + // Build an existing contact read from database + final ContentValues contact = new ContentValues(); + contact.put(RawContacts.VERSION, version); + contact.put(RawContacts._ID, rawContactId); + final RawContact before = new RawContact(contact); + for (ContentValues entry : entries) { + before.addDataItemValues(entry); + } + return RawContactDelta.fromBefore(before); + } + + static RawContactDelta buildAfterEntity(ContentValues... entries) { + // Build an existing contact read from database + final ContentValues contact = new ContentValues(); + contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT); + final RawContactDelta after = new RawContactDelta(ValuesDelta.fromAfter(contact)); + for (ContentValues entry : entries) { + after.addEntry(ValuesDelta.fromAfter(entry)); + } + return after; + } + + static ContentValues buildPhone(long phoneId) { + return buildPhone(phoneId, Long.toString(phoneId)); + } + + static ContentValues buildPhone(long phoneId, String value) { + final ContentValues values = new ContentValues(); + values.put(Data._ID, phoneId); + values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + values.put(Phone.NUMBER, value); + values.put(Phone.TYPE, Phone.TYPE_HOME); + return values; + } + + static ContentValues buildEmail(long emailId) { + final ContentValues values = new ContentValues(); + values.put(Data._ID, emailId); + values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + values.put(Email.DATA, Long.toString(emailId)); + values.put(Email.TYPE, Email.TYPE_HOME); + return values; + } + + static void insertPhone(RawContactDeltaList set, long rawContactId, ContentValues values) { + final RawContactDelta match = set.getByRawContactId(rawContactId); + match.addEntry(ValuesDelta.fromAfter(values)); + } + + static ValuesDelta getPhone(RawContactDeltaList set, long rawContactId, long dataId) { + final RawContactDelta match = set.getByRawContactId(rawContactId); + return match.getEntry(dataId); + } + + static void assertDiffPattern(RawContactDelta delta, ContentProviderOperation... pattern) { + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + delta.buildAssert(diff); + delta.buildDiff(diff); + assertDiffPattern(diff, pattern); + } + + static void assertDiffPattern(RawContactDeltaList set, ContentProviderOperation... pattern) { + assertDiffPattern(set.buildDiff(), pattern); + } + + static void assertDiffPattern(ArrayList<ContentProviderOperation> diff, + ContentProviderOperation... pattern) { + assertEquals("Unexpected operations", pattern.length, diff.size()); + for (int i = 0; i < pattern.length; i++) { + final ContentProviderOperation expected = pattern[i]; + final ContentProviderOperation found = diff.get(i); + + assertEquals("Unexpected uri", expected.getUri(), found.getUri()); + + final String expectedType = getStringForType(expected.getType()); + final String foundType = getStringForType(found.getType()); + assertEquals("Unexpected type", expectedType, foundType); + + if (expected.getType() == TYPE_DELETE) continue; + + try { + final ContentValues expectedValues = getValues(expected); + final ContentValues foundValues = getValues(found); + + expectedValues.remove(BaseColumns._ID); + foundValues.remove(BaseColumns._ID); + + assertEquals("Unexpected values", expectedValues, foundValues); + } catch (NoSuchFieldException e) { + fail(e.toString()); + } catch (IllegalAccessException e) { + fail(e.toString()); + } + } + } + + static String getStringForType(int type) { + switch (type) { + case TYPE_ASSERT: return "TYPE_ASSERT"; + case TYPE_INSERT: return "TYPE_INSERT"; + case TYPE_UPDATE: return "TYPE_UPDATE"; + case TYPE_DELETE: return "TYPE_DELETE"; + default: return Integer.toString(type); + } + } + + static ContentProviderOperation buildAssertVersion(long version) { + final ContentValues values = new ContentValues(); + values.put(RawContacts.VERSION, version); + return buildOper(RawContacts.CONTENT_URI, TYPE_ASSERT, values); + } + + static ContentProviderOperation buildAggregationModeUpdate(int mode) { + final ContentValues values = new ContentValues(); + values.put(RawContacts.AGGREGATION_MODE, mode); + return buildOper(RawContacts.CONTENT_URI, TYPE_UPDATE, values); + } + + static ContentProviderOperation buildUpdateAggregationSuspended() { + return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED); + } + + static ContentProviderOperation buildUpdateAggregationDefault() { + return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT); + } + + static ContentProviderOperation buildUpdateAggregationKeepTogether(long rawContactId) { + final ContentValues values = new ContentValues(); + values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId); + values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); + return buildOper(AggregationExceptions.CONTENT_URI, TYPE_UPDATE, values); + } + + static ContentValues buildDataInsert(ValuesDelta values, long rawContactId) { + final ContentValues insertValues = values.getCompleteValues(); + insertValues.put(Data.RAW_CONTACT_ID, rawContactId); + return insertValues; + } + + static ContentProviderOperation buildDelete(Uri uri) { + return buildOper(uri, TYPE_DELETE, (ContentValues)null); + } + + static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) { + return buildOper(uri, type, values.getCompleteValues()); + } + + static ContentProviderOperation buildOper(Uri uri, int type, ContentValues values) { + switch (type) { + case TYPE_ASSERT: + return ContentProviderOperation.newAssertQuery(uri).withValues(values).build(); + case TYPE_INSERT: + return ContentProviderOperation.newInsert(uri).withValues(values).build(); + case TYPE_UPDATE: + return ContentProviderOperation.newUpdate(uri).withValues(values).build(); + case TYPE_DELETE: + return ContentProviderOperation.newDelete(uri).build(); + } + return null; + } + + static Long getVersion(RawContactDeltaList set, Long rawContactId) { + return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION); + } + + /** + * Count number of {@link AggregationExceptions} updates contained in the + * given list of {@link ContentProviderOperation}. + */ + static int countExceptionUpdates(ArrayList<ContentProviderOperation> diff) { + int updateCount = 0; + for (ContentProviderOperation oper : diff) { + if (AggregationExceptions.CONTENT_URI.equals(oper.getUri()) + && oper.getType() == ContentProviderOperation.TYPE_UPDATE) { + updateCount++; + } + } + return updateCount; + } + + public void testInsert() { + final RawContactDelta insert = getInsert(); + final RawContactDeltaList set = buildSet(insert); + + // Inserting single shouldn't create rules + final ArrayList<ContentProviderOperation> diff = set.buildDiff(); + final int exceptionCount = countExceptionUpdates(diff); + assertEquals("Unexpected exception updates", 0, exceptionCount); + } + + public void testUpdateUpdate() { + final RawContactDelta updateFirst = getUpdate(mContext, CONTACT_FIRST); + final RawContactDelta updateSecond = getUpdate(mContext, CONTACT_SECOND); + final RawContactDeltaList set = buildSet(updateFirst, updateSecond); + + // Updating two existing shouldn't create rules + final ArrayList<ContentProviderOperation> diff = set.buildDiff(); + final int exceptionCount = countExceptionUpdates(diff); + assertEquals("Unexpected exception updates", 0, exceptionCount); + } + + public void testUpdateInsert() { + final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST); + final RawContactDelta insert = getInsert(); + final RawContactDeltaList set = buildSet(update, insert); + + // New insert should only create one rule + final ArrayList<ContentProviderOperation> diff = set.buildDiff(); + final int exceptionCount = countExceptionUpdates(diff); + assertEquals("Unexpected exception updates", 1, exceptionCount); + } + + public void testInsertUpdateInsert() { + final RawContactDelta insertFirst = getInsert(); + final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST); + final RawContactDelta insertSecond = getInsert(); + final RawContactDeltaList set = buildSet(insertFirst, update, insertSecond); + + // Two inserts should create two rules to bind against single existing + final ArrayList<ContentProviderOperation> diff = set.buildDiff(); + final int exceptionCount = countExceptionUpdates(diff); + assertEquals("Unexpected exception updates", 2, exceptionCount); + } + + public void testInsertInsertInsert() { + final RawContactDelta insertFirst = getInsert(); + final RawContactDelta insertSecond = getInsert(); + final RawContactDelta insertThird = getInsert(); + final RawContactDeltaList set = buildSet(insertFirst, insertSecond, insertThird); + + // Three new inserts should create only two binding rules + final ArrayList<ContentProviderOperation> diff = set.buildDiff(); + final int exceptionCount = countExceptionUpdates(diff); + assertEquals("Unexpected exception updates", 2, exceptionCount); + } + + public void testMergeDataRemoteInsert() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); + + // Merge in second version, verify they match + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertEquals("Unexpected change when merging", second, merged); + } + + public void testMergeDataLocalUpdateRemoteInsert() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); + + // Change the local number to trigger update + final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED); + phone.put(Phone.NUMBER, TEST_PHONE); + + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), + buildUpdateAggregationDefault()); + + // Merge in the second version, verify diff matches + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged, + buildAssertVersion(VER_SECOND), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), + buildUpdateAggregationDefault()); + } + + public void testMergeDataLocalUpdateRemoteDelete() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_GREEN))); + + // Change the local number to trigger update + final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED); + phone.put(Phone.NUMBER, TEST_PHONE); + + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), + buildUpdateAggregationDefault()); + + // Merge in the second version, verify that our update changed to + // insert, since RED was deleted on remote side + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged, + buildAssertVersion(VER_SECOND), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(phone, CONTACT_BOB)), + buildUpdateAggregationDefault()); + } + + public void testMergeDataLocalDeleteRemoteUpdate() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_RED, TEST_PHONE))); + + // Delete phone locally + final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED); + phone.markDeleted(); + + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildUpdateAggregationSuspended(), + buildDelete(Data.CONTENT_URI), + buildUpdateAggregationDefault()); + + // Merge in the second version, verify that our delete remains + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged, + buildAssertVersion(VER_SECOND), + buildUpdateAggregationSuspended(), + buildDelete(Data.CONTENT_URI), + buildUpdateAggregationDefault()); + } + + public void testMergeDataLocalInsertRemoteInsert() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); + + // Insert new phone locally + final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE)); + first.getByRawContactId(CONTACT_BOB).addEntry(bluePhone); + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)), + buildUpdateAggregationDefault()); + + // Merge in the second version, verify that our insert remains + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged, + buildAssertVersion(VER_SECOND), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)), + buildUpdateAggregationDefault()); + } + + public void testMergeRawContactLocalInsertRemoteInsert() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_RED)), buildBeforeEntity(mContext, CONTACT_MARY, + VER_SECOND, buildPhone(PHONE_RED))); + + // Add new contact locally, should remain insert + final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE); + final RawContactDelta joeContact = buildAfterEntity(joePhoneInsert); + final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues(); + joeContactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); + first.add(joeContact); + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert), + buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert), + buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT), + buildUpdateAggregationKeepTogether(CONTACT_BOB)); + + // Merge in the second version, verify that our insert remains + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged, + buildAssertVersion(VER_SECOND), + buildAssertVersion(VER_SECOND), + buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert), + buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert), + buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT), + buildUpdateAggregationKeepTogether(CONTACT_BOB)); + } + + public void testMergeRawContactLocalDeleteRemoteDelete() { + final RawContactDeltaList first = buildSet( + buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)), + buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet( + buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED))); + + // Remove contact locally + first.getByRawContactId(CONTACT_MARY).markDeleted(); + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildAssertVersion(VER_FIRST), + buildDelete(RawContacts.CONTENT_URI)); + + // Merge in the second version, verify that our delete isn't needed + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged); + } + + public void testMergeRawContactLocalUpdateRemoteDelete() { + final RawContactDeltaList first = buildSet( + buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)), + buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet( + buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED))); + + // Perform local update + final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED); + phone.put(Phone.NUMBER, TEST_PHONE); + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildAssertVersion(VER_FIRST), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), + buildUpdateAggregationDefault()); + + final ContentValues phoneInsert = phone.getCompleteValues(); + final ContentValues contactInsert = first.getByRawContactId(CONTACT_MARY).getValues() + .getCompleteValues(); + contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); + + // Merge and verify that update turned into insert + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged, + buildAssertVersion(VER_SECOND), + buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert), + buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert), + buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT), + buildUpdateAggregationKeepTogether(CONTACT_BOB)); + } + + public void testMergeUsesNewVersion() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildPhone(PHONE_RED))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildPhone(PHONE_RED))); + + assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB)); + assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB)); + + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB)); + } + + public void testMergeAfterEnsureAndTrim() { + final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_FIRST, buildEmail(EMAIL_YELLOW))); + final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, + VER_SECOND, buildEmail(EMAIL_YELLOW))); + + // Ensure we have at least one phone + final AccountType source = getAccountType(); + final RawContactDelta bobContact = first.getByRawContactId(CONTACT_BOB); + RawContactModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE); + final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true); + + // Make sure the update would insert a row + assertDiffPattern(first, + buildAssertVersion(VER_FIRST), + buildUpdateAggregationSuspended(), + buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bobPhone, CONTACT_BOB)), + buildUpdateAggregationDefault()); + + // Trim values and ensure that we don't insert things + RawContactModifier.trimEmpty(bobContact, source); + assertDiffPattern(first); + + // Now re-parent the change, which should remain no-op + final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); + assertDiffPattern(merged); + } +} diff --git a/tests/src/com/android/contacts/common/RawContactDeltaTests.java b/tests/src/com/android/contacts/common/RawContactDeltaTests.java new file mode 100644 index 00000000..8fc30528 --- /dev/null +++ b/tests/src/com/android/contacts/common/RawContactDeltaTests.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2009 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.contacts.common; + +import static android.content.ContentProviderOperation.TYPE_ASSERT; +import static android.content.ContentProviderOperation.TYPE_DELETE; +import static android.content.ContentProviderOperation.TYPE_INSERT; +import static android.content.ContentProviderOperation.TYPE_UPDATE; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderOperation.Builder; +import android.content.ContentValues; +import android.content.Context; +import android.os.Parcel; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.contacts.common.model.RawContact; +import com.android.contacts.common.model.RawContactDelta; +import com.android.contacts.common.model.ValuesDelta; +import com.google.common.collect.Lists; + +import java.util.ArrayList; + +/** + * Tests for {@link RawContactDelta} and {@link ValuesDelta}. These tests + * focus on passing changes across {@link Parcel}, and verifying that they + * correctly build expected "diff" operations. + */ +@LargeTest +public class RawContactDeltaTests extends AndroidTestCase { + public static final String TAG = "EntityDeltaTests"; + + public static final long TEST_CONTACT_ID = 12; + public static final long TEST_PHONE_ID = 24; + + public static final String TEST_PHONE_NUMBER_1 = "218-555-1111"; + public static final String TEST_PHONE_NUMBER_2 = "218-555-2222"; + + public static final String TEST_ACCOUNT_NAME = "TEST"; + + public RawContactDeltaTests() { + super(); + } + + @Override + public void setUp() { + mContext = getContext(); + } + + public static RawContact getRawContact(Context context, long contactId, long phoneId) { + // Build an existing contact read from database + final ContentValues contact = new ContentValues(); + contact.put(RawContacts.VERSION, 43); + contact.put(RawContacts._ID, contactId); + + final ContentValues phone = new ContentValues(); + phone.put(Data._ID, phoneId); + phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); + phone.put(Phone.TYPE, Phone.TYPE_HOME); + + final RawContact before = new RawContact(contact); + before.addDataItemValues(phone); + return before; + } + + /** + * Test that {@link RawContactDelta#mergeAfter(RawContactDelta)} correctly passes + * any changes through the {@link Parcel} object. This enforces that + * {@link RawContactDelta} should be identical when serialized against the same + * "before" {@link RawContact}. + */ + public void testParcelChangesNone() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + final RawContactDelta dest = RawContactDelta.fromBefore(before); + + // Merge modified values and assert they match + final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); + assertEquals("Unexpected change when merging", source, merged); + } + + public void testParcelChangesInsert() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + final RawContactDelta dest = RawContactDelta.fromBefore(before); + + // Add a new row and pass across parcel, should be same + final ContentValues phone = new ContentValues(); + phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); + phone.put(Phone.TYPE, Phone.TYPE_WORK); + source.addEntry(ValuesDelta.fromAfter(phone)); + + // Merge modified values and assert they match + final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); + assertEquals("Unexpected change when merging", source, merged); + } + + public void testParcelChangesUpdate() { + // Update existing row and pass across parcel, should be same + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + final RawContactDelta dest = RawContactDelta.fromBefore(before); + + final ValuesDelta child = source.getEntry(TEST_PHONE_ID); + child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); + + // Merge modified values and assert they match + final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); + assertEquals("Unexpected change when merging", source, merged); + } + + public void testParcelChangesDelete() { + // Delete a row and pass across parcel, should be same + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + final RawContactDelta dest = RawContactDelta.fromBefore(before); + + final ValuesDelta child = source.getEntry(TEST_PHONE_ID); + child.markDeleted(); + + // Merge modified values and assert they match + final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source); + assertEquals("Unexpected change when merging", source, merged); + } + + public void testValuesDiffDelete() { + final ContentValues before = new ContentValues(); + before.put(Data._ID, TEST_PHONE_ID); + before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1); + + final ValuesDelta values = ValuesDelta.fromBefore(before); + values.markDeleted(); + + // Should produce a delete action + final Builder builder = values.buildDiff(Data.CONTENT_URI); + final int type = builder.build().getType(); + assertEquals("Didn't produce delete action", TYPE_DELETE, type); + } + + /** + * Test that {@link RawContactDelta#buildDiff(ArrayList)} is correctly built for + * insert, update, and delete cases. This only tests a subset of possible + * {@link Data} row changes. + */ + public void testEntityDiffNone() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + + // Assert that writing unchanged produces few operations + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildDiff(diff); + + assertTrue("Created changes when none needed", (diff.size() == 0)); + } + + public void testEntityDiffNoneInsert() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + + // Insert a new phone number + final ContentValues phone = new ContentValues(); + phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); + phone.put(Phone.TYPE, Phone.TYPE_WORK); + source.addEntry(ValuesDelta.fromAfter(phone)); + + // Assert two operations: insert Data row and enforce version + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildAssert(diff); + source.buildDiff(diff); + assertEquals("Unexpected operations", 4, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(3); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testEntityDiffUpdateInsert() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + + // Update parent contact values + source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED); + + // Insert a new phone number + final ContentValues phone = new ContentValues(); + phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); + phone.put(Phone.TYPE, Phone.TYPE_WORK); + source.addEntry(ValuesDelta.fromAfter(phone)); + + // Assert three operations: update Contact, insert Data row, enforce version + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildAssert(diff); + source.buildDiff(diff); + assertEquals("Unexpected operations", 5, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(3); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(4); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testEntityDiffNoneUpdate() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + + // Update existing phone number + final ValuesDelta child = source.getEntry(TEST_PHONE_ID); + child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); + + // Assert that version is enforced + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildAssert(diff); + source.buildDiff(diff); + assertEquals("Unexpected operations", 4, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(3); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testEntityDiffDelete() { + final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID); + final RawContactDelta source = RawContactDelta.fromBefore(before); + + // Delete entire entity + source.getValues().markDeleted(); + + // Assert two operations: delete Contact and enforce version + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildAssert(diff); + source.buildDiff(diff); + assertEquals("Unexpected operations", 2, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected version enforcement", TYPE_ASSERT, oper.getType()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testEntityDiffInsert() { + // Insert a RawContact + final ContentValues after = new ContentValues(); + after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); + after.put(RawContacts.SEND_TO_VOICEMAIL, 1); + + final ValuesDelta values = ValuesDelta.fromAfter(after); + final RawContactDelta source = new RawContactDelta(values); + + // Assert two operations: delete Contact and enforce version + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildAssert(diff); + source.buildDiff(diff); + assertEquals("Unexpected operations", 2, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testEntityDiffInsertInsert() { + // Insert a RawContact + final ContentValues after = new ContentValues(); + after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); + after.put(RawContacts.SEND_TO_VOICEMAIL, 1); + + final ValuesDelta values = ValuesDelta.fromAfter(after); + final RawContactDelta source = new RawContactDelta(values); + + // Insert a new phone number + final ContentValues phone = new ContentValues(); + phone.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2); + phone.put(Phone.TYPE, Phone.TYPE_WORK); + source.addEntry(ValuesDelta.fromAfter(phone)); + + // Assert two operations: delete Contact and enforce version + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + source.buildAssert(diff); + source.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + + } + } +} diff --git a/tests/src/com/android/contacts/common/RawContactModifierTests.java b/tests/src/com/android/contacts/common/RawContactModifierTests.java new file mode 100644 index 00000000..2e972cfb --- /dev/null +++ b/tests/src/com/android/contacts/common/RawContactModifierTests.java @@ -0,0 +1,1235 @@ +/* + * Copyright (C) 2009 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.contacts.common; + +import static android.content.ContentProviderOperation.TYPE_DELETE; +import static android.content.ContentProviderOperation.TYPE_INSERT; +import static android.content.ContentProviderOperation.TYPE_UPDATE; + +import android.content.ContentProviderOperation; +import android.content.ContentValues; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.Event; +import android.provider.ContactsContract.CommonDataKinds.Im; +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.Data; +import android.provider.ContactsContract.Intents.Insert; +import android.provider.ContactsContract.RawContacts; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.contacts.common.model.AccountTypeManager; +import com.android.contacts.common.model.RawContact; +import com.android.contacts.common.model.RawContactDelta; +import com.android.contacts.common.model.ValuesDelta; +import com.android.contacts.common.model.RawContactDeltaList; +import com.android.contacts.common.model.RawContactModifier; +import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.AccountType.EditType; +import com.android.contacts.common.model.account.ExchangeAccountType; +import com.android.contacts.common.model.account.GoogleAccountType; +import com.android.contacts.common.model.dataitem.DataKind; +import com.android.contacts.common.test.mocks.ContactsMockContext; +import com.android.contacts.common.test.mocks.MockAccountTypeManager; +import com.android.contacts.common.test.mocks.MockContentProvider; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link RawContactModifier} to verify that {@link AccountType} + * constraints are being enforced correctly. + */ +@LargeTest +public class RawContactModifierTests extends AndroidTestCase { + public static final String TAG = "EntityModifierTests"; + + public static final long VER_FIRST = 100; + + private static final long TEST_ID = 4; + private static final String TEST_PHONE = "218-555-1212"; + private static final String TEST_NAME = "Adam Young"; + private static final String TEST_NAME2 = "Breanne Duren"; + private static final String TEST_IM = "example@example.com"; + private static final String TEST_POSTAL = "1600 Amphitheatre Parkway"; + + private static final String TEST_ACCOUNT_NAME = "unittest@example.com"; + private static final String TEST_ACCOUNT_TYPE = "com.example.unittest"; + + private static final String EXCHANGE_ACCT_TYPE = "com.android.exchange"; + + @Override + public void setUp() { + mContext = getContext(); + } + + public static class MockContactsSource extends AccountType { + + MockContactsSource() { + try { + this.accountType = TEST_ACCOUNT_TYPE; + + final DataKind nameKind = new DataKind(StructuredName.CONTENT_ITEM_TYPE, + R.string.nameLabelsGroup, -1, true); + nameKind.typeOverallMax = 1; + addKind(nameKind); + + // Phone allows maximum 2 home, 1 work, and unlimited other, with + // constraint of 5 numbers maximum. + final DataKind phoneKind = new DataKind( + Phone.CONTENT_ITEM_TYPE, -1, 10, true); + + phoneKind.typeOverallMax = 5; + phoneKind.typeColumn = Phone.TYPE; + phoneKind.typeList = Lists.newArrayList(); + phoneKind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2)); + phoneKind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1)); + phoneKind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true)); + phoneKind.typeList.add(new EditType(Phone.TYPE_OTHER, -1)); + + phoneKind.fieldList = Lists.newArrayList(); + phoneKind.fieldList.add(new EditField(Phone.NUMBER, -1, -1)); + phoneKind.fieldList.add(new EditField(Phone.LABEL, -1, -1)); + + addKind(phoneKind); + + // Email is unlimited + final DataKind emailKind = new DataKind(Email.CONTENT_ITEM_TYPE, -1, 10, true); + emailKind.typeOverallMax = -1; + emailKind.fieldList = Lists.newArrayList(); + emailKind.fieldList.add(new EditField(Email.DATA, -1, -1)); + addKind(emailKind); + + // IM is only one + final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, 10, true); + imKind.typeOverallMax = 1; + imKind.fieldList = Lists.newArrayList(); + imKind.fieldList.add(new EditField(Im.DATA, -1, -1)); + addKind(imKind); + + // Organization is only one + final DataKind orgKind = new DataKind(Organization.CONTENT_ITEM_TYPE, -1, 10, true); + orgKind.typeOverallMax = 1; + orgKind.fieldList = Lists.newArrayList(); + orgKind.fieldList.add(new EditField(Organization.COMPANY, -1, -1)); + orgKind.fieldList.add(new EditField(Organization.TITLE, -1, -1)); + addKind(orgKind); + } catch (DefinitionException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isGroupMembershipEditable() { + return false; + } + + @Override + public boolean areContactsWritable() { + return true; + } + } + + /** + * Build a {@link AccountType} that has various odd constraints for + * testing purposes. + */ + protected AccountType getAccountType() { + return new MockContactsSource(); + } + + /** + * Build {@link AccountTypeManager} instance. + */ + protected AccountTypeManager getAccountTypes(AccountType... types) { + return new MockAccountTypeManager(types, null); + } + + /** + * Build an {@link RawContact} with the requested set of phone numbers. + */ + protected RawContactDelta getRawContact(Long existingId, ContentValues... entries) { + final ContentValues contact = new ContentValues(); + if (existingId != null) { + contact.put(RawContacts._ID, existingId); + } + contact.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); + contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE); + + final RawContact before = new RawContact(contact); + for (ContentValues values : entries) { + before.addDataItemValues(values); + } + return RawContactDelta.fromBefore(before); + } + + /** + * Assert this {@link List} contains the given {@link Object}. + */ + protected void assertContains(List<?> list, Object object) { + assertTrue("Missing expected value", list.contains(object)); + } + + /** + * Assert this {@link List} does not contain the given {@link Object}. + */ + protected void assertNotContains(List<?> list, Object object) { + assertFalse("Contained unexpected value", list.contains(object)); + } + + /** + * Insert various rows to test + * {@link RawContactModifier#getValidTypes(RawContactDelta, DataKind, EditType)} + */ + public void testValidTypes() { + // Build a source and pull specific types + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK); + final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER); + + List<EditType> validTypes; + + // Add first home, first work + final RawContactDelta state = getRawContact(TEST_ID); + RawContactModifier.insertChild(state, kindPhone, typeHome); + RawContactModifier.insertChild(state, kindPhone, typeWork); + + // Expecting home, other + validTypes = RawContactModifier.getValidTypes(state, kindPhone, null); + assertContains(validTypes, typeHome); + assertNotContains(validTypes, typeWork); + assertContains(validTypes, typeOther); + + // Add second home + RawContactModifier.insertChild(state, kindPhone, typeHome); + + // Expecting other + validTypes = RawContactModifier.getValidTypes(state, kindPhone, null); + assertNotContains(validTypes, typeHome); + assertNotContains(validTypes, typeWork); + assertContains(validTypes, typeOther); + + // Add third and fourth home (invalid, but possible) + RawContactModifier.insertChild(state, kindPhone, typeHome); + RawContactModifier.insertChild(state, kindPhone, typeHome); + + // Expecting none + validTypes = RawContactModifier.getValidTypes(state, kindPhone, null); + assertNotContains(validTypes, typeHome); + assertNotContains(validTypes, typeWork); + assertNotContains(validTypes, typeOther); + } + + /** + * Test {@link RawContactModifier#canInsert(RawContactDelta, DataKind)} by + * inserting various rows. + */ + public void testCanInsert() { + // Build a source and pull specific types + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK); + final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER); + + // Add first home, first work + final RawContactDelta state = getRawContact(TEST_ID); + RawContactModifier.insertChild(state, kindPhone, typeHome); + RawContactModifier.insertChild(state, kindPhone, typeWork); + assertTrue("Unable to insert", RawContactModifier.canInsert(state, kindPhone)); + + // Add two other, which puts us just under "5" overall limit + RawContactModifier.insertChild(state, kindPhone, typeOther); + RawContactModifier.insertChild(state, kindPhone, typeOther); + assertTrue("Unable to insert", RawContactModifier.canInsert(state, kindPhone)); + + // Add second home, which should push to snug limit + RawContactModifier.insertChild(state, kindPhone, typeHome); + assertFalse("Able to insert", RawContactModifier.canInsert(state, kindPhone)); + } + + /** + * Test + * {@link RawContactModifier#getBestValidType(RawContactDelta, DataKind, boolean, int)} + * by asserting expected best options in various states. + */ + public void testBestValidType() { + // Build a source and pull specific types + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK); + final EditType typeFaxWork = RawContactModifier.getType(kindPhone, Phone.TYPE_FAX_WORK); + final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER); + + EditType suggested; + + // Default suggestion should be home + final RawContactDelta state = getRawContact(TEST_ID); + suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); + assertEquals("Unexpected suggestion", typeHome, suggested); + + // Add first home, should now suggest work + RawContactModifier.insertChild(state, kindPhone, typeHome); + suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); + assertEquals("Unexpected suggestion", typeWork, suggested); + + // Add work fax, should still suggest work + RawContactModifier.insertChild(state, kindPhone, typeFaxWork); + suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); + assertEquals("Unexpected suggestion", typeWork, suggested); + + // Add other, should still suggest work + RawContactModifier.insertChild(state, kindPhone, typeOther); + suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); + assertEquals("Unexpected suggestion", typeWork, suggested); + + // Add work, now should suggest other + RawContactModifier.insertChild(state, kindPhone, typeWork); + suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); + assertEquals("Unexpected suggestion", typeOther, suggested); + } + + public void testIsEmptyEmpty() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + + // Test entirely empty row + final ContentValues after = new ContentValues(); + final ValuesDelta values = ValuesDelta.fromAfter(after); + + assertTrue("Expected empty", RawContactModifier.isEmpty(values, kindPhone)); + } + + public void testIsEmptyDirectFields() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Test row that has type values, but core fields are empty + final RawContactDelta state = getRawContact(TEST_ID); + final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome); + + assertTrue("Expected empty", RawContactModifier.isEmpty(values, kindPhone)); + + // Insert some data to trigger non-empty state + values.put(Phone.NUMBER, TEST_PHONE); + + assertFalse("Expected non-empty", RawContactModifier.isEmpty(values, kindPhone)); + } + + public void testTrimEmptySingle() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Test row that has type values, but core fields are empty + final RawContactDelta state = getRawContact(TEST_ID); + RawContactModifier.insertChild(state, kindPhone, typeHome); + + // Build diff, expecting insert for data row and update enforcement + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + + // Trim empty rows and try again, expecting delete of overall contact + RawContactModifier.trimEmpty(state, source); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 1, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testTrimEmptySpaces() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Test row that has type values, but values are spaces + final RawContactDelta state = RawContactDeltaListTests.buildBeforeEntity(mContext, TEST_ID, + VER_FIRST); + final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome); + values.put(Phone.NUMBER, " "); + + // Build diff, expecting insert for data row and update enforcement + RawContactDeltaListTests.assertDiffPattern(state, + RawContactDeltaListTests.buildAssertVersion(VER_FIRST), + RawContactDeltaListTests.buildUpdateAggregationSuspended(), + RawContactDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT, + RawContactDeltaListTests.buildDataInsert(values, TEST_ID)), + RawContactDeltaListTests.buildUpdateAggregationDefault()); + + // Trim empty rows and try again, expecting delete of overall contact + RawContactModifier.trimEmpty(state, source); + RawContactDeltaListTests.assertDiffPattern(state, + RawContactDeltaListTests.buildAssertVersion(VER_FIRST), + RawContactDeltaListTests.buildDelete(RawContacts.CONTENT_URI)); + } + + public void testTrimLeaveValid() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Test row that has type values with valid number + final RawContactDelta state = RawContactDeltaListTests.buildBeforeEntity(mContext, TEST_ID, + VER_FIRST); + final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome); + values.put(Phone.NUMBER, TEST_PHONE); + + // Build diff, expecting insert for data row and update enforcement + RawContactDeltaListTests.assertDiffPattern(state, + RawContactDeltaListTests.buildAssertVersion(VER_FIRST), + RawContactDeltaListTests.buildUpdateAggregationSuspended(), + RawContactDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT, + RawContactDeltaListTests.buildDataInsert(values, TEST_ID)), + RawContactDeltaListTests.buildUpdateAggregationDefault()); + + // Trim empty rows and try again, expecting no differences + RawContactModifier.trimEmpty(state, source); + RawContactDeltaListTests.assertDiffPattern(state, + RawContactDeltaListTests.buildAssertVersion(VER_FIRST), + RawContactDeltaListTests.buildUpdateAggregationSuspended(), + RawContactDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT, + RawContactDeltaListTests.buildDataInsert(values, TEST_ID)), + RawContactDeltaListTests.buildUpdateAggregationDefault()); + } + + public void testTrimEmptyUntouched() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Build "before" that has empty row + final RawContactDelta state = getRawContact(TEST_ID); + final ContentValues before = new ContentValues(); + before.put(Data._ID, TEST_ID); + before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + state.addEntry(ValuesDelta.fromBefore(before)); + + // Build diff, expecting no changes + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + + // Try trimming existing empty, which we shouldn't touch + RawContactModifier.trimEmpty(state, source); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + } + + public void testTrimEmptyAfterUpdate() { + final AccountType source = getAccountType(); + final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Build "before" that has row with some phone number + final ContentValues before = new ContentValues(); + before.put(Data._ID, TEST_ID); + before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + before.put(kindPhone.typeColumn, typeHome.rawValue); + before.put(Phone.NUMBER, TEST_PHONE); + final RawContactDelta state = getRawContact(TEST_ID, before); + + // Build diff, expecting no changes + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + + // Now update row by changing number to empty string, expecting single update + final ValuesDelta child = state.getEntry(TEST_ID); + child.put(Phone.NUMBER, ""); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + + // Now run trim, which should turn that update into delete + RawContactModifier.trimEmpty(state, source); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 1, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testTrimInsertEmpty() { + final AccountType accountType = getAccountType(); + final AccountTypeManager accountTypes = getAccountTypes(accountType); + final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Try creating a contact without any child entries + final RawContactDelta state = getRawContact(null); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); + + + // Build diff, expecting single insert + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 2, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + + // Trim empty rows and try again, expecting no insert + RawContactModifier.trimEmpty(set, accountTypes); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + } + + public void testTrimInsertInsert() { + final AccountType accountType = getAccountType(); + final AccountTypeManager accountTypes = getAccountTypes(accountType); + final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Try creating a contact with single empty entry + final RawContactDelta state = getRawContact(null); + RawContactModifier.insertChild(state, kindPhone, typeHome); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); + + // Build diff, expecting two insert operations + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + + // Trim empty rows and try again, expecting silence + RawContactModifier.trimEmpty(set, accountTypes); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + } + + public void testTrimUpdateRemain() { + final AccountType accountType = getAccountType(); + final AccountTypeManager accountTypes = getAccountTypes(accountType); + final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Build "before" with two phone numbers + final ContentValues first = new ContentValues(); + first.put(Data._ID, TEST_ID); + first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + first.put(kindPhone.typeColumn, typeHome.rawValue); + first.put(Phone.NUMBER, TEST_PHONE); + + final ContentValues second = new ContentValues(); + second.put(Data._ID, TEST_ID); + second.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + second.put(kindPhone.typeColumn, typeHome.rawValue); + second.put(Phone.NUMBER, TEST_PHONE); + + final RawContactDelta state = getRawContact(TEST_ID, first, second); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); + + // Build diff, expecting no changes + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + + // Now update row by changing number to empty string, expecting single update + final ValuesDelta child = state.getEntry(TEST_ID); + child.put(Phone.NUMBER, ""); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + + // Now run trim, which should turn that update into delete + RawContactModifier.trimEmpty(set, accountTypes); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testTrimUpdateUpdate() { + final AccountType accountType = getAccountType(); + final AccountTypeManager accountTypes = getAccountTypes(accountType); + final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME); + + // Build "before" with two phone numbers + final ContentValues first = new ContentValues(); + first.put(Data._ID, TEST_ID); + first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + first.put(kindPhone.typeColumn, typeHome.rawValue); + first.put(Phone.NUMBER, TEST_PHONE); + + final RawContactDelta state = getRawContact(TEST_ID, first); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); + + // Build diff, expecting no changes + final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 0, diff.size()); + + // Now update row by changing number to empty string, expecting single update + final ValuesDelta child = state.getEntry(TEST_ID); + child.put(Phone.NUMBER, ""); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 3, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(1); + assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); + } + { + final ContentProviderOperation oper = diff.get(2); + assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + + // Now run trim, which should turn into deleting the whole contact + RawContactModifier.trimEmpty(set, accountTypes); + diff.clear(); + state.buildDiff(diff); + assertEquals("Unexpected operations", 1, diff.size()); + { + final ContentProviderOperation oper = diff.get(0); + assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); + assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); + } + } + + public void testParseExtrasExistingName() { + final AccountType accountType = getAccountType(); + + // Build "before" name + final ContentValues first = new ContentValues(); + first.put(Data._ID, TEST_ID); + first.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + first.put(StructuredName.GIVEN_NAME, TEST_NAME); + + // Parse extras, making sure we keep single name + final RawContactDelta state = getRawContact(TEST_ID, first); + final Bundle extras = new Bundle(); + extras.putString(Insert.NAME, TEST_NAME2); + RawContactModifier.parseExtras(mContext, accountType, state, extras); + + final int nameCount = state.getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true); + assertEquals("Unexpected names", 1, nameCount); + } + + public void testParseExtrasIgnoreLimit() { + final AccountType accountType = getAccountType(); + + // Build "before" IM + final ContentValues first = new ContentValues(); + first.put(Data._ID, TEST_ID); + first.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); + first.put(Im.DATA, TEST_IM); + + final RawContactDelta state = getRawContact(TEST_ID, first); + final int beforeCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size(); + + // We should ignore data that doesn't fit account type rules, since account type + // only allows single Im + final Bundle extras = new Bundle(); + extras.putInt(Insert.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK); + extras.putString(Insert.IM_HANDLE, TEST_IM); + RawContactModifier.parseExtras(mContext, accountType, state, extras); + + final int afterCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size(); + assertEquals("Broke account type rules", beforeCount, afterCount); + } + + public void testParseExtrasIgnoreUnhandled() { + final AccountType accountType = getAccountType(); + final RawContactDelta state = getRawContact(TEST_ID); + + // We should silently ignore types unsupported by account type + final Bundle extras = new Bundle(); + extras.putString(Insert.POSTAL, TEST_POSTAL); + RawContactModifier.parseExtras(mContext, accountType, state, extras); + + assertNull("Broke accoun type rules", + state.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE)); + } + + public void testParseExtrasJobTitle() { + final AccountType accountType = getAccountType(); + final RawContactDelta state = getRawContact(TEST_ID); + + // Make sure that we create partial Organizations + final Bundle extras = new Bundle(); + extras.putString(Insert.JOB_TITLE, TEST_NAME); + RawContactModifier.parseExtras(mContext, accountType, state, extras); + + final int count = state.getMimeEntries(Organization.CONTENT_ITEM_TYPE).size(); + assertEquals("Expected to create organization", 1, count); + } + + public void testMigrateWithDisplayNameFromGoogleToExchange1() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE); + + ContactsMockContext context = new ContactsMockContext(getContext()); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + mockNameValues.put(StructuredName.PREFIX, "prefix"); + mockNameValues.put(StructuredName.GIVEN_NAME, "given"); + mockNameValues.put(StructuredName.MIDDLE_NAME, "middle"); + mockNameValues.put(StructuredName.FAMILY_NAME, "family"); + mockNameValues.put(StructuredName.SUFFIX, "suffix"); + mockNameValues.put(StructuredName.PHONETIC_FAMILY_NAME, "PHONETIC_FAMILY"); + mockNameValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "PHONETIC_MIDDLE"); + mockNameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "PHONETIC_GIVEN"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateStructuredName(context, oldState, newState, kind); + List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE); + assertEquals(1, list.size()); + + ContentValues output = list.get(0).getAfter(); + assertEquals("prefix", output.getAsString(StructuredName.PREFIX)); + assertEquals("given", output.getAsString(StructuredName.GIVEN_NAME)); + assertEquals("middle", output.getAsString(StructuredName.MIDDLE_NAME)); + assertEquals("family", output.getAsString(StructuredName.FAMILY_NAME)); + assertEquals("suffix", output.getAsString(StructuredName.SUFFIX)); + // Phonetic middle name isn't supported by Exchange. + assertEquals("PHONETIC_FAMILY", output.getAsString(StructuredName.PHONETIC_FAMILY_NAME)); + assertEquals("PHONETIC_GIVEN", output.getAsString(StructuredName.PHONETIC_GIVEN_NAME)); + } + + public void testMigrateWithDisplayNameFromGoogleToExchange2() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE); + + ContactsMockContext context = new ContactsMockContext(getContext()); + MockContentProvider provider = context.getContactsProvider(); + + String inputDisplayName = "prefix given middle family suffix"; + // The method will ask the provider to split/join StructuredName. + Uri uriForBuildDisplayName = + ContactsContract.AUTHORITY_URI + .buildUpon() + .appendPath("complete_name") + .appendQueryParameter(StructuredName.DISPLAY_NAME, inputDisplayName) + .build(); + provider.expectQuery(uriForBuildDisplayName) + .returnRow("prefix", "given", "middle", "family", "suffix") + .withProjection(StructuredName.PREFIX, StructuredName.GIVEN_NAME, + StructuredName.MIDDLE_NAME, StructuredName.FAMILY_NAME, + StructuredName.SUFFIX); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + mockNameValues.put(StructuredName.DISPLAY_NAME, inputDisplayName); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateStructuredName(context, oldState, newState, kind); + List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE); + assertEquals(1, list.size()); + + ContentValues outputValues = list.get(0).getAfter(); + assertEquals("prefix", outputValues.getAsString(StructuredName.PREFIX)); + assertEquals("given", outputValues.getAsString(StructuredName.GIVEN_NAME)); + assertEquals("middle", outputValues.getAsString(StructuredName.MIDDLE_NAME)); + assertEquals("family", outputValues.getAsString(StructuredName.FAMILY_NAME)); + assertEquals("suffix", outputValues.getAsString(StructuredName.SUFFIX)); + } + + public void testMigrateWithStructuredNameFromExchangeToGoogle() { + AccountType oldAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + AccountType newAccountType = new GoogleAccountType(getContext(), ""); + DataKind kind = newAccountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE); + + ContactsMockContext context = new ContactsMockContext(getContext()); + MockContentProvider provider = context.getContactsProvider(); + + // The method will ask the provider to split/join StructuredName. + Uri uriForBuildDisplayName = + ContactsContract.AUTHORITY_URI + .buildUpon() + .appendPath("complete_name") + .appendQueryParameter(StructuredName.PREFIX, "prefix") + .appendQueryParameter(StructuredName.GIVEN_NAME, "given") + .appendQueryParameter(StructuredName.MIDDLE_NAME, "middle") + .appendQueryParameter(StructuredName.FAMILY_NAME, "family") + .appendQueryParameter(StructuredName.SUFFIX, "suffix") + .build(); + provider.expectQuery(uriForBuildDisplayName) + .returnRow("prefix given middle family suffix") + .withProjection(StructuredName.DISPLAY_NAME); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + mockNameValues.put(StructuredName.PREFIX, "prefix"); + mockNameValues.put(StructuredName.GIVEN_NAME, "given"); + mockNameValues.put(StructuredName.MIDDLE_NAME, "middle"); + mockNameValues.put(StructuredName.FAMILY_NAME, "family"); + mockNameValues.put(StructuredName.SUFFIX, "suffix"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateStructuredName(context, oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(1, list.size()); + ContentValues outputValues = list.get(0).getAfter(); + assertEquals("prefix given middle family suffix", + outputValues.getAsString(StructuredName.DISPLAY_NAME)); + } + + public void testMigratePostalFromGoogleToExchange() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); + mockNameValues.put(StructuredPostal.FORMATTED_ADDRESS, "formatted_address"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migratePostal(oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(1, list.size()); + ContentValues outputValues = list.get(0).getAfter(); + // FORMATTED_ADDRESS isn't supported by Exchange. + assertNull(outputValues.getAsString(StructuredPostal.FORMATTED_ADDRESS)); + assertEquals("formatted_address", outputValues.getAsString(StructuredPostal.STREET)); + } + + public void testMigratePostalFromExchangeToGoogle() { + AccountType oldAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + AccountType newAccountType = new GoogleAccountType(getContext(), ""); + DataKind kind = newAccountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); + mockNameValues.put(StructuredPostal.COUNTRY, "country"); + mockNameValues.put(StructuredPostal.POSTCODE, "postcode"); + mockNameValues.put(StructuredPostal.REGION, "region"); + mockNameValues.put(StructuredPostal.CITY, "city"); + mockNameValues.put(StructuredPostal.STREET, "street"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migratePostal(oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(1, list.size()); + ContentValues outputValues = list.get(0).getAfter(); + + // Check FORMATTED_ADDRESS contains all info. + String formattedAddress = outputValues.getAsString(StructuredPostal.FORMATTED_ADDRESS); + assertNotNull(formattedAddress); + assertTrue(formattedAddress.contains("country")); + assertTrue(formattedAddress.contains("postcode")); + assertTrue(formattedAddress.contains("region")); + assertTrue(formattedAddress.contains("postcode")); + assertTrue(formattedAddress.contains("city")); + assertTrue(formattedAddress.contains("street")); + } + + public void testMigrateEventFromGoogleToExchange1() { + testMigrateEventCommon(new GoogleAccountType(getContext(), ""), + new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE)); + } + + public void testMigrateEventFromExchangeToGoogle() { + testMigrateEventCommon(new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE), + new GoogleAccountType(getContext(), "")); + } + + private void testMigrateEventCommon(AccountType oldAccountType, AccountType newAccountType) { + DataKind kind = newAccountType.getKindForMimetype(Event.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); + mockNameValues.put(Event.START_DATE, "1972-02-08"); + mockNameValues.put(Event.TYPE, Event.TYPE_BIRTHDAY); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateEvent(oldState, newState, kind, 1990); + + List<ValuesDelta> list = newState.getMimeEntries(Event.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(1, list.size()); // Anniversary should be dropped. + ContentValues outputValues = list.get(0).getAfter(); + + assertEquals("1972-02-08", outputValues.getAsString(Event.START_DATE)); + assertEquals(Event.TYPE_BIRTHDAY, outputValues.getAsInteger(Event.TYPE).intValue()); + } + + public void testMigrateEventFromGoogleToExchange2() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(Event.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); + // No year format is not supported by Exchange. + mockNameValues.put(Event.START_DATE, "--06-01"); + mockNameValues.put(Event.TYPE, Event.TYPE_BIRTHDAY); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); + mockNameValues.put(Event.START_DATE, "1980-08-02"); + // Anniversary is not supported by Exchange + mockNameValues.put(Event.TYPE, Event.TYPE_ANNIVERSARY); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateEvent(oldState, newState, kind, 1990); + + List<ValuesDelta> list = newState.getMimeEntries(Event.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(1, list.size()); // Anniversary should be dropped. + ContentValues outputValues = list.get(0).getAfter(); + + // Default year should be used. + assertEquals("1990-06-01", outputValues.getAsString(Event.START_DATE)); + assertEquals(Event.TYPE_BIRTHDAY, outputValues.getAsInteger(Event.TYPE).intValue()); + } + + public void testMigrateEmailFromGoogleToExchange() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(Email.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + mockNameValues.put(Email.TYPE, Email.TYPE_CUSTOM); + mockNameValues.put(Email.LABEL, "custom_type"); + mockNameValues.put(Email.ADDRESS, "address1"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + mockNameValues.put(Email.TYPE, Email.TYPE_HOME); + mockNameValues.put(Email.ADDRESS, "address2"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + mockNameValues.put(Email.TYPE, Email.TYPE_WORK); + mockNameValues.put(Email.ADDRESS, "address3"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + // Exchange can have up to 3 email entries. This 4th entry should be dropped. + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + mockNameValues.put(Email.TYPE, Email.TYPE_OTHER); + mockNameValues.put(Email.ADDRESS, "address4"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(Email.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(3, list.size()); + + ContentValues outputValues = list.get(0).getAfter(); + assertEquals(Email.TYPE_CUSTOM, outputValues.getAsInteger(Email.TYPE).intValue()); + assertEquals("custom_type", outputValues.getAsString(Email.LABEL)); + assertEquals("address1", outputValues.getAsString(Email.ADDRESS)); + + outputValues = list.get(1).getAfter(); + assertEquals(Email.TYPE_HOME, outputValues.getAsInteger(Email.TYPE).intValue()); + assertEquals("address2", outputValues.getAsString(Email.ADDRESS)); + + outputValues = list.get(2).getAfter(); + assertEquals(Email.TYPE_WORK, outputValues.getAsInteger(Email.TYPE).intValue()); + assertEquals("address3", outputValues.getAsString(Email.ADDRESS)); + } + + public void testMigrateImFromGoogleToExchange() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(Im.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); + // Exchange doesn't support TYPE_HOME + mockNameValues.put(Im.TYPE, Im.TYPE_HOME); + mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_JABBER); + mockNameValues.put(Im.DATA, "im1"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); + // Exchange doesn't support TYPE_WORK + mockNameValues.put(Im.TYPE, Im.TYPE_WORK); + mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_YAHOO); + mockNameValues.put(Im.DATA, "im2"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); + mockNameValues.put(Im.TYPE, Im.TYPE_OTHER); + mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM); + mockNameValues.put(Im.CUSTOM_PROTOCOL, "custom_protocol"); + mockNameValues.put(Im.DATA, "im3"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + // Exchange can have up to 3 IM entries. This 4th entry should be dropped. + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); + mockNameValues.put(Im.TYPE, Im.TYPE_OTHER); + mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK); + mockNameValues.put(Im.DATA, "im4"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(Im.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(3, list.size()); + + assertNotNull(kind.defaultValues.getAsInteger(Im.TYPE)); + + int defaultType = kind.defaultValues.getAsInteger(Im.TYPE); + + ContentValues outputValues = list.get(0).getAfter(); + // HOME should become default type. + assertEquals(defaultType, outputValues.getAsInteger(Im.TYPE).intValue()); + assertEquals(Im.PROTOCOL_JABBER, outputValues.getAsInteger(Im.PROTOCOL).intValue()); + assertEquals("im1", outputValues.getAsString(Im.DATA)); + + outputValues = list.get(1).getAfter(); + assertEquals(defaultType, outputValues.getAsInteger(Im.TYPE).intValue()); + assertEquals(Im.PROTOCOL_YAHOO, outputValues.getAsInteger(Im.PROTOCOL).intValue()); + assertEquals("im2", outputValues.getAsString(Im.DATA)); + + outputValues = list.get(2).getAfter(); + assertEquals(defaultType, outputValues.getAsInteger(Im.TYPE).intValue()); + assertEquals(Im.PROTOCOL_CUSTOM, outputValues.getAsInteger(Im.PROTOCOL).intValue()); + assertEquals("custom_protocol", outputValues.getAsString(Im.CUSTOM_PROTOCOL)); + assertEquals("im3", outputValues.getAsString(Im.DATA)); + } + + public void testMigratePhoneFromGoogleToExchange() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); + + // Create 5 numbers. + // - "1" -- HOME + // - "2" -- WORK + // - "3" -- CUSTOM + // - "4" -- WORK + // - "5" -- WORK_MOBILE + // Then we convert it to Exchange account type. + // - "1" -- HOME + // - "2" -- WORK + // - "3" -- Because CUSTOM is not supported, it'll be changed to the default, MOBILE + // - "4" -- WORK + // - "5" -- WORK_MOBILE not suppoted again, so will be MOBILE. + // But then, Exchange doesn't support multiple MOBILE numbers, so "5" will be removed. + // i.e. the result will be: + // - "1" -- HOME + // - "2" -- WORK + // - "3" -- MOBILE + // - "4" -- WORK + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + mockNameValues.put(Phone.TYPE, Phone.TYPE_HOME); + mockNameValues.put(Phone.NUMBER, "1"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + mockNameValues.put(Phone.TYPE, Phone.TYPE_WORK); + mockNameValues.put(Phone.NUMBER, "2"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + // Exchange doesn't support this type. Default to MOBILE + mockNameValues.put(Phone.TYPE, Phone.TYPE_CUSTOM); + mockNameValues.put(Phone.LABEL, "custom_type"); + mockNameValues.put(Phone.NUMBER, "3"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + mockNameValues.put(Phone.TYPE, Phone.TYPE_WORK); + mockNameValues.put(Phone.NUMBER, "4"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + mockNameValues = new ContentValues(); + + mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + mockNameValues.put(Phone.TYPE, Phone.TYPE_WORK_MOBILE); + mockNameValues.put(Phone.NUMBER, "5"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(Phone.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(4, list.size()); + + int defaultType = Phone.TYPE_MOBILE; + + ContentValues outputValues = list.get(0).getAfter(); + assertEquals(Phone.TYPE_HOME, outputValues.getAsInteger(Phone.TYPE).intValue()); + assertEquals("1", outputValues.getAsString(Phone.NUMBER)); + outputValues = list.get(1).getAfter(); + assertEquals(Phone.TYPE_WORK, outputValues.getAsInteger(Phone.TYPE).intValue()); + assertEquals("2", outputValues.getAsString(Phone.NUMBER)); + outputValues = list.get(2).getAfter(); + assertEquals(defaultType, outputValues.getAsInteger(Phone.TYPE).intValue()); + assertNull(outputValues.getAsInteger(Phone.LABEL)); + assertEquals("3", outputValues.getAsString(Phone.NUMBER)); + outputValues = list.get(3).getAfter(); + assertEquals(Phone.TYPE_WORK, outputValues.getAsInteger(Phone.TYPE).intValue()); + assertEquals("4", outputValues.getAsString(Phone.NUMBER)); + } + + public void testMigrateOrganizationFromGoogleToExchange() { + AccountType oldAccountType = new GoogleAccountType(getContext(), ""); + AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE); + DataKind kind = newAccountType.getKindForMimetype(Organization.CONTENT_ITEM_TYPE); + + RawContactDelta oldState = new RawContactDelta(); + ContentValues mockNameValues = new ContentValues(); + mockNameValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); + mockNameValues.put(Organization.COMPANY, "company1"); + mockNameValues.put(Organization.DEPARTMENT, "department1"); + oldState.addEntry(ValuesDelta.fromAfter(mockNameValues)); + + RawContactDelta newState = new RawContactDelta(); + RawContactModifier.migrateGenericWithoutTypeColumn(oldState, newState, kind); + + List<ValuesDelta> list = newState.getMimeEntries(Organization.CONTENT_ITEM_TYPE); + assertNotNull(list); + assertEquals(1, list.size()); + + ContentValues outputValues = list.get(0).getAfter(); + assertEquals("company1", outputValues.getAsString(Organization.COMPANY)); + assertEquals("department1", outputValues.getAsString(Organization.DEPARTMENT)); + } +} diff --git a/tests/src/com/android/contacts/common/model/ContactLoaderTest.java b/tests/src/com/android/contacts/common/model/ContactLoaderTest.java new file mode 100644 index 00000000..94c64ebb --- /dev/null +++ b/tests/src/com/android/contacts/common/model/ContactLoaderTest.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2010 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.contacts.common.model; + +import android.content.ContentUris; +import android.net.Uri; +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.DisplayNameSources; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.StatusUpdates; +import android.test.LoaderTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.contacts.common.model.AccountTypeManager; +import com.android.contacts.common.test.mocks.ContactsMockContext; +import com.android.contacts.common.test.mocks.MockContentProvider; +import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.account.BaseAccountType; +import com.android.contacts.common.test.InjectedServices; +import com.android.contacts.common.test.mocks.MockAccountTypeManager; + +/** + * Runs ContactLoader tests for the the contact-detail and editor view. + */ +@LargeTest +public class ContactLoaderTest extends LoaderTestCase { + private ContactsMockContext mMockContext; + private MockContentProvider mContactsProvider; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mMockContext = new ContactsMockContext(getContext()); + mContactsProvider = mMockContext.getContactsProvider(); + + InjectedServices services = new InjectedServices(); + AccountType accountType = new BaseAccountType() { + @Override + public boolean areContactsWritable() { + return false; + } + }; + accountType.accountType = "mockAccountType"; + + AccountWithDataSet account = + new AccountWithDataSet("mockAccountName", "mockAccountType", null); + + AccountTypeManager.setInstanceForTest( + new MockAccountTypeManager( + new AccountType[]{accountType}, new AccountWithDataSet[]{account})); + } + + @Override + protected void tearDown() throws Exception { + mMockContext = null; + mContactsProvider = null; + super.tearDown(); + } + + private Contact assertLoadContact(Uri uri) { + final ContactLoader loader = new ContactLoader(mMockContext, uri, true); + return getLoaderResultSynchronously(loader); + } + + public void testNullUri() { + Contact result = assertLoadContact(null); + assertTrue(result.isError()); + } + + public void testEmptyUri() { + Contact result = assertLoadContact(Uri.EMPTY); + assertTrue(result.isError()); + } + + public void testInvalidUri() { + Contact result = assertLoadContact(Uri.parse("content://wtf")); + assertTrue(result.isError()); + } + + public void testLoadContactWithContactIdUri() { + // Use content Uris that only contain the ID + final long contactId = 1; + final long rawContactId = 11; + final long dataId = 21; + + final String lookupKey = "aa%12%@!"; + final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + final Uri entityUri = Uri.withAppendedPath(baseUri, Contacts.Entity.CONTENT_DIRECTORY); + final Uri lookupUri = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), + contactId); + + ContactQueries queries = new ContactQueries(); + mContactsProvider.expectTypeQuery(baseUri, Contacts.CONTENT_ITEM_TYPE); + queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey); + + Contact contact = assertLoadContact(baseUri); + + assertEquals(contactId, contact.getId()); + assertEquals(rawContactId, contact.getNameRawContactId()); + assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); + assertEquals(lookupKey, contact.getLookupKey()); + assertEquals(lookupUri, contact.getLookupUri()); + assertEquals(1, contact.getRawContacts().size()); + assertEquals(1, contact.getStatuses().size()); + mContactsProvider.verify(); + } + + public void testLoadContactWithOldStyleUri() { + // Use content Uris that only contain the ID but use the format used in Donut + final long contactId = 1; + final long rawContactId = 11; + final long dataId = 21; + + final String lookupKey = "aa%12%@!"; + final Uri legacyUri = ContentUris.withAppendedId( + Uri.parse("content://contacts"), rawContactId); + final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); + final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + final Uri lookupUri = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), + contactId); + final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY); + + ContactQueries queries = new ContactQueries(); + queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, lookupKey); + queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey); + + Contact contact = assertLoadContact(legacyUri); + + assertEquals(contactId, contact.getId()); + assertEquals(rawContactId, contact.getNameRawContactId()); + assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); + assertEquals(lookupKey, contact.getLookupKey()); + assertEquals(lookupUri, contact.getLookupUri()); + assertEquals(1, contact.getRawContacts().size()); + assertEquals(1, contact.getStatuses().size()); + mContactsProvider.verify(); + } + + public void testLoadContactWithRawContactIdUri() { + // Use content Uris that only contain the ID but use the format used in Donut + final long contactId = 1; + final long rawContactId = 11; + final long dataId = 21; + + final String lookupKey = "aa%12%@!"; + final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); + final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + final Uri lookupUri = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), + contactId); + final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY); + + ContactQueries queries = new ContactQueries(); + mContactsProvider.expectTypeQuery(rawContactUri, RawContacts.CONTENT_ITEM_TYPE); + queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, lookupKey); + queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey); + + Contact contact = assertLoadContact(rawContactUri); + + assertEquals(contactId, contact.getId()); + assertEquals(rawContactId, contact.getNameRawContactId()); + assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); + assertEquals(lookupKey, contact.getLookupKey()); + assertEquals(lookupUri, contact.getLookupUri()); + assertEquals(1, contact.getRawContacts().size()); + assertEquals(1, contact.getStatuses().size()); + mContactsProvider.verify(); + } + + public void testLoadContactWithContactLookupUri() { + // Use lookup-style Uris that do not contain the Contact-ID + + final long contactId = 1; + final long rawContactId = 11; + final long dataId = 21; + + final String lookupKey = "aa%12%@!"; + final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + final Uri lookupNoIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); + final Uri lookupUri = ContentUris.withAppendedId(lookupNoIdUri, contactId); + final Uri entityUri = Uri.withAppendedPath(lookupNoIdUri, Contacts.Entity.CONTENT_DIRECTORY); + + ContactQueries queries = new ContactQueries(); + mContactsProvider.expectTypeQuery(lookupNoIdUri, Contacts.CONTENT_ITEM_TYPE); + queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey); + + Contact contact = assertLoadContact(lookupNoIdUri); + + assertEquals(contactId, contact.getId()); + assertEquals(rawContactId, contact.getNameRawContactId()); + assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); + assertEquals(lookupKey, contact.getLookupKey()); + assertEquals(lookupUri, contact.getLookupUri()); + assertEquals(1, contact.getRawContacts().size()); + assertEquals(1, contact.getStatuses().size()); + mContactsProvider.verify(); + } + + public void testLoadContactWithContactLookupAndIdUri() { + // Use lookup-style Uris that also contain the Contact-ID + final long contactId = 1; + final long rawContactId = 11; + final long dataId = 21; + + final String lookupKey = "aa%12%@!"; + final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + final Uri lookupUri = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), + contactId); + final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY); + + ContactQueries queries = new ContactQueries(); + mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE); + queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey); + + Contact contact = assertLoadContact(lookupUri); + + assertEquals(contactId, contact.getId()); + assertEquals(rawContactId, contact.getNameRawContactId()); + assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); + assertEquals(lookupKey, contact.getLookupKey()); + assertEquals(lookupUri, contact.getLookupUri()); + assertEquals(1, contact.getRawContacts().size()); + assertEquals(1, contact.getStatuses().size()); + mContactsProvider.verify(); + } + + public void testLoadContactWithContactLookupWithIncorrectIdUri() { + // Use lookup-style Uris that contain incorrect Contact-ID + // (we want to ensure that still the correct contact is chosen) + + final long contactId = 1; + final long wrongContactId = 2; + final long rawContactId = 11; + final long wrongRawContactId = 12; + final long dataId = 21; + + final String lookupKey = "aa%12%@!"; + final String wrongLookupKey = "ab%12%@!"; + final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId); + final Uri lookupUri = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), + contactId); + final Uri lookupWithWrongIdUri = ContentUris.withAppendedId( + Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), + wrongContactId); + final Uri entityUri = Uri.withAppendedPath(lookupWithWrongIdUri, + Contacts.Entity.CONTENT_DIRECTORY); + + ContactQueries queries = new ContactQueries(); + mContactsProvider.expectTypeQuery(lookupWithWrongIdUri, Contacts.CONTENT_ITEM_TYPE); + queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey); + + Contact contact = assertLoadContact(lookupWithWrongIdUri); + + assertEquals(contactId, contact.getId()); + assertEquals(rawContactId, contact.getNameRawContactId()); + assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); + assertEquals(lookupKey, contact.getLookupKey()); + assertEquals(lookupUri, contact.getLookupUri()); + assertEquals(1, contact.getRawContacts().size()); + assertEquals(1, contact.getStatuses().size()); + + mContactsProvider.verify(); + } + + class ContactQueries { + public void fetchAllData( + Uri baseUri, long contactId, long rawContactId, long dataId, String encodedLookup) { + mContactsProvider.expectQuery(baseUri) + .withProjection(new String[] { + Contacts.NAME_RAW_CONTACT_ID, Contacts.DISPLAY_NAME_SOURCE, + Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME, + Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.PHONETIC_NAME, + Contacts.PHOTO_ID, Contacts.STARRED, Contacts.CONTACT_PRESENCE, + Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, + Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, + + Contacts.Entity.CONTACT_ID, + Contacts.Entity.RAW_CONTACT_ID, + + RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, + RawContacts.DATA_SET, RawContacts.ACCOUNT_TYPE_AND_DATA_SET, + RawContacts.DIRTY, RawContacts.VERSION, RawContacts.SOURCE_ID, + RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4, + RawContacts.DELETED, RawContacts.NAME_VERIFIED, + + Contacts.Entity.DATA_ID, + + Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, + Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, + Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, + Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4, + Data.DATA_VERSION, Data.IS_PRIMARY, + Data.IS_SUPER_PRIMARY, Data.MIMETYPE, Data.RES_PACKAGE, + + GroupMembership.GROUP_SOURCE_ID, + + Data.PRESENCE, Data.CHAT_CAPABILITY, + Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON, + Data.STATUS_LABEL, Data.STATUS_TIMESTAMP, + + Contacts.PHOTO_URI, + + Contacts.SEND_TO_VOICEMAIL, + Contacts.CUSTOM_RINGTONE, + Contacts.IS_USER_PROFILE, + }) + .withSortOrder(Contacts.Entity.RAW_CONTACT_ID) + .returnRow( + rawContactId, 40, + "aa%12%@!", "John Doe", "Doe, John", "jdo", + 0, 0, StatusUpdates.AVAILABLE, + "Having lunch", 0, + "mockPkg1", 10, + + contactId, + rawContactId, + + "mockAccountName", "mockAccountType", null, "mockAccountType", + 0, 1, 0, + "sync1", "sync2", "sync3", "sync4", + 0, 0, + + dataId, + + "dat1", "dat2", "dat3", "dat4", "dat5", + "dat6", "dat7", "dat8", "dat9", "dat10", + "dat11", "dat12", "dat13", "dat14", "dat15", + "syn1", "syn2", "syn3", "syn4", + + 0, 0, + 0, StructuredName.CONTENT_ITEM_TYPE, "mockPkg2", + + "groupId", + + StatusUpdates.INVISIBLE, null, + "Having dinner", "mockPkg3", 0, + 20, 0, + + "content:some.photo.uri", + + 0, + null, + 0 + ); + } + + void fetchLookupAndId(final Uri sourceUri, final long expectedContactId, + final String expectedEncodedLookup) { + mContactsProvider.expectQuery(sourceUri) + .withProjection(Contacts.LOOKUP_KEY, Contacts._ID) + .returnRow(expectedEncodedLookup, expectedContactId); + } + + void fetchContactIdAndLookupFromRawContactUri(final Uri rawContactUri, + final long expectedContactId, final String expectedEncodedLookup) { + // TODO: use a lighter query by joining rawcontacts with contacts in provider + // (See ContactContracts.java) + final Uri dataUri = Uri.withAppendedPath(rawContactUri, + RawContacts.Data.CONTENT_DIRECTORY); + mContactsProvider.expectQuery(dataUri) + .withProjection(RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY) + .returnRow(expectedContactId, expectedEncodedLookup); + } + } +} diff --git a/tests/src/com/android/contacts/common/model/RawContactTest.java b/tests/src/com/android/contacts/common/model/RawContactTest.java new file mode 100644 index 00000000..1c698c0b --- /dev/null +++ b/tests/src/com/android/contacts/common/model/RawContactTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012 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 src.com.android.contacts.common.model; + +import android.content.ContentValues; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.contacts.common.model.RawContact; + +import junit.framework.TestCase; + +/** + * Unit test for {@link RawContact}. + */ +public class RawContactTest extends TestCase { + + private RawContact buildRawContact() { + final ContentValues values = new ContentValues(); + values.put("key1", "value1"); + values.put("key2", "value2"); + + final ContentValues dataItem = new ContentValues(); + dataItem.put("key3", "value3"); + dataItem.put("key4", "value4"); + + final RawContact contact = new RawContact(values); + contact.addDataItemValues(dataItem); + + return contact; + } + + private RawContact buildRawContact2() { + final ContentValues values = new ContentValues(); + values.put("key11", "value11"); + values.put("key22", "value22"); + + final ContentValues dataItem = new ContentValues(); + dataItem.put("key33", "value33"); + dataItem.put("key44", "value44"); + + final RawContact contact = new RawContact(values); + contact.addDataItemValues(dataItem); + + return contact; + } + + public void testNotEquals() { + final RawContact one = buildRawContact(); + final RawContact two = buildRawContact2(); + assertFalse(one.equals(two)); + } + + public void testEquals() { + assertEquals(buildRawContact(), buildRawContact()); + } + + public void testParcelable() { + assertParcelableEquals(buildRawContact()); + } + + private RawContact.NamedDataItem buildNamedDataItem() { + final ContentValues values = new ContentValues(); + values.put("key1", "value1"); + values.put("key2", "value2"); + final Uri uri = Uri.fromParts("content:", "ssp", "fragment"); + + return new RawContact.NamedDataItem(uri, values); + } + + private RawContact.NamedDataItem buildNamedDataItem2() { + final ContentValues values = new ContentValues(); + values.put("key11", "value11"); + values.put("key22", "value22"); + final Uri uri = Uri.fromParts("content:", "blah", "blah"); + + return new RawContact.NamedDataItem(uri, values); + } + + public void testNamedDataItemEquals() { + assertEquals(buildNamedDataItem(), buildNamedDataItem()); + } + + public void testNamedDataItemNotEquals() { + assertFalse(buildNamedDataItem().equals(buildNamedDataItem2())); + } + + public void testNamedDataItemParcelable() { + assertParcelableEquals(buildNamedDataItem()); + } + + private void assertParcelableEquals(Parcelable parcelable) { + final Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(parcelable, 0); + parcel.setDataPosition(0); + + Parcelable out = parcel.readParcelable(parcelable.getClass().getClassLoader()); + assertEquals(parcelable, out); + } finally { + parcel.recycle(); + } + } +} diff --git a/tests/src/com/android/contacts/common/util/NameConverterTests.java b/tests/src/com/android/contacts/common/util/NameConverterTests.java new file mode 100644 index 00000000..c4f67c3b --- /dev/null +++ b/tests/src/com/android/contacts/common/util/NameConverterTests.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2011 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 src.com.android.contacts.common.util; + +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.text.TextUtils; + +import com.android.contacts.common.util.NameConverter; + +import java.util.HashMap; +import java.util.Map; + +/** + * Tests for {@link NameConverter}. + */ +@SmallTest +public class NameConverterTests extends AndroidTestCase { + + public void testStructuredNameToDisplayName() { + Map<String, String> structuredName = new HashMap<String, String>(); + structuredName.put(StructuredName.PREFIX, "Mr."); + structuredName.put(StructuredName.GIVEN_NAME, "John"); + structuredName.put(StructuredName.MIDDLE_NAME, "Quincy"); + structuredName.put(StructuredName.FAMILY_NAME, "Adams"); + structuredName.put(StructuredName.SUFFIX, "Esquire"); + + assertEquals("Mr. John Quincy Adams, Esquire", + NameConverter.structuredNameToDisplayName(mContext, structuredName)); + + structuredName.remove(StructuredName.SUFFIX); + assertEquals("Mr. John Quincy Adams", + NameConverter.structuredNameToDisplayName(mContext, structuredName)); + + structuredName.remove(StructuredName.MIDDLE_NAME); + assertEquals("Mr. John Adams", + NameConverter.structuredNameToDisplayName(mContext, structuredName)); + } + + public void testDisplayNameToStructuredName() { + assertStructuredName("Mr. John Quincy Adams, Esquire", + "Mr.", "John", "Quincy", "Adams", "Esquire"); + assertStructuredName("John Doe", null, "John", null, "Doe", null); + assertStructuredName("Ms. Jane Eyre", "Ms.", "Jane", null, "Eyre", null); + assertStructuredName("Dr Leo Spaceman, PhD", "Dr", "Leo", null, "Spaceman", "PhD"); + } + + /** + * Helper method to check whether a given display name parses out to the other parameters. + * @param displayName Display name to break into a structured name. + * @param prefix Expected prefix (null if not expected). + * @param givenName Expected given name (null if not expected). + * @param middleName Expected middle name (null if not expected). + * @param familyName Expected family name (null if not expected). + * @param suffix Expected suffix (null if not expected). + */ + private void assertStructuredName(String displayName, String prefix, + String givenName, String middleName, String familyName, String suffix) { + Map<String, String> structuredName = NameConverter.displayNameToStructuredName(mContext, + displayName); + checkNameComponent(StructuredName.PREFIX, prefix, structuredName); + checkNameComponent(StructuredName.GIVEN_NAME, givenName, structuredName); + checkNameComponent(StructuredName.MIDDLE_NAME, middleName, structuredName); + checkNameComponent(StructuredName.FAMILY_NAME, familyName, structuredName); + checkNameComponent(StructuredName.SUFFIX, suffix, structuredName); + assertEquals(0, structuredName.size()); + } + + /** + * Checks that the given field and value are present in the structured name map (or not present + * if the given value is null). If the value is present and matches, the key is removed from + * the map - once all components of the name are checked, the map should be empty. + * @param field Field to check. + * @param value Expected value for the field (null if it is not expected to be populated). + * @param structuredName The map of structured field names to values. + */ + private void checkNameComponent(String field, String value, + Map<String, String> structuredName) { + if (TextUtils.isEmpty(value)) { + assertNull(structuredName.get(field)); + } else { + assertEquals(value, structuredName.get(field)); + } + structuredName.remove(field); + } +} |