Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
     20 
     21 import android.accounts.Account;
     22 import android.content.ContentProvider;
     23 import android.content.ContentResolver;
     24 import android.content.ContentUris;
     25 import android.content.ContentValues;
     26 import android.content.Context;
     27 import android.content.Entity;
     28 import android.content.res.Resources;
     29 import android.database.Cursor;
     30 import android.net.Uri;
     31 import android.provider.ContactsContract;
     32 import android.provider.ContactsContract.AggregationExceptions;
     33 import android.provider.ContactsContract.Contacts;
     34 import android.provider.ContactsContract.Data;
     35 import android.provider.ContactsContract.Groups;
     36 import android.provider.ContactsContract.RawContacts;
     37 import android.provider.ContactsContract.Settings;
     38 import android.provider.ContactsContract.StatusUpdates;
     39 import android.provider.ContactsContract.CommonDataKinds.Email;
     40 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     41 import android.provider.ContactsContract.CommonDataKinds.Im;
     42 import android.provider.ContactsContract.CommonDataKinds.Nickname;
     43 import android.provider.ContactsContract.CommonDataKinds.Organization;
     44 import android.provider.ContactsContract.CommonDataKinds.Phone;
     45 import android.provider.ContactsContract.CommonDataKinds.Photo;
     46 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     47 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     48 import android.test.AndroidTestCase;
     49 import android.test.mock.MockContentResolver;
     50 import android.util.Log;
     51 
     52 import java.io.ByteArrayOutputStream;
     53 import java.io.IOException;
     54 import java.io.InputStream;
     55 import java.util.ArrayList;
     56 import java.util.Arrays;
     57 import java.util.Comparator;
     58 import java.util.Iterator;
     59 import java.util.Map;
     60 import java.util.Set;
     61 import java.util.Map.Entry;
     62 
     63 /**
     64  * A common superclass for {@link ContactsProvider2}-related tests.
     65  */
     66 public abstract class BaseContactsProvider2Test extends AndroidTestCase {
     67 
     68     protected static final String PACKAGE = "ContactsProvider2Test";
     69     public static final String READ_ONLY_ACCOUNT_TYPE =
     70             SynchronousContactsProvider2.READ_ONLY_ACCOUNT_TYPE;
     71 
     72     protected ContactsActor mActor;
     73     protected MockContentResolver mResolver;
     74     protected Account mAccount = new Account("account1", "account type1");
     75     protected Account mAccountTwo = new Account("account2", "account type2");
     76 
     77     private byte[] mTestPhoto;
     78 
     79     protected final static Long NO_LONG = new Long(0);
     80     protected final static String NO_STRING = new String("");
     81     protected final static Account NO_ACCOUNT = new Account("a", "b");
     82 
     83     protected Class<? extends ContentProvider> getProviderClass() {
     84         return SynchronousContactsProvider2.class;
     85     }
     86 
     87     protected String getAuthority() {
     88         return ContactsContract.AUTHORITY;
     89     }
     90 
     91     @Override
     92     protected void setUp() throws Exception {
     93         super.setUp();
     94 
     95         mActor = new ContactsActor(getContext(), PACKAGE_GREY, getProviderClass(), getAuthority());
     96         mResolver = mActor.resolver;
     97         if (mActor.provider instanceof SynchronousContactsProvider2) {
     98             ((SynchronousContactsProvider2) mActor.provider)
     99                     .getDatabaseHelper(mActor.context).wipeData();
    100         }
    101     }
    102 
    103     @Override
    104     protected void tearDown() throws Exception {
    105         if (mActor.provider instanceof SynchronousContactsProvider2) {
    106             ((SynchronousContactsProvider2) mActor.provider)
    107                     .getDatabaseHelper(mActor.context).close();
    108         }
    109         super.tearDown();
    110     }
    111 
    112     public Context getMockContext() {
    113         return mActor.context;
    114     }
    115 
    116     public void addAuthority(String authority) {
    117         mActor.addAuthority(authority);
    118     }
    119 
    120     public ContentProvider addProvider(Class<? extends ContentProvider> providerClass,
    121             String authority) throws Exception {
    122         return mActor.addProvider(providerClass, authority);
    123     }
    124 
    125     public ContentProvider getProvider() {
    126         return mActor.provider;
    127     }
    128 
    129     protected Uri maybeAddAccountQueryParameters(Uri uri, Account account) {
    130         if (account == null) {
    131             return uri;
    132         }
    133         return uri.buildUpon()
    134                 .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
    135                 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
    136                 .build();
    137     }
    138 
    139     protected long createRawContact() {
    140         return createRawContact(null);
    141     }
    142 
    143     protected long createRawContactWithName() {
    144         return createRawContactWithName(null);
    145     }
    146 
    147     protected long createRawContactWithName(Account account) {
    148         return createRawContactWithName("John", "Doe", account);
    149     }
    150 
    151     protected long createRawContactWithName(String firstName, String lastName) {
    152         return createRawContactWithName(firstName, lastName, null);
    153     }
    154 
    155     protected long createRawContactWithName(String firstName, String lastName, Account account) {
    156         long rawContactId = createRawContact(account);
    157         insertStructuredName(rawContactId, firstName, lastName);
    158         return rawContactId;
    159     }
    160 
    161     protected Uri setCallerIsSyncAdapter(Uri uri, Account account) {
    162         if (account == null) {
    163             return uri;
    164         }
    165         final Uri.Builder builder = uri.buildUpon();
    166         builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
    167         builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
    168         builder.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true");
    169         return builder.build();
    170     }
    171 
    172     protected long createRawContact(Account account, String... extras) {
    173         ContentValues values = new ContentValues();
    174         for (int i = 0; i < extras.length; ) {
    175             values.put(extras[i], extras[i + 1]);
    176             i += 2;
    177         }
    178         final Uri uri = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account);
    179         Uri contactUri = mResolver.insert(uri, values);
    180         return ContentUris.parseId(contactUri);
    181     }
    182 
    183     protected long createGroup(Account account, String sourceId, String title) {
    184         return createGroup(account, sourceId, title, 1);
    185     }
    186 
    187     protected long createGroup(Account account, String sourceId, String title, int visible) {
    188         ContentValues values = new ContentValues();
    189         values.put(Groups.SOURCE_ID, sourceId);
    190         values.put(Groups.TITLE, title);
    191         values.put(Groups.GROUP_VISIBLE, visible);
    192         final Uri uri = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account);
    193         return ContentUris.parseId(mResolver.insert(uri, values));
    194     }
    195 
    196     protected void createSettings(Account account, String shouldSync, String ungroupedVisible) {
    197         ContentValues values = new ContentValues();
    198         values.put(Settings.ACCOUNT_NAME, account.name);
    199         values.put(Settings.ACCOUNT_TYPE, account.type);
    200         values.put(Settings.SHOULD_SYNC, shouldSync);
    201         values.put(Settings.UNGROUPED_VISIBLE, ungroupedVisible);
    202         mResolver.insert(Settings.CONTENT_URI, values);
    203     }
    204 
    205     protected Uri insertStructuredName(long rawContactId, String givenName, String familyName) {
    206         ContentValues values = new ContentValues();
    207         StringBuilder sb = new StringBuilder();
    208         if (givenName != null) {
    209             sb.append(givenName);
    210         }
    211         if (givenName != null && familyName != null) {
    212             sb.append(" ");
    213         }
    214         if (familyName != null) {
    215             sb.append(familyName);
    216         }
    217         values.put(StructuredName.DISPLAY_NAME, sb.toString());
    218         values.put(StructuredName.GIVEN_NAME, givenName);
    219         values.put(StructuredName.FAMILY_NAME, familyName);
    220 
    221         return insertStructuredName(rawContactId, values);
    222     }
    223 
    224     protected Uri insertStructuredName(long rawContactId, ContentValues values) {
    225         values.put(Data.RAW_CONTACT_ID, rawContactId);
    226         values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    227         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    228         return resultUri;
    229     }
    230 
    231     protected Uri insertOrganization(long rawContactId, ContentValues values) {
    232         return insertOrganization(rawContactId, values, false);
    233     }
    234 
    235     protected Uri insertOrganization(long rawContactId, ContentValues values, boolean primary) {
    236         values.put(Data.RAW_CONTACT_ID, rawContactId);
    237         values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
    238         values.put(Organization.TYPE, Organization.TYPE_WORK);
    239         if (primary) {
    240             values.put(Data.IS_PRIMARY, 1);
    241         }
    242 
    243         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    244         return resultUri;
    245     }
    246 
    247     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber) {
    248         return insertPhoneNumber(rawContactId, phoneNumber, false);
    249     }
    250 
    251     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary) {
    252         ContentValues values = new ContentValues();
    253         values.put(Data.RAW_CONTACT_ID, rawContactId);
    254         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    255         values.put(Phone.NUMBER, phoneNumber);
    256         values.put(Phone.TYPE, Phone.TYPE_HOME);
    257         if (primary) {
    258             values.put(Data.IS_PRIMARY, 1);
    259         }
    260 
    261         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    262         return resultUri;
    263     }
    264 
    265     protected Uri insertEmail(long rawContactId, String email) {
    266         return insertEmail(rawContactId, email, false);
    267     }
    268 
    269     protected Uri insertEmail(long rawContactId, String email, boolean primary) {
    270         return insertEmail(rawContactId, email, primary, Email.TYPE_HOME, null);
    271     }
    272 
    273     protected Uri insertEmail(long rawContactId, String email, boolean primary, int type,
    274             String label) {
    275         ContentValues values = new ContentValues();
    276         values.put(Data.RAW_CONTACT_ID, rawContactId);
    277         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
    278         values.put(Email.DATA, email);
    279         values.put(Email.TYPE, type);
    280         values.put(Email.LABEL, label);
    281         if (primary) {
    282             values.put(Data.IS_PRIMARY, 1);
    283         }
    284 
    285         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    286         return resultUri;
    287     }
    288 
    289     protected Uri insertNickname(long rawContactId, String nickname) {
    290         ContentValues values = new ContentValues();
    291         values.put(Data.RAW_CONTACT_ID, rawContactId);
    292         values.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
    293         values.put(Nickname.NAME, nickname);
    294         values.put(Nickname.TYPE, Nickname.TYPE_OTHER_NAME);
    295 
    296         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    297         return resultUri;
    298     }
    299 
    300     protected Uri insertPostalAddress(long rawContactId, String formattedAddress) {
    301         ContentValues values = new ContentValues();
    302         values.put(Data.RAW_CONTACT_ID, rawContactId);
    303         values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
    304         values.put(StructuredPostal.FORMATTED_ADDRESS, formattedAddress);
    305 
    306         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    307         return resultUri;
    308     }
    309 
    310     protected Uri insertPhoto(long rawContactId) {
    311         ContentValues values = new ContentValues();
    312         values.put(Data.RAW_CONTACT_ID, rawContactId);
    313         values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    314         values.put(Photo.PHOTO, loadTestPhoto());
    315         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    316         return resultUri;
    317     }
    318 
    319     protected Uri insertGroupMembership(long rawContactId, String sourceId) {
    320         ContentValues values = new ContentValues();
    321         values.put(Data.RAW_CONTACT_ID, rawContactId);
    322         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
    323         values.put(GroupMembership.GROUP_SOURCE_ID, sourceId);
    324         return mResolver.insert(Data.CONTENT_URI, values);
    325     }
    326 
    327     protected Uri insertGroupMembership(long rawContactId, Long groupId) {
    328         ContentValues values = new ContentValues();
    329         values.put(Data.RAW_CONTACT_ID, rawContactId);
    330         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
    331         values.put(GroupMembership.GROUP_ROW_ID, groupId);
    332         return mResolver.insert(Data.CONTENT_URI, values);
    333     }
    334 
    335     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
    336             int presence, String status, int chatMode) {
    337         return insertStatusUpdate(protocol, customProtocol, handle, presence, status, 0, chatMode);
    338     }
    339 
    340     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
    341             int presence, String status, long timestamp, int chatMode) {
    342         ContentValues values = new ContentValues();
    343         values.put(StatusUpdates.PROTOCOL, protocol);
    344         values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol);
    345         values.put(StatusUpdates.IM_HANDLE, handle);
    346         if (presence != 0) {
    347             values.put(StatusUpdates.PRESENCE, presence);
    348             values.put(StatusUpdates.CHAT_CAPABILITY, chatMode);
    349         }
    350         if (status != null) {
    351             values.put(StatusUpdates.STATUS, status);
    352         }
    353         if (timestamp != 0) {
    354             values.put(StatusUpdates.STATUS_TIMESTAMP, timestamp);
    355         }
    356 
    357         Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values);
    358         return resultUri;
    359     }
    360 
    361     protected Uri insertImHandle(long rawContactId, int protocol, String customProtocol,
    362             String handle) {
    363         ContentValues values = new ContentValues();
    364         values.put(Data.RAW_CONTACT_ID, rawContactId);
    365         values.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
    366         values.put(Im.PROTOCOL, protocol);
    367         values.put(Im.CUSTOM_PROTOCOL, customProtocol);
    368         values.put(Im.DATA, handle);
    369         values.put(Im.TYPE, Im.TYPE_HOME);
    370 
    371         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    372         return resultUri;
    373     }
    374 
    375     protected void setContactAccount(long rawContactId, String accountType, String accountName) {
    376         ContentValues values = new ContentValues();
    377         values.put(RawContacts.ACCOUNT_TYPE, accountType);
    378         values.put(RawContacts.ACCOUNT_NAME, accountName);
    379 
    380         mResolver.update(ContentUris.withAppendedId(
    381                 RawContacts.CONTENT_URI, rawContactId), values, null, null);
    382     }
    383 
    384     protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
    385         ContentValues values = new ContentValues();
    386         values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
    387         values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
    388         values.put(AggregationExceptions.TYPE, type);
    389         assertEquals(1, mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null));
    390     }
    391 
    392     protected Cursor queryRawContact(long rawContactId) {
    393         return mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
    394                 null, null, null, null);
    395     }
    396 
    397     protected Cursor queryContact(long contactId) {
    398         return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
    399                 null, null, null, null);
    400     }
    401 
    402     protected Cursor queryContact(long contactId, String[] projection) {
    403         return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
    404                 projection, null, null, null);
    405     }
    406 
    407     protected long queryContactId(long rawContactId) {
    408         Cursor c = queryRawContact(rawContactId);
    409         assertTrue(c.moveToFirst());
    410         long contactId = c.getLong(c.getColumnIndex(RawContacts.CONTACT_ID));
    411         c.close();
    412         return contactId;
    413     }
    414 
    415     protected long queryPhotoId(long contactId) {
    416         Cursor c = queryContact(contactId);
    417         assertTrue(c.moveToFirst());
    418         long photoId = c.getInt(c.getColumnIndex(Contacts.PHOTO_ID));
    419         c.close();
    420         return photoId;
    421     }
    422 
    423     protected String queryDisplayName(long contactId) {
    424         Cursor c = queryContact(contactId);
    425         assertTrue(c.moveToFirst());
    426         String displayName = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
    427         c.close();
    428         return displayName;
    429     }
    430 
    431     private String queryLookupKey(long contactId) {
    432         Cursor c = queryContact(contactId);
    433         assertTrue(c.moveToFirst());
    434         String lookupKey = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY));
    435         c.close();
    436         return lookupKey;
    437     }
    438 
    439     protected void assertAggregated(long rawContactId1, long rawContactId2) {
    440         long contactId1 = queryContactId(rawContactId1);
    441         long contactId2 = queryContactId(rawContactId2);
    442         assertTrue(contactId1 == contactId2);
    443     }
    444 
    445     protected void assertAggregated(long rawContactId1, long rawContactId2,
    446             String expectedDisplayName) {
    447         long contactId1 = queryContactId(rawContactId1);
    448         long contactId2 = queryContactId(rawContactId2);
    449         assertTrue(contactId1 == contactId2);
    450 
    451         String displayName = queryDisplayName(contactId1);
    452         assertEquals(expectedDisplayName, displayName);
    453     }
    454 
    455     protected void assertNotAggregated(long rawContactId1, long rawContactId2) {
    456         long contactId1 = queryContactId(rawContactId1);
    457         long contactId2 = queryContactId(rawContactId2);
    458         assertTrue(contactId1 != contactId2);
    459     }
    460 
    461     protected void assertStructuredName(long rawContactId, String prefix, String givenName,
    462             String middleName, String familyName, String suffix) {
    463         Uri uri =
    464                 Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
    465                 RawContacts.Data.CONTENT_DIRECTORY);
    466 
    467         final String[] projection = new String[] {
    468                 StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME,
    469                 StructuredName.FAMILY_NAME, StructuredName.SUFFIX
    470         };
    471 
    472         Cursor c = mResolver.query(uri, projection, Data.MIMETYPE + "='"
    473                 + StructuredName.CONTENT_ITEM_TYPE + "'", null, null);
    474 
    475         assertTrue(c.moveToFirst());
    476         assertEquals(prefix, c.getString(0));
    477         assertEquals(givenName, c.getString(1));
    478         assertEquals(middleName, c.getString(2));
    479         assertEquals(familyName, c.getString(3));
    480         assertEquals(suffix, c.getString(4));
    481         c.close();
    482     }
    483 
    484     protected long assertSingleGroup(Long rowId, Account account, String sourceId, String title) {
    485         Cursor c = mResolver.query(Groups.CONTENT_URI, null, null, null, null);
    486         try {
    487             assertTrue(c.moveToNext());
    488             long actualRowId = assertGroup(c, rowId, account, sourceId, title);
    489             assertFalse(c.moveToNext());
    490             return actualRowId;
    491         } finally {
    492             c.close();
    493         }
    494     }
    495 
    496     protected long assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId,
    497             String sourceId) {
    498         Cursor c = mResolver.query(ContactsContract.Data.CONTENT_URI, null, null, null, null);
    499         try {
    500             assertTrue(c.moveToNext());
    501             long actualRowId = assertGroupMembership(c, rowId, rawContactId, groupRowId, sourceId);
    502             assertFalse(c.moveToNext());
    503             return actualRowId;
    504         } finally {
    505             c.close();
    506         }
    507     }
    508 
    509     protected long assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId,
    510             String sourceId) {
    511         assertNullOrEquals(c, rowId, Data._ID);
    512         assertNullOrEquals(c, rawContactId, GroupMembership.RAW_CONTACT_ID);
    513         assertNullOrEquals(c, groupRowId, GroupMembership.GROUP_ROW_ID);
    514         assertNullOrEquals(c, sourceId, GroupMembership.GROUP_SOURCE_ID);
    515         return c.getLong(c.getColumnIndexOrThrow("_id"));
    516     }
    517 
    518     protected long assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title) {
    519         assertNullOrEquals(c, rowId, Groups._ID);
    520         assertNullOrEquals(c, account);
    521         assertNullOrEquals(c, sourceId, Groups.SOURCE_ID);
    522         assertNullOrEquals(c, title, Groups.TITLE);
    523         return c.getLong(c.getColumnIndexOrThrow("_id"));
    524     }
    525 
    526     private void assertNullOrEquals(Cursor c, Account account) {
    527         if (account == NO_ACCOUNT) {
    528             return;
    529         }
    530         if (account == null) {
    531             assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME)));
    532             assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE)));
    533         } else {
    534             assertEquals(account.name, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME)));
    535             assertEquals(account.type, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE)));
    536         }
    537     }
    538 
    539     private void assertNullOrEquals(Cursor c, Long value, String columnName) {
    540         if (value != NO_LONG) {
    541             if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName)));
    542             else assertEquals((long) value, c.getLong(c.getColumnIndexOrThrow(columnName)));
    543         }
    544     }
    545 
    546     private void assertNullOrEquals(Cursor c, String value, String columnName) {
    547         if (value != NO_STRING) {
    548             if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName)));
    549             else assertEquals(value, c.getString(c.getColumnIndexOrThrow(columnName)));
    550         }
    551     }
    552 
    553     protected void assertDataRow(ContentValues actual, String expectedMimetype,
    554             Object... expectedArguments) {
    555         assertEquals(actual.toString(), expectedMimetype, actual.getAsString(Data.MIMETYPE));
    556         for (int i = 0; i < expectedArguments.length; i += 2) {
    557             String columnName = (String) expectedArguments[i];
    558             Object expectedValue = expectedArguments[i + 1];
    559             if (expectedValue instanceof Uri) {
    560                 expectedValue = ContentUris.parseId((Uri) expectedValue);
    561             }
    562             if (expectedValue == null) {
    563                 assertNull(actual.toString(), actual.get(columnName));
    564             }
    565             if (expectedValue instanceof Long) {
    566                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    567                         expectedValue, actual.getAsLong(columnName));
    568             } else if (expectedValue instanceof Integer) {
    569                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    570                         expectedValue, actual.getAsInteger(columnName));
    571             } else if (expectedValue instanceof String) {
    572                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    573                         expectedValue, actual.getAsString(columnName));
    574             } else {
    575                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    576                         expectedValue, actual.get(columnName));
    577             }
    578         }
    579     }
    580 
    581     protected static class IdComparator implements Comparator<ContentValues> {
    582         public int compare(ContentValues o1, ContentValues o2) {
    583             long id1 = o1.getAsLong(ContactsContract.Data._ID);
    584             long id2 = o2.getAsLong(ContactsContract.Data._ID);
    585             if (id1 == id2) return 0;
    586             return (id1 < id2) ? -1 : 1;
    587         }
    588     }
    589 
    590     protected ContentValues[] asSortedContentValuesArray(
    591             ArrayList<Entity.NamedContentValues> subValues) {
    592         ContentValues[] result = new ContentValues[subValues.size()];
    593         int i = 0;
    594         for (Entity.NamedContentValues subValue : subValues) {
    595             result[i] = subValue.values;
    596             i++;
    597         }
    598         Arrays.sort(result, new IdComparator());
    599         return result;
    600     }
    601 
    602     protected void assertDirty(Uri uri, boolean state) {
    603         Cursor c = mResolver.query(uri, new String[]{"dirty"}, null, null, null);
    604         assertTrue(c.moveToNext());
    605         assertEquals(state, c.getLong(0) != 0);
    606         assertFalse(c.moveToNext());
    607         c.close();
    608     }
    609 
    610     protected long getVersion(Uri uri) {
    611         Cursor c = mResolver.query(uri, new String[]{"version"}, null, null, null);
    612         assertTrue(c.moveToNext());
    613         long version = c.getLong(0);
    614         assertFalse(c.moveToNext());
    615         c.close();
    616         return version;
    617     }
    618 
    619     protected void clearDirty(Uri uri) {
    620         ContentValues values = new ContentValues();
    621         values.put("dirty", 0);
    622         mResolver.update(uri, values, null, null);
    623     }
    624 
    625     protected void storeValue(Uri contentUri, long id, String column, String value) {
    626         storeValue(ContentUris.withAppendedId(contentUri, id), column, value);
    627     }
    628 
    629     protected void storeValue(Uri contentUri, String column, String value) {
    630         ContentValues values = new ContentValues();
    631         values.put(column, value);
    632 
    633         mResolver.update(contentUri, values, null, null);
    634     }
    635 
    636     protected void storeValue(Uri contentUri, long id, String column, long value) {
    637         storeValue(ContentUris.withAppendedId(contentUri, id), column, value);
    638     }
    639 
    640     protected void storeValue(Uri contentUri, String column, long value) {
    641         ContentValues values = new ContentValues();
    642         values.put(column, value);
    643 
    644         mResolver.update(contentUri, values, null, null);
    645     }
    646 
    647     protected void assertStoredValue(Uri contentUri, long id, String column, Object expectedValue) {
    648         assertStoredValue(ContentUris.withAppendedId(contentUri, id), column, expectedValue);
    649     }
    650 
    651     protected void assertStoredValue(Uri rowUri, String column, Object expectedValue) {
    652         String value = getStoredValue(rowUri, column);
    653         if (expectedValue == null) {
    654             assertNull("Column value " + column, value);
    655         } else {
    656             assertEquals("Column value " + column, String.valueOf(expectedValue), value);
    657         }
    658     }
    659 
    660     protected void assertStoredValue(Uri rowUri, String selection, String[] selectionArgs,
    661             String column, Object expectedValue) {
    662         String value = getStoredValue(rowUri, selection, selectionArgs, column);
    663         if (expectedValue == null) {
    664             assertNull("Column value " + column, value);
    665         } else {
    666             assertEquals("Column value " + column, String.valueOf(expectedValue), value);
    667         }
    668     }
    669 
    670     protected String getStoredValue(Uri rowUri, String column) {
    671         return getStoredValue(rowUri, null, null, column);
    672     }
    673 
    674     protected String getStoredValue(Uri uri, String selection, String[] selectionArgs,
    675             String column) {
    676         String value = null;
    677         Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null);
    678         try {
    679             if (c.moveToFirst()) {
    680                 value = c.getString(c.getColumnIndex(column));
    681             }
    682         } finally {
    683             c.close();
    684         }
    685         return value;
    686     }
    687 
    688     protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) {
    689         assertStoredValues(rowUri, null, null, expectedValues);
    690     }
    691 
    692     protected void assertStoredValues(Uri rowUri, String selection, String[] selectionArgs,
    693             ContentValues expectedValues) {
    694         Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null);
    695         try {
    696             assertEquals("Record count", 1, c.getCount());
    697             c.moveToFirst();
    698             assertCursorValues(c, expectedValues);
    699         } finally {
    700             c.close();
    701         }
    702     }
    703 
    704     protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues) {
    705         Cursor c = mResolver.query(rowUri, buildProjection(expectedValues), null, null, null);
    706         try {
    707             assertEquals("Record count", 1, c.getCount());
    708             c.moveToFirst();
    709             assertCursorValues(c, expectedValues);
    710         } finally {
    711             c.close();
    712         }
    713     }
    714 
    715     /**
    716      * Constructs a selection (where clause) out of all supplied values, uses it
    717      * to query the provider and verifies that a single row is returned and it
    718      * has the same values as requested.
    719      */
    720     protected void assertSelection(Uri uri, ContentValues values, String idColumn, long id) {
    721         assertSelection(uri, values, idColumn, id, null);
    722     }
    723 
    724     public void assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn,
    725             long id) {
    726         assertSelection(uri, values, idColumn, id, buildProjection(values));
    727     }
    728 
    729     private void assertSelection(Uri uri, ContentValues values, String idColumn, long id,
    730             String[] projection) {
    731         StringBuilder sb = new StringBuilder();
    732         ArrayList<String> selectionArgs = new ArrayList<String>(values.size());
    733         if (idColumn != null) {
    734             sb.append(idColumn).append("=").append(id);
    735         }
    736         Set<Map.Entry<String, Object>> entries = values.valueSet();
    737         for (Map.Entry<String, Object> entry : entries) {
    738             String column = entry.getKey();
    739             Object value = entry.getValue();
    740             if (sb.length() != 0) {
    741                 sb.append(" AND ");
    742             }
    743             sb.append(column);
    744             if (value == null) {
    745                 sb.append(" IS NULL");
    746             } else {
    747                 sb.append("=?");
    748                 selectionArgs.add(String.valueOf(value));
    749             }
    750         }
    751 
    752         Cursor c = mResolver.query(uri, projection, sb.toString(), selectionArgs.toArray(new String[0]),
    753                 null);
    754         try {
    755             assertEquals("Record count", 1, c.getCount());
    756             c.moveToFirst();
    757             assertCursorValues(c, values);
    758         } finally {
    759             c.close();
    760         }
    761     }
    762 
    763     protected void assertCursorValues(Cursor cursor, ContentValues expectedValues) {
    764         Set<Map.Entry<String, Object>> entries = expectedValues.valueSet();
    765         for (Map.Entry<String, Object> entry : entries) {
    766             String column = entry.getKey();
    767             int index = cursor.getColumnIndex(column);
    768             assertTrue("No such column: " + column, index != -1);
    769             Object expectedValue = expectedValues.get(column);
    770             String value;
    771             if (expectedValue instanceof byte[]) {
    772                 expectedValue = Hex.encodeHex((byte[])expectedValue, false);
    773                 value = Hex.encodeHex(cursor.getBlob(index), false);
    774             } else {
    775                 expectedValue = expectedValues.getAsString(column);
    776                 value = cursor.getString(index);
    777             }
    778             assertEquals("Column value " + column, expectedValue, value);
    779         }
    780     }
    781 
    782     private String[] buildProjection(ContentValues values) {
    783         String[] projection = new String[values.size()];
    784         Iterator<Entry<String, Object>> iter = values.valueSet().iterator();
    785         for (int i = 0; i < projection.length; i++) {
    786             projection[i] = iter.next().getKey();
    787         }
    788         return projection;
    789     }
    790 
    791     protected int getCount(Uri uri, String selection, String[] selectionArgs) {
    792         Cursor c = mResolver.query(uri, null, selection, selectionArgs, null);
    793         try {
    794             return c.getCount();
    795         } finally {
    796             c.close();
    797         }
    798     }
    799 
    800     protected byte[] loadTestPhoto() {
    801         if (mTestPhoto == null) {
    802             final Resources resources = getContext().getResources();
    803             InputStream is = resources
    804                     .openRawResource(com.android.internal.R.drawable.ic_contact_picture);
    805             ByteArrayOutputStream os = new ByteArrayOutputStream();
    806             byte[] buffer = new byte[1000];
    807             int count;
    808             try {
    809                 while ((count = is.read(buffer)) != -1) {
    810                     os.write(buffer, 0, count);
    811                 }
    812             } catch (IOException e) {
    813                 throw new RuntimeException(e);
    814             }
    815             mTestPhoto = os.toByteArray();
    816         }
    817         return mTestPhoto;
    818     }
    819 
    820     public static void dump(ContentResolver resolver, boolean aggregatedOnly) {
    821         String[] projection = new String[] {
    822                 Contacts._ID,
    823                 Contacts.DISPLAY_NAME
    824         };
    825         String selection = null;
    826         if (aggregatedOnly) {
    827             selection = Contacts._ID
    828                     + " IN (SELECT contact_id" +
    829                     		" FROM raw_contacts GROUP BY contact_id HAVING count(*) > 1)";
    830         }
    831 
    832         Cursor c = resolver.query(Contacts.CONTENT_URI, projection, selection, null,
    833                 Contacts.DISPLAY_NAME);
    834         while(c.moveToNext()) {
    835             long contactId = c.getLong(0);
    836             Log.i("Contact   ", String.format("%5d %s", contactId, c.getString(1)));
    837             dumpRawContacts(resolver, contactId);
    838             Log.i("          ", ".");
    839         }
    840         c.close();
    841     }
    842 
    843     private static void dumpRawContacts(ContentResolver resolver, long contactId) {
    844         String[] projection = new String[] {
    845                 RawContacts._ID,
    846         };
    847         Cursor c = resolver.query(RawContacts.CONTENT_URI, projection, RawContacts.CONTACT_ID + "="
    848                 + contactId, null, null);
    849         while(c.moveToNext()) {
    850             long rawContactId = c.getLong(0);
    851             Log.i("RawContact", String.format("      %-5d", rawContactId));
    852             dumpData(resolver, rawContactId);
    853         }
    854         c.close();
    855     }
    856 
    857     private static void dumpData(ContentResolver resolver, long rawContactId) {
    858         String[] projection = new String[] {
    859                 Data.MIMETYPE,
    860                 Data.DATA1,
    861                 Data.DATA2,
    862                 Data.DATA3,
    863         };
    864         Cursor c = resolver.query(Data.CONTENT_URI, projection, Data.RAW_CONTACT_ID + "="
    865                 + rawContactId, null, Data.MIMETYPE);
    866         while(c.moveToNext()) {
    867             String mimetype = c.getString(0);
    868             if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
    869                 Log.i("Photo     ", "");
    870             } else {
    871                 mimetype = mimetype.substring(mimetype.indexOf('/') + 1);
    872                 Log.i("Data      ", String.format("            %-10s %s,%s,%s", mimetype,
    873                         c.getString(1), c.getString(2), c.getString(3)));
    874             }
    875         }
    876         c.close();
    877     }
    878 
    879     protected void assertNetworkNotified(boolean expected) {
    880         assertEquals(expected, ((SynchronousContactsProvider2)mActor.provider).isNetworkNotified());
    881     }
    882 
    883     /**
    884      * A contact in the database, and the attributes used to create it.  Construct using
    885      * {@link GoldenContactBuilder#build()}.
    886      */
    887     public final class GoldenContact {
    888 
    889         private final long rawContactId;
    890 
    891         private final long contactId;
    892 
    893         private final String givenName;
    894 
    895         private final String familyName;
    896 
    897         private final String nickname;
    898 
    899         private final byte[] photo;
    900 
    901         private final String company;
    902 
    903         private final String title;
    904 
    905         private final String phone;
    906 
    907         private final String email;
    908 
    909         private GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId) {
    910 
    911             this.rawContactId = rawContactId;
    912             this.contactId = contactId;
    913             givenName = builder.givenName;
    914             familyName = builder.familyName;
    915             nickname = builder.nickname;
    916             photo = builder.photo;
    917             company = builder.company;
    918             title = builder.title;
    919             phone = builder.phone;
    920             email = builder.email;
    921         }
    922 
    923         public void delete() {
    924             Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
    925             mResolver.delete(rawContactUri, null, null);
    926         }
    927 
    928         /**
    929          * Returns the index of the contact in table "raw_contacts"
    930          */
    931         public long getRawContactId() {
    932             return rawContactId;
    933         }
    934 
    935         /**
    936          * Returns the index of the contact in table "contacts"
    937          */
    938         public long getContactId() {
    939             return contactId;
    940         }
    941 
    942         /**
    943          * Returns the lookup key for the contact.
    944          */
    945         public String getLookupKey() {
    946             return queryLookupKey(contactId);
    947         }
    948 
    949         /**
    950          * Returns the contact's given name.
    951          */
    952         public String getGivenName() {
    953             return givenName;
    954         }
    955 
    956         /**
    957          * Returns the contact's family name.
    958          */
    959         public String getFamilyName() {
    960             return familyName;
    961         }
    962 
    963         /**
    964          * Returns the contact's nickname.
    965          */
    966         public String getNickname() {
    967             return nickname;
    968         }
    969 
    970         /**
    971          * Return's the contact's photo
    972          */
    973         public byte[] getPhoto() {
    974             return photo;
    975         }
    976 
    977         /**
    978          * Return's the company at which the contact works.
    979          */
    980         public String getCompany() {
    981             return company;
    982         }
    983 
    984         /**
    985          * Returns the contact's job title.
    986          */
    987         public String getTitle() {
    988             return title;
    989         }
    990 
    991         /**
    992          * Returns the contact's phone number
    993          */
    994         public String getPhone() {
    995             return phone;
    996         }
    997 
    998         /**
    999          * Returns the contact's email address
   1000          */
   1001         public String getEmail() {
   1002             return email;
   1003         }
   1004      }
   1005 
   1006     /**
   1007      * Builds {@link GoldenContact} objects.  Unspecified boolean objects default to false.
   1008      * Unspecified String objects default to null.
   1009      */
   1010     public final class GoldenContactBuilder {
   1011 
   1012         private String givenName;
   1013 
   1014         private String familyName;
   1015 
   1016         private String nickname;
   1017 
   1018         private byte[] photo;
   1019 
   1020         private String company;
   1021 
   1022         private String title;
   1023 
   1024         private String phone;
   1025 
   1026         private String email;
   1027 
   1028         /**
   1029          * The contact's given and family names.
   1030          *
   1031          * TODO(dplotnikov): inline, or should we require them to set both names if they set either?
   1032          */
   1033         public GoldenContactBuilder name(String givenName, String familyName) {
   1034             return givenName(givenName).familyName(familyName);
   1035         }
   1036 
   1037         /**
   1038          * The contact's given name.
   1039          */
   1040         public GoldenContactBuilder givenName(String value) {
   1041             givenName = value;
   1042             return this;
   1043         }
   1044 
   1045         /**
   1046          * The contact's family name.
   1047          */
   1048         public GoldenContactBuilder familyName(String value) {
   1049             familyName = value;
   1050             return this;
   1051         }
   1052 
   1053         /**
   1054          * The contact's nickname.
   1055          */
   1056         public GoldenContactBuilder nickname(String value) {
   1057             nickname = value;
   1058             return this;
   1059         }
   1060 
   1061         /**
   1062          * The contact's photo.
   1063          */
   1064         public GoldenContactBuilder photo(byte[] value) {
   1065             photo = value;
   1066             return this;
   1067         }
   1068 
   1069         /**
   1070          * The company at which the contact works.
   1071          */
   1072         public GoldenContactBuilder company(String value) {
   1073             company = value;
   1074             return this;
   1075         }
   1076 
   1077         /**
   1078          * The contact's job title.
   1079          */
   1080         public GoldenContactBuilder title(String value) {
   1081             title = value;
   1082             return this;
   1083         }
   1084 
   1085         /**
   1086          * The contact's phone number.
   1087          */
   1088         public GoldenContactBuilder phone(String value) {
   1089             phone = value;
   1090             return this;
   1091         }
   1092 
   1093         /**
   1094          * The contact's email address; also sets their IM status to {@link StatusUpdates#OFFLINE}
   1095          * with a presence of "Coding for Android".
   1096          */
   1097         public GoldenContactBuilder email(String value) {
   1098             email = value;
   1099             return this;
   1100         }
   1101 
   1102         /**
   1103          * Builds the {@link GoldenContact} specified by this builder.
   1104          */
   1105         public GoldenContact build() {
   1106 
   1107             final long groupId = createGroup(mAccount, "gsid1", "title1");
   1108 
   1109             long rawContactId = createRawContact();
   1110             insertGroupMembership(rawContactId, groupId);
   1111 
   1112             if (givenName != null || familyName != null) {
   1113                 insertStructuredName(rawContactId, givenName, familyName);
   1114             }
   1115             if (nickname != null) {
   1116                 insertNickname(rawContactId, nickname);
   1117             }
   1118             if (photo != null) {
   1119                 insertPhoto(rawContactId);
   1120             }
   1121             if (company != null || title != null) {
   1122                 insertOrganization(rawContactId);
   1123             }
   1124             if (email != null) {
   1125                 insertEmail(rawContactId);
   1126             }
   1127             if (phone != null) {
   1128                 insertPhone(rawContactId);
   1129             }
   1130 
   1131             long contactId = queryContactId(rawContactId);
   1132 
   1133             return new GoldenContact(this, rawContactId, contactId);
   1134         }
   1135 
   1136         private void insertPhoto(long rawContactId) {
   1137             ContentValues values = new ContentValues();
   1138             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1139             values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
   1140             values.put(Photo.PHOTO, photo);
   1141             mResolver.insert(Data.CONTENT_URI, values);
   1142         }
   1143 
   1144         private void insertOrganization(long rawContactId) {
   1145 
   1146             ContentValues values = new ContentValues();
   1147             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1148             values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
   1149             values.put(Organization.TYPE, Organization.TYPE_WORK);
   1150             if (company != null) {
   1151                 values.put(Organization.COMPANY, company);
   1152             }
   1153             if (title != null) {
   1154                 values.put(Organization.TITLE, title);
   1155             }
   1156             mResolver.insert(Data.CONTENT_URI, values);
   1157         }
   1158 
   1159         private void insertEmail(long rawContactId) {
   1160 
   1161             ContentValues values = new ContentValues();
   1162             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1163             values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
   1164             values.put(Email.TYPE, Email.TYPE_WORK);
   1165             values.put(Email.DATA, "foo (at) acme.com");
   1166             mResolver.insert(Data.CONTENT_URI, values);
   1167 
   1168             int protocol = Im.PROTOCOL_GOOGLE_TALK;
   1169 
   1170             values.clear();
   1171             values.put(StatusUpdates.PROTOCOL, protocol);
   1172             values.put(StatusUpdates.IM_HANDLE, email);
   1173             values.put(StatusUpdates.IM_ACCOUNT, "foo");
   1174             values.put(StatusUpdates.PRESENCE_STATUS, StatusUpdates.OFFLINE);
   1175             values.put(StatusUpdates.CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
   1176             values.put(StatusUpdates.PRESENCE_CUSTOM_STATUS, "Coding for Android");
   1177             mResolver.insert(StatusUpdates.CONTENT_URI, values);
   1178         }
   1179 
   1180         private void insertPhone(long rawContactId) {
   1181             ContentValues values = new ContentValues();
   1182             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1183             values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1184             values.put(Data.IS_PRIMARY, 1);
   1185             values.put(Phone.TYPE, Phone.TYPE_HOME);
   1186             values.put(Phone.NUMBER, phone);
   1187             mResolver.insert(Data.CONTENT_URI, values);
   1188         }
   1189     }
   1190 }
   1191