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 import static com.android.providers.contacts.TestUtils.cv;
     21 
     22 import android.accounts.Account;
     23 import android.content.ContentProvider;
     24 import android.content.ContentResolver;
     25 import android.content.ContentUris;
     26 import android.content.ContentValues;
     27 import android.content.Context;
     28 import android.content.Entity;
     29 import android.database.Cursor;
     30 import android.database.sqlite.SQLiteDatabase;
     31 import android.net.Uri;
     32 import android.provider.BaseColumns;
     33 import android.provider.ContactsContract;
     34 import android.provider.ContactsContract.AggregationExceptions;
     35 import android.provider.ContactsContract.CommonDataKinds.Email;
     36 import android.provider.ContactsContract.CommonDataKinds.Event;
     37 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     38 import android.provider.ContactsContract.CommonDataKinds.Identity;
     39 import android.provider.ContactsContract.CommonDataKinds.Im;
     40 import android.provider.ContactsContract.CommonDataKinds.Nickname;
     41 import android.provider.ContactsContract.CommonDataKinds.Note;
     42 import android.provider.ContactsContract.CommonDataKinds.Organization;
     43 import android.provider.ContactsContract.CommonDataKinds.Phone;
     44 import android.provider.ContactsContract.CommonDataKinds.Photo;
     45 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
     46 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     47 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     48 import android.provider.ContactsContract.Contacts;
     49 import android.provider.ContactsContract.Data;
     50 import android.provider.ContactsContract.Groups;
     51 import android.provider.ContactsContract.RawContacts;
     52 import android.provider.ContactsContract.Settings;
     53 import android.provider.ContactsContract.StatusUpdates;
     54 import android.provider.ContactsContract.StreamItems;
     55 import android.test.MoreAsserts;
     56 import android.test.mock.MockContentResolver;
     57 import android.util.Log;
     58 
     59 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
     60 import com.android.providers.contacts.testutil.CommonDatabaseUtils;
     61 import com.android.providers.contacts.testutil.DataUtil;
     62 import com.android.providers.contacts.testutil.RawContactUtil;
     63 import com.android.providers.contacts.testutil.TestUtil;
     64 import com.android.providers.contacts.util.Hex;
     65 import com.android.providers.contacts.util.MockClock;
     66 import com.google.android.collect.Sets;
     67 
     68 import java.util.ArrayList;
     69 import java.util.Arrays;
     70 import java.util.BitSet;
     71 import java.util.Comparator;
     72 import java.util.Iterator;
     73 import java.util.Map;
     74 import java.util.Map.Entry;
     75 import java.util.Set;
     76 
     77 /**
     78  * A common superclass for {@link ContactsProvider2}-related tests.
     79  */
     80 public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
     81 
     82     protected static final String PACKAGE = "ContactsProvider2Test";
     83     public static final String READ_ONLY_ACCOUNT_TYPE =
     84             SynchronousContactsProvider2.READ_ONLY_ACCOUNT_TYPE;
     85 
     86     protected ContactsActor mActor;
     87     protected MockContentResolver mResolver;
     88     protected Account mAccount = new Account("account1", "account type1");
     89     protected Account mAccountTwo = new Account("account2", "account type2");
     90 
     91     protected final static Long NO_LONG = new Long(0);
     92     protected final static String NO_STRING = new String("");
     93     protected final static Account NO_ACCOUNT = new Account("a", "b");
     94 
     95     /**
     96      * Use {@link MockClock#install()} to start using it.
     97      * It'll be automatically uninstalled by {@link #tearDown()}.
     98      */
     99     protected static final MockClock sMockClock = new MockClock();
    100 
    101     protected Class<? extends ContentProvider> getProviderClass() {
    102         return SynchronousContactsProvider2.class;
    103     }
    104 
    105     protected String getAuthority() {
    106         return ContactsContract.AUTHORITY;
    107     }
    108 
    109     @Override
    110     protected void setUp() throws Exception {
    111         super.setUp();
    112 
    113         mActor = new ContactsActor(getContext(), PACKAGE_GREY, getProviderClass(), getAuthority());
    114         mResolver = mActor.resolver;
    115         if (mActor.provider instanceof SynchronousContactsProvider2) {
    116             getContactsProvider().wipeData();
    117         }
    118 
    119         // Give the actor access to read/write contacts and profile data by default.
    120         mActor.addPermissions(
    121                 "android.permission.READ_CONTACTS",
    122                 "android.permission.WRITE_CONTACTS",
    123                 "android.permission.READ_SOCIAL_STREAM",
    124                 "android.permission.WRITE_SOCIAL_STREAM",
    125                 "android.permission.READ_PROFILE",
    126                 "android.permission.WRITE_PROFILE");
    127     }
    128 
    129     @Override
    130     protected void tearDown() throws Exception {
    131         sMockClock.uninstall();
    132         super.tearDown();
    133     }
    134 
    135     public SynchronousContactsProvider2 getContactsProvider() {
    136         return (SynchronousContactsProvider2) mActor.provider;
    137     }
    138 
    139     public Context getMockContext() {
    140         return mActor.context;
    141     }
    142 
    143     public void addAuthority(String authority) {
    144         mActor.addAuthority(authority);
    145     }
    146 
    147     public ContentProvider addProvider(Class<? extends ContentProvider> providerClass,
    148             String authority) throws Exception {
    149         return mActor.addProvider(providerClass, authority);
    150     }
    151 
    152     public ContentProvider getProvider() {
    153         return mActor.provider;
    154     }
    155 
    156     protected Uri setCallerIsSyncAdapter(Uri uri, Account account) {
    157         if (account == null) {
    158             return uri;
    159         }
    160         final Uri.Builder builder = uri.buildUpon();
    161         builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
    162         builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
    163         builder.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true");
    164         return builder.build();
    165     }
    166 
    167     protected int updateItem(Uri uri, long id, String... extras) {
    168         Uri itemUri = ContentUris.withAppendedId(uri, id);
    169         return updateItem(itemUri, extras);
    170     }
    171 
    172     protected int updateItem(Uri uri, String... extras) {
    173         ContentValues values = new ContentValues();
    174         CommonDatabaseUtils.extrasVarArgsToValues(values, extras);
    175         return mResolver.update(uri, values, null, null);
    176     }
    177 
    178     protected long createGroup(Account account, String sourceId, String title) {
    179         return createGroup(account, sourceId, title, 1, false, false);
    180     }
    181 
    182     protected long createGroup(Account account, String sourceId, String title, int visible) {
    183         return createGroup(account, sourceId, title, visible, false, false);
    184     }
    185 
    186     protected long createAutoAddGroup(Account account) {
    187         return createGroup(account, "auto", "auto",
    188                 0 /* visible */,  true /* auto-add */, false /* fav */);
    189     }
    190 
    191     protected long createGroup(Account account, String sourceId, String title,
    192             int visible, boolean autoAdd, boolean favorite) {
    193         ContentValues values = new ContentValues();
    194         values.put(Groups.SOURCE_ID, sourceId);
    195         values.put(Groups.TITLE, title);
    196         values.put(Groups.GROUP_VISIBLE, visible);
    197         values.put(Groups.AUTO_ADD, autoAdd ? 1 : 0);
    198         values.put(Groups.FAVORITES, favorite ? 1 : 0);
    199         final Uri uri = TestUtil.maybeAddAccountQueryParameters(Groups.CONTENT_URI, account);
    200         return ContentUris.parseId(mResolver.insert(uri, values));
    201     }
    202 
    203     protected void createSettings(Account account, String shouldSync, String ungroupedVisible) {
    204         createSettings(new AccountWithDataSet(account.name, account.type, null),
    205                 shouldSync, ungroupedVisible);
    206     }
    207 
    208     protected void createSettings(AccountWithDataSet account, String shouldSync,
    209             String ungroupedVisible) {
    210         ContentValues values = new ContentValues();
    211         values.put(Settings.ACCOUNT_NAME, account.getAccountName());
    212         values.put(Settings.ACCOUNT_TYPE, account.getAccountType());
    213         if (account.getDataSet() != null) {
    214             values.put(Settings.DATA_SET, account.getDataSet());
    215         }
    216         values.put(Settings.SHOULD_SYNC, shouldSync);
    217         values.put(Settings.UNGROUPED_VISIBLE, ungroupedVisible);
    218         mResolver.insert(Settings.CONTENT_URI, values);
    219     }
    220 
    221     protected Uri insertOrganization(long rawContactId, ContentValues values) {
    222         return insertOrganization(rawContactId, values, false);
    223     }
    224 
    225     protected Uri insertOrganization(long rawContactId, ContentValues values, boolean primary) {
    226         values.put(Data.RAW_CONTACT_ID, rawContactId);
    227         values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
    228         values.put(Organization.TYPE, Organization.TYPE_WORK);
    229         if (primary) {
    230             values.put(Data.IS_PRIMARY, 1);
    231         }
    232 
    233         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    234         return resultUri;
    235     }
    236 
    237     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber) {
    238         return insertPhoneNumber(rawContactId, phoneNumber, false);
    239     }
    240 
    241     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary) {
    242         return insertPhoneNumber(rawContactId, phoneNumber, primary, Phone.TYPE_HOME);
    243     }
    244 
    245     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary,
    246             int type) {
    247         ContentValues values = new ContentValues();
    248         values.put(Data.RAW_CONTACT_ID, rawContactId);
    249         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    250         values.put(Phone.NUMBER, phoneNumber);
    251         values.put(Phone.TYPE, type);
    252         if (primary) {
    253             values.put(Data.IS_PRIMARY, 1);
    254         }
    255 
    256         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    257         return resultUri;
    258     }
    259 
    260     protected Uri insertEmail(long rawContactId, String email) {
    261         return insertEmail(rawContactId, email, false);
    262     }
    263 
    264     protected Uri insertEmail(long rawContactId, String email, boolean primary) {
    265         return insertEmail(rawContactId, email, primary, Email.TYPE_HOME, null);
    266     }
    267 
    268     protected Uri insertEmail(long rawContactId, String email, boolean primary,
    269             boolean superPrimary) {
    270         return insertEmail(rawContactId, email, primary, superPrimary, Email.TYPE_HOME, null);
    271     }
    272 
    273     protected Uri insertEmail(long rawContactId, String email, boolean primary, int type,
    274             String label) {
    275         return insertEmail(rawContactId, email, primary, false, type, label);
    276     }
    277 
    278     protected Uri insertEmail(long rawContactId, String email, boolean primary,
    279             boolean superPrimary, int type,  String label) {
    280         ContentValues values = new ContentValues();
    281         values.put(Data.RAW_CONTACT_ID, rawContactId);
    282         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
    283         values.put(Email.DATA, email);
    284         values.put(Email.TYPE, type);
    285         values.put(Email.LABEL, label);
    286         if (primary) {
    287             values.put(Data.IS_PRIMARY, 1);
    288         }
    289         if (superPrimary) {
    290             values.put(Data.IS_SUPER_PRIMARY, 1);
    291         }
    292 
    293         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    294         return resultUri;
    295     }
    296 
    297     protected Uri insertSipAddress(long rawContactId, String sipAddress) {
    298         return insertSipAddress(rawContactId, sipAddress, false);
    299     }
    300 
    301     protected Uri insertSipAddress(long rawContactId, String sipAddress, boolean primary) {
    302         ContentValues values = new ContentValues();
    303         values.put(Data.RAW_CONTACT_ID, rawContactId);
    304         values.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
    305         values.put(SipAddress.SIP_ADDRESS, sipAddress);
    306         if (primary) {
    307             values.put(Data.IS_PRIMARY, 1);
    308         }
    309 
    310         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    311         return resultUri;
    312     }
    313 
    314     protected Uri insertNickname(long rawContactId, String nickname) {
    315         ContentValues values = new ContentValues();
    316         values.put(Data.RAW_CONTACT_ID, rawContactId);
    317         values.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
    318         values.put(Nickname.NAME, nickname);
    319         values.put(Nickname.TYPE, Nickname.TYPE_OTHER_NAME);
    320 
    321         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    322         return resultUri;
    323     }
    324 
    325     protected Uri insertPostalAddress(long rawContactId, String formattedAddress) {
    326         ContentValues values = new ContentValues();
    327         values.put(Data.RAW_CONTACT_ID, rawContactId);
    328         values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
    329         values.put(StructuredPostal.FORMATTED_ADDRESS, formattedAddress);
    330 
    331         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    332         return resultUri;
    333     }
    334 
    335     protected Uri insertPostalAddress(long rawContactId, ContentValues values) {
    336         values.put(Data.RAW_CONTACT_ID, rawContactId);
    337         values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
    338         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    339         return resultUri;
    340     }
    341 
    342     protected Uri insertPhoto(long rawContactId) {
    343         ContentValues values = new ContentValues();
    344         values.put(Data.RAW_CONTACT_ID, rawContactId);
    345         values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    346         values.put(Photo.PHOTO, loadTestPhoto());
    347         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    348         return resultUri;
    349     }
    350 
    351     protected Uri insertPhoto(long rawContactId, int resourceId) {
    352         ContentValues values = new ContentValues();
    353         values.put(Data.RAW_CONTACT_ID, rawContactId);
    354         values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    355         values.put(Photo.PHOTO, loadPhotoFromResource(resourceId, PhotoSize.ORIGINAL));
    356         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    357         return resultUri;
    358     }
    359 
    360     protected Uri insertGroupMembership(long rawContactId, String sourceId) {
    361         ContentValues values = new ContentValues();
    362         values.put(Data.RAW_CONTACT_ID, rawContactId);
    363         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
    364         values.put(GroupMembership.GROUP_SOURCE_ID, sourceId);
    365         return mResolver.insert(Data.CONTENT_URI, values);
    366     }
    367 
    368     protected Uri insertGroupMembership(long rawContactId, Long groupId) {
    369         ContentValues values = new ContentValues();
    370         values.put(Data.RAW_CONTACT_ID, rawContactId);
    371         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
    372         values.put(GroupMembership.GROUP_ROW_ID, groupId);
    373         return mResolver.insert(Data.CONTENT_URI, values);
    374     }
    375 
    376     public void removeGroupMemberships(long rawContactId) {
    377         mResolver.delete(Data.CONTENT_URI,
    378                 Data.MIMETYPE + "=? AND " + GroupMembership.RAW_CONTACT_ID + "=?",
    379                 new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(rawContactId) });
    380     }
    381 
    382     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
    383             int presence, String status, int chatMode) {
    384         return insertStatusUpdate(protocol, customProtocol, handle, presence, status, chatMode,
    385                 false);
    386     }
    387 
    388     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
    389             int presence, String status, int chatMode, boolean isUserProfile) {
    390         return insertStatusUpdate(protocol, customProtocol, handle, presence, status, 0, chatMode,
    391                 isUserProfile);
    392     }
    393 
    394     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
    395             int presence, String status, long timestamp, int chatMode, boolean isUserProfile) {
    396         ContentValues values = new ContentValues();
    397         values.put(StatusUpdates.PROTOCOL, protocol);
    398         values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol);
    399         values.put(StatusUpdates.IM_HANDLE, handle);
    400         return insertStatusUpdate(values, presence, status, timestamp, chatMode, isUserProfile);
    401     }
    402 
    403     protected Uri insertStatusUpdate(
    404             long dataId, int presence, String status, long timestamp, int chatMode) {
    405         return insertStatusUpdate(dataId, presence, status, timestamp, chatMode, false);
    406     }
    407 
    408     protected Uri insertStatusUpdate(
    409             long dataId, int presence, String status, long timestamp, int chatMode,
    410             boolean isUserProfile) {
    411         ContentValues values = new ContentValues();
    412         values.put(StatusUpdates.DATA_ID, dataId);
    413         return insertStatusUpdate(values, presence, status, timestamp, chatMode, isUserProfile);
    414     }
    415 
    416     private Uri insertStatusUpdate(
    417             ContentValues values, int presence, String status, long timestamp, int chatMode,
    418             boolean isUserProfile) {
    419         if (presence != 0) {
    420             values.put(StatusUpdates.PRESENCE, presence);
    421             values.put(StatusUpdates.CHAT_CAPABILITY, chatMode);
    422         }
    423         if (status != null) {
    424             values.put(StatusUpdates.STATUS, status);
    425         }
    426         if (timestamp != 0) {
    427             values.put(StatusUpdates.STATUS_TIMESTAMP, timestamp);
    428         }
    429 
    430         Uri insertUri = isUserProfile
    431                 ? StatusUpdates.PROFILE_CONTENT_URI
    432                 : StatusUpdates.CONTENT_URI;
    433         Uri resultUri = mResolver.insert(insertUri, values);
    434         return resultUri;
    435     }
    436 
    437     protected Uri insertStreamItem(long rawContactId, ContentValues values, Account account) {
    438         return mResolver.insert(
    439                 TestUtil.maybeAddAccountQueryParameters(Uri.withAppendedPath(
    440                         ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
    441                         RawContacts.StreamItems.CONTENT_DIRECTORY), account),
    442                 values);
    443     }
    444 
    445     protected Uri insertStreamItemPhoto(long streamItemId, ContentValues values, Account account) {
    446         return mResolver.insert(
    447                 TestUtil.maybeAddAccountQueryParameters(Uri.withAppendedPath(
    448                         ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
    449                         StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), account),
    450                 values);
    451     }
    452 
    453     protected Uri insertImHandle(long rawContactId, int protocol, String customProtocol,
    454             String handle) {
    455         ContentValues values = new ContentValues();
    456         values.put(Data.RAW_CONTACT_ID, rawContactId);
    457         values.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
    458         values.put(Im.PROTOCOL, protocol);
    459         values.put(Im.CUSTOM_PROTOCOL, customProtocol);
    460         values.put(Im.DATA, handle);
    461         values.put(Im.TYPE, Im.TYPE_HOME);
    462 
    463         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    464         return resultUri;
    465     }
    466 
    467     protected Uri insertEvent(long rawContactId, int type, String date) {
    468         ContentValues values = new ContentValues();
    469         values.put(Data.RAW_CONTACT_ID, rawContactId);
    470         values.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
    471         values.put(Event.TYPE, type);
    472         values.put(Event.START_DATE, date);
    473         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    474         return resultUri;
    475     }
    476 
    477     protected Uri insertNote(long rawContactId, String note) {
    478         ContentValues values = new ContentValues();
    479         values.put(Data.RAW_CONTACT_ID, rawContactId);
    480         values.put(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
    481         values.put(Note.NOTE, note);
    482         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    483         return resultUri;
    484     }
    485 
    486     protected Uri insertIdentity(long rawContactId, String identity, String namespace) {
    487         ContentValues values = new ContentValues();
    488         values.put(Data.RAW_CONTACT_ID, rawContactId);
    489         values.put(Data.MIMETYPE, Identity.CONTENT_ITEM_TYPE);
    490         values.put(Identity.NAMESPACE, namespace);
    491         values.put(Identity.IDENTITY, identity);
    492 
    493         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
    494         return resultUri;
    495     }
    496 
    497     protected void setContactAccount(long rawContactId, String accountType, String accountName) {
    498         ContentValues values = new ContentValues();
    499         values.put(RawContacts.ACCOUNT_TYPE, accountType);
    500         values.put(RawContacts.ACCOUNT_NAME, accountName);
    501 
    502         mResolver.update(ContentUris.withAppendedId(
    503                 RawContacts.CONTENT_URI, rawContactId), values, null, null);
    504     }
    505 
    506     protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
    507         ContentValues values = new ContentValues();
    508         values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
    509         values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
    510         values.put(AggregationExceptions.TYPE, type);
    511         assertEquals(1, mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null));
    512     }
    513 
    514     protected void markInvisible(long contactId) {
    515         // There's no api for this, so we just tweak the DB directly.
    516         SQLiteDatabase db = ((ContactsProvider2) getProvider()).getDatabaseHelper()
    517                 .getWritableDatabase();
    518         db.execSQL("DELETE FROM " + Tables.DEFAULT_DIRECTORY +
    519                 " WHERE " + BaseColumns._ID + "=" + contactId);
    520     }
    521 
    522     protected Cursor queryRawContact(long rawContactId) {
    523         return mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
    524                 null, null, null, null);
    525     }
    526 
    527     protected Cursor queryContact(long contactId) {
    528         return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
    529                 null, null, null, null);
    530     }
    531 
    532     protected Cursor queryContact(long contactId, String[] projection) {
    533         return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
    534                 projection, null, null, null);
    535     }
    536 
    537     protected Uri getContactUriForRawContact(long rawContactId) {
    538         return ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId));
    539     }
    540 
    541     protected long queryContactId(long rawContactId) {
    542         Cursor c = queryRawContact(rawContactId);
    543         assertTrue(c.moveToFirst());
    544         long contactId = c.getLong(c.getColumnIndex(RawContacts.CONTACT_ID));
    545         c.close();
    546         return contactId;
    547     }
    548 
    549     protected long queryPhotoId(long contactId) {
    550         Cursor c = queryContact(contactId);
    551         assertTrue(c.moveToFirst());
    552         long photoId = c.getInt(c.getColumnIndex(Contacts.PHOTO_ID));
    553         c.close();
    554         return photoId;
    555     }
    556 
    557     protected long queryPhotoFileId(long contactId) {
    558         return getStoredLongValue(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
    559                 Contacts.PHOTO_FILE_ID);
    560     }
    561 
    562     protected boolean queryRawContactIsStarred(long rawContactId) {
    563         Cursor c = queryRawContact(rawContactId);
    564         try {
    565             assertTrue(c.moveToFirst());
    566             return c.getLong(c.getColumnIndex(RawContacts.STARRED)) != 0;
    567         } finally {
    568             c.close();
    569         }
    570     }
    571 
    572     protected String queryDisplayName(long contactId) {
    573         Cursor c = queryContact(contactId);
    574         assertTrue(c.moveToFirst());
    575         String displayName = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
    576         c.close();
    577         return displayName;
    578     }
    579 
    580     protected String queryLookupKey(long contactId) {
    581         Cursor c = queryContact(contactId);
    582         assertTrue(c.moveToFirst());
    583         String lookupKey = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY));
    584         c.close();
    585         return lookupKey;
    586     }
    587 
    588     protected void assertAggregated(long rawContactId1, long rawContactId2) {
    589         long contactId1 = queryContactId(rawContactId1);
    590         long contactId2 = queryContactId(rawContactId2);
    591         assertTrue(contactId1 == contactId2);
    592     }
    593 
    594     protected void assertAggregated(long rawContactId1, long rawContactId2,
    595             String expectedDisplayName) {
    596         long contactId1 = queryContactId(rawContactId1);
    597         long contactId2 = queryContactId(rawContactId2);
    598         assertTrue(contactId1 == contactId2);
    599 
    600         String displayName = queryDisplayName(contactId1);
    601         assertEquals(expectedDisplayName, displayName);
    602     }
    603 
    604     protected void assertNotAggregated(long rawContactId1, long rawContactId2) {
    605         long contactId1 = queryContactId(rawContactId1);
    606         long contactId2 = queryContactId(rawContactId2);
    607         assertTrue(contactId1 != contactId2);
    608     }
    609 
    610     protected void assertStructuredName(long rawContactId, String prefix, String givenName,
    611             String middleName, String familyName, String suffix) {
    612         Uri uri = Uri.withAppendedPath(
    613                 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
    614                 RawContacts.Data.CONTENT_DIRECTORY);
    615 
    616         final String[] projection = new String[] {
    617                 StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME,
    618                 StructuredName.FAMILY_NAME, StructuredName.SUFFIX
    619         };
    620 
    621         Cursor c = mResolver.query(uri, projection, Data.MIMETYPE + "='"
    622                 + StructuredName.CONTENT_ITEM_TYPE + "'", null, null);
    623 
    624         assertTrue(c.moveToFirst());
    625         assertEquals(prefix, c.getString(0));
    626         assertEquals(givenName, c.getString(1));
    627         assertEquals(middleName, c.getString(2));
    628         assertEquals(familyName, c.getString(3));
    629         assertEquals(suffix, c.getString(4));
    630         c.close();
    631     }
    632 
    633     protected long assertSingleGroup(Long rowId, Account account, String sourceId, String title) {
    634         Cursor c = mResolver.query(Groups.CONTENT_URI, null, null, null, null);
    635         try {
    636             assertTrue(c.moveToNext());
    637             long actualRowId = assertGroup(c, rowId, account, sourceId, title);
    638             assertFalse(c.moveToNext());
    639             return actualRowId;
    640         } finally {
    641             c.close();
    642         }
    643     }
    644 
    645     protected long assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId,
    646             String sourceId) {
    647         Cursor c = mResolver.query(ContactsContract.Data.CONTENT_URI, null, null, null, null);
    648         try {
    649             assertTrue(c.moveToNext());
    650             long actualRowId = assertGroupMembership(c, rowId, rawContactId, groupRowId, sourceId);
    651             assertFalse(c.moveToNext());
    652             return actualRowId;
    653         } finally {
    654             c.close();
    655         }
    656     }
    657 
    658     protected long assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId,
    659             String sourceId) {
    660         assertNullOrEquals(c, rowId, Data._ID);
    661         assertNullOrEquals(c, rawContactId, GroupMembership.RAW_CONTACT_ID);
    662         assertNullOrEquals(c, groupRowId, GroupMembership.GROUP_ROW_ID);
    663         assertNullOrEquals(c, sourceId, GroupMembership.GROUP_SOURCE_ID);
    664         return c.getLong(c.getColumnIndexOrThrow("_id"));
    665     }
    666 
    667     protected long assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title) {
    668         assertNullOrEquals(c, rowId, Groups._ID);
    669         assertNullOrEquals(c, account);
    670         assertNullOrEquals(c, sourceId, Groups.SOURCE_ID);
    671         assertNullOrEquals(c, title, Groups.TITLE);
    672         return c.getLong(c.getColumnIndexOrThrow("_id"));
    673     }
    674 
    675     private void assertNullOrEquals(Cursor c, Account account) {
    676         if (account == NO_ACCOUNT) {
    677             return;
    678         }
    679         if (account == null) {
    680             assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME)));
    681             assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE)));
    682         } else {
    683             assertEquals(account.name, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME)));
    684             assertEquals(account.type, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE)));
    685         }
    686     }
    687 
    688     private void assertNullOrEquals(Cursor c, Long value, String columnName) {
    689         if (value != NO_LONG) {
    690             if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName)));
    691             else assertEquals((long) value, c.getLong(c.getColumnIndexOrThrow(columnName)));
    692         }
    693     }
    694 
    695     private void assertNullOrEquals(Cursor c, String value, String columnName) {
    696         if (value != NO_STRING) {
    697             if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName)));
    698             else assertEquals(value, c.getString(c.getColumnIndexOrThrow(columnName)));
    699         }
    700     }
    701 
    702     protected void assertDataRow(ContentValues actual, String expectedMimetype,
    703             Object... expectedArguments) {
    704         assertEquals(actual.toString(), expectedMimetype, actual.getAsString(Data.MIMETYPE));
    705         for (int i = 0; i < expectedArguments.length; i += 2) {
    706             String columnName = (String) expectedArguments[i];
    707             Object expectedValue = expectedArguments[i + 1];
    708             if (expectedValue instanceof Uri) {
    709                 expectedValue = ContentUris.parseId((Uri) expectedValue);
    710             }
    711             if (expectedValue == null) {
    712                 assertNull(actual.toString(), actual.get(columnName));
    713             }
    714             if (expectedValue instanceof Long) {
    715                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    716                         expectedValue, actual.getAsLong(columnName));
    717             } else if (expectedValue instanceof Integer) {
    718                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    719                         expectedValue, actual.getAsInteger(columnName));
    720             } else if (expectedValue instanceof String) {
    721                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    722                         expectedValue, actual.getAsString(columnName));
    723             } else {
    724                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
    725                         expectedValue, actual.get(columnName));
    726             }
    727         }
    728     }
    729 
    730     protected void assertNoRowsAndClose(Cursor c) {
    731         try {
    732             assertFalse(c.moveToNext());
    733         } finally {
    734             c.close();
    735         }
    736     }
    737 
    738     protected static class IdComparator implements Comparator<ContentValues> {
    739         @Override
    740         public int compare(ContentValues o1, ContentValues o2) {
    741             long id1 = o1.getAsLong(ContactsContract.Data._ID);
    742             long id2 = o2.getAsLong(ContactsContract.Data._ID);
    743             if (id1 == id2) return 0;
    744             return (id1 < id2) ? -1 : 1;
    745         }
    746     }
    747 
    748     protected ContentValues[] asSortedContentValuesArray(
    749             ArrayList<Entity.NamedContentValues> subValues) {
    750         ContentValues[] result = new ContentValues[subValues.size()];
    751         int i = 0;
    752         for (Entity.NamedContentValues subValue : subValues) {
    753             result[i] = subValue.values;
    754             i++;
    755         }
    756         Arrays.sort(result, new IdComparator());
    757         return result;
    758     }
    759 
    760     protected void assertDirty(Uri uri, boolean state) {
    761         Cursor c = mResolver.query(uri, new String[]{"dirty"}, null, null, null);
    762         assertTrue(c.moveToNext());
    763         assertEquals(state, c.getLong(0) != 0);
    764         assertFalse(c.moveToNext());
    765         c.close();
    766     }
    767 
    768     protected long getVersion(Uri uri) {
    769         Cursor c = mResolver.query(uri, new String[]{"version"}, null, null, null);
    770         assertTrue(c.moveToNext());
    771         long version = c.getLong(0);
    772         assertFalse(c.moveToNext());
    773         c.close();
    774         return version;
    775     }
    776 
    777     protected void clearDirty(Uri uri) {
    778         ContentValues values = new ContentValues();
    779         values.put("dirty", 0);
    780         mResolver.update(uri, values, null, null);
    781     }
    782 
    783     protected void storeValue(Uri contentUri, long id, String column, String value) {
    784         storeValue(ContentUris.withAppendedId(contentUri, id), column, value);
    785     }
    786 
    787     protected void storeValue(Uri contentUri, String column, String value) {
    788         ContentValues values = new ContentValues();
    789         values.put(column, value);
    790 
    791         mResolver.update(contentUri, values, null, null);
    792     }
    793 
    794     protected void storeValue(Uri contentUri, long id, String column, long value) {
    795         storeValue(ContentUris.withAppendedId(contentUri, id), column, value);
    796     }
    797 
    798     protected void storeValue(Uri contentUri, String column, long value) {
    799         ContentValues values = new ContentValues();
    800         values.put(column, value);
    801 
    802         mResolver.update(contentUri, values, null, null);
    803     }
    804 
    805     protected void assertStoredValue(Uri contentUri, long id, String column, Object expectedValue) {
    806         assertStoredValue(ContentUris.withAppendedId(contentUri, id), column, expectedValue);
    807     }
    808 
    809     protected void assertStoredValue(Uri rowUri, String column, Object expectedValue) {
    810         String value = getStoredValue(rowUri, column);
    811         if (expectedValue == null) {
    812             assertNull("Column value " + column, value);
    813         } else {
    814             assertEquals("Column value " + column, String.valueOf(expectedValue), value);
    815         }
    816     }
    817 
    818     protected void assertStoredValue(Uri rowUri, String selection, String[] selectionArgs,
    819             String column, Object expectedValue) {
    820         String value = getStoredValue(rowUri, selection, selectionArgs, column);
    821         if (expectedValue == null) {
    822             assertNull("Column value " + column, value);
    823         } else {
    824             assertEquals("Column value " + column, String.valueOf(expectedValue), value);
    825         }
    826     }
    827 
    828     protected String getStoredValue(Uri rowUri, String column) {
    829         return getStoredValue(rowUri, null, null, column);
    830     }
    831 
    832     protected String getStoredValue(Uri uri, String selection, String[] selectionArgs,
    833             String column) {
    834         String value = null;
    835         Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null);
    836         try {
    837             assertEquals("Record count for " + uri, 1, c.getCount());
    838 
    839             if (c.moveToFirst()) {
    840                 value = c.getString(c.getColumnIndex(column));
    841             }
    842         } finally {
    843             c.close();
    844         }
    845         return value;
    846     }
    847 
    848     protected Long getStoredLongValue(Uri uri, String selection, String[] selectionArgs,
    849             String column) {
    850         Long value = null;
    851         Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null);
    852         try {
    853             assertEquals("Record count", 1, c.getCount());
    854 
    855             if (c.moveToFirst()) {
    856                 value = c.getLong(c.getColumnIndex(column));
    857             }
    858         } finally {
    859             c.close();
    860         }
    861         return value;
    862     }
    863 
    864     protected Long getStoredLongValue(Uri uri, String column) {
    865         return getStoredLongValue(uri, null, null, column);
    866     }
    867 
    868     protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) {
    869         assertStoredValues(rowUri, null, null, expectedValues);
    870     }
    871 
    872     protected void assertStoredValues(Uri rowUri, ContentValues... expectedValues) {
    873         assertStoredValues(rowUri, null, null, expectedValues);
    874     }
    875 
    876     protected void assertStoredValues(Uri rowUri, String selection, String[] selectionArgs,
    877             ContentValues expectedValues) {
    878         Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null);
    879         try {
    880             assertEquals("Record count", 1, c.getCount());
    881             c.moveToFirst();
    882             assertCursorValues(c, expectedValues);
    883         } catch (Error e) {
    884             TestUtils.dumpCursor(c);
    885             throw e;
    886         } finally {
    887             c.close();
    888         }
    889     }
    890 
    891     protected void assertContainsValues(Uri rowUri, ContentValues expectedValues) {
    892         Cursor c = mResolver.query(rowUri, null, null, null, null);
    893         try {
    894             assertEquals("Record count", 1, c.getCount());
    895             c.moveToFirst();
    896             assertCursorValuesPartialMatch(c, expectedValues);
    897         } catch (Error e) {
    898             TestUtils.dumpCursor(c);
    899             throw e;
    900         } finally {
    901             c.close();
    902         }
    903     }
    904 
    905     protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues) {
    906         assertStoredValuesWithProjection(rowUri, new ContentValues[] {expectedValues});
    907     }
    908 
    909     protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues... expectedValues) {
    910         assertTrue("Need at least one ContentValues for this test", expectedValues.length > 0);
    911         Cursor c = mResolver.query(rowUri, buildProjection(expectedValues[0]), null, null, null);
    912         try {
    913             assertEquals("Record count", expectedValues.length, c.getCount());
    914             c.moveToFirst();
    915             assertCursorValues(c, expectedValues);
    916         } catch (Error e) {
    917             TestUtils.dumpCursor(c);
    918             throw e;
    919         } finally {
    920             c.close();
    921         }
    922     }
    923 
    924     protected void assertStoredValues(
    925             Uri rowUri, String selection, String[] selectionArgs, ContentValues... expectedValues) {
    926         assertStoredValues(mResolver.query(rowUri, null, selection, selectionArgs, null),
    927                 expectedValues);
    928     }
    929 
    930     private void assertStoredValues(Cursor c, ContentValues... expectedValues) {
    931         try {
    932             assertEquals("Record count", expectedValues.length, c.getCount());
    933             assertCursorValues(c, expectedValues);
    934         } catch (Error e) {
    935             TestUtils.dumpCursor(c);
    936             throw e;
    937         } finally {
    938             c.close();
    939         }
    940     }
    941 
    942     /**
    943      * A variation of {@link #assertStoredValues}, but it queries directly to the DB.
    944      */
    945     protected void assertStoredValuesDb(
    946             String sql, String[] selectionArgs, ContentValues... expectedValues) {
    947         SQLiteDatabase db = ((ContactsProvider2) getProvider()).getDatabaseHelper()
    948                 .getReadableDatabase();
    949         assertStoredValues(db.rawQuery(sql, selectionArgs), expectedValues);
    950     }
    951 
    952     protected void assertStoredValuesOrderly(Uri rowUri, ContentValues... expectedValues) {
    953         assertStoredValuesOrderly(rowUri, null, null, expectedValues);
    954     }
    955 
    956     protected void assertStoredValuesOrderly(Uri rowUri, String selection,
    957             String[] selectionArgs, ContentValues... expectedValues) {
    958         Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null);
    959         try {
    960             assertEquals("Record count", expectedValues.length, c.getCount());
    961             assertCursorValuesOrderly(c, expectedValues);
    962         } catch (Error e) {
    963             TestUtils.dumpCursor(c);
    964             throw e;
    965         } finally {
    966             c.close();
    967         }
    968     }
    969 
    970     /**
    971      * Constructs a selection (where clause) out of all supplied values, uses it
    972      * to query the provider and verifies that a single row is returned and it
    973      * has the same values as requested.
    974      */
    975     protected void assertSelection(Uri uri, ContentValues values, String idColumn, long id) {
    976         assertSelection(uri, values, idColumn, id, null);
    977     }
    978 
    979     public void assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn,
    980             long id) {
    981         assertSelection(uri, values, idColumn, id, buildProjection(values));
    982     }
    983 
    984     private void assertSelection(Uri uri, ContentValues values, String idColumn, long id,
    985             String[] projection) {
    986         StringBuilder sb = new StringBuilder();
    987         ArrayList<String> selectionArgs = new ArrayList<String>(values.size());
    988         if (idColumn != null) {
    989             sb.append(idColumn).append("=").append(id);
    990         }
    991         Set<Map.Entry<String, Object>> entries = values.valueSet();
    992         for (Map.Entry<String, Object> entry : entries) {
    993             String column = entry.getKey();
    994             Object value = entry.getValue();
    995             if (sb.length() != 0) {
    996                 sb.append(" AND ");
    997             }
    998             sb.append(column);
    999             if (value == null) {
   1000                 sb.append(" IS NULL");
   1001             } else {
   1002                 sb.append("=?");
   1003                 selectionArgs.add(String.valueOf(value));
   1004             }
   1005         }
   1006 
   1007         Cursor c = mResolver.query(uri, projection, sb.toString(), selectionArgs.toArray(new String[0]),
   1008                 null);
   1009         try {
   1010             assertEquals("Record count", 1, c.getCount());
   1011             c.moveToFirst();
   1012             assertCursorValues(c, values);
   1013         } catch (Error e) {
   1014             TestUtils.dumpCursor(c);
   1015             throw e;
   1016         } finally {
   1017             c.close();
   1018         }
   1019     }
   1020 
   1021     protected void assertCursorValue(Cursor cursor, String column, Object expectedValue) {
   1022         String actualValue = cursor.getString(cursor.getColumnIndex(column));
   1023         assertEquals("Column " + column, String.valueOf(expectedValue),
   1024                 String.valueOf(actualValue));
   1025     }
   1026 
   1027     protected void assertCursorValues(Cursor cursor, ContentValues expectedValues) {
   1028         StringBuilder message = new StringBuilder();
   1029         boolean result = equalsWithExpectedValues(cursor, expectedValues, message);
   1030         assertTrue(message.toString(), result);
   1031     }
   1032 
   1033     protected void assertCursorValuesPartialMatch(Cursor cursor, ContentValues expectedValues) {
   1034         StringBuilder message = new StringBuilder();
   1035         boolean result = expectedValuePartiallyMatches(cursor, expectedValues, message);
   1036         assertTrue(message.toString(), result);
   1037     }
   1038 
   1039     protected void assertCursorHasAnyRecordMatch(Cursor cursor, ContentValues expectedValues) {
   1040         final StringBuilder message = new StringBuilder();
   1041         boolean found = false;
   1042         cursor.moveToPosition(-1);
   1043         while (cursor.moveToNext()) {
   1044             message.setLength(0);
   1045             final int pos = cursor.getPosition();
   1046             found = equalsWithExpectedValues(cursor, expectedValues, message);
   1047             if (found) {
   1048                 break;
   1049             }
   1050         }
   1051         assertTrue("Expected values can not be found " + expectedValues + "," + message.toString(),
   1052                 found);
   1053     }
   1054 
   1055     protected void assertCursorValues(Cursor cursor, ContentValues... expectedValues) {
   1056         StringBuilder message = new StringBuilder();
   1057 
   1058         // In case if expectedValues contains multiple identical values, remember which cursor
   1059         // rows are "consumed" to prevent multiple ContentValues from hitting the same row.
   1060         final BitSet used = new BitSet(cursor.getCount());
   1061 
   1062         for (ContentValues v : expectedValues) {
   1063             boolean found = false;
   1064             cursor.moveToPosition(-1);
   1065             while (cursor.moveToNext()) {
   1066                 final int pos = cursor.getPosition();
   1067                 if (used.get(pos)) continue;
   1068                 found = equalsWithExpectedValues(cursor, v, message);
   1069                 if (found) {
   1070                     used.set(pos);
   1071                     break;
   1072                 }
   1073             }
   1074             assertTrue("Expected values can not be found " + v + "," + message.toString(), found);
   1075         }
   1076     }
   1077 
   1078     private void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) {
   1079         StringBuilder message = new StringBuilder();
   1080         cursor.moveToPosition(-1);
   1081         for (ContentValues v : expectedValues) {
   1082             assertTrue(cursor.moveToNext());
   1083             boolean ok = equalsWithExpectedValues(cursor, v, message);
   1084             assertTrue("ContentValues didn't match.  Pos=" + cursor.getPosition() + ", values=" +
   1085                     v + message.toString(), ok);
   1086         }
   1087     }
   1088 
   1089     private boolean expectedValuePartiallyMatches(Cursor cursor, ContentValues expectedValues,
   1090             StringBuilder msgBuffer) {
   1091         for (String column : expectedValues.keySet()) {
   1092             int index = cursor.getColumnIndex(column);
   1093             if (index == -1) {
   1094                 msgBuffer.append(" No such column: ").append(column);
   1095                 return false;
   1096             }
   1097             String expectedValue = expectedValues.getAsString(column);
   1098             String value = cursor.getString(cursor.getColumnIndex(column));
   1099             if (value != null && !value.contains(expectedValue)) {
   1100                 msgBuffer.append(" Column value ").append(column).append(" expected to contain <")
   1101                         .append(expectedValue).append(">, but was <").append(value).append('>');
   1102                 return false;
   1103             }
   1104         }
   1105         return true;
   1106     }
   1107 
   1108     private boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
   1109             StringBuilder msgBuffer) {
   1110         for (String column : expectedValues.keySet()) {
   1111             int index = cursor.getColumnIndex(column);
   1112             if (index == -1) {
   1113                 msgBuffer.append(" No such column: ").append(column);
   1114                 return false;
   1115             }
   1116             Object expectedValue = expectedValues.get(column);
   1117             String value;
   1118             if (expectedValue instanceof byte[]) {
   1119                 expectedValue = Hex.encodeHex((byte[])expectedValue, false);
   1120                 value = Hex.encodeHex(cursor.getBlob(index), false);
   1121             } else {
   1122                 expectedValue = expectedValues.getAsString(column);
   1123                 value = cursor.getString(cursor.getColumnIndex(column));
   1124             }
   1125             if (expectedValue != null && !expectedValue.equals(value) || value != null
   1126                     && !value.equals(expectedValue)) {
   1127                 msgBuffer
   1128                         .append(" Column value ")
   1129                         .append(column)
   1130                         .append(" expected <")
   1131                         .append(expectedValue)
   1132                         .append(">, but was <")
   1133                         .append(value)
   1134                         .append('>');
   1135                 return false;
   1136             }
   1137         }
   1138         return true;
   1139     }
   1140 
   1141     private static final String[] DATA_USAGE_PROJECTION =
   1142             new String[] {Data.DATA1, Data.TIMES_USED, Data.LAST_TIME_USED};
   1143 
   1144     protected void assertDataUsageCursorContains(Uri uri, String data1, int timesUsed,
   1145             int lastTimeUsed) {
   1146         final Cursor cursor = mResolver.query(uri, DATA_USAGE_PROJECTION, null, null,
   1147                 null);
   1148         try {
   1149             assertCursorHasAnyRecordMatch(cursor, cv(Data.DATA1, data1, Data.TIMES_USED, timesUsed,
   1150                     Data.LAST_TIME_USED, lastTimeUsed));
   1151         } finally {
   1152             cursor.close();
   1153         }
   1154     }
   1155 
   1156     private String[] buildProjection(ContentValues values) {
   1157         String[] projection = new String[values.size()];
   1158         Iterator<Entry<String, Object>> iter = values.valueSet().iterator();
   1159         for (int i = 0; i < projection.length; i++) {
   1160             projection[i] = iter.next().getKey();
   1161         }
   1162         return projection;
   1163     }
   1164 
   1165     protected int getCount(Uri uri, String selection, String[] selectionArgs) {
   1166         Cursor c = mResolver.query(uri, null, selection, selectionArgs, null);
   1167         try {
   1168             return c.getCount();
   1169         } finally {
   1170             c.close();
   1171         }
   1172     }
   1173 
   1174     public static void dump(ContentResolver resolver, boolean aggregatedOnly) {
   1175         String[] projection = new String[] {
   1176                 Contacts._ID,
   1177                 Contacts.DISPLAY_NAME
   1178         };
   1179         String selection = null;
   1180         if (aggregatedOnly) {
   1181             selection = Contacts._ID
   1182                     + " IN (SELECT contact_id" +
   1183                     		" FROM raw_contacts GROUP BY contact_id HAVING count(*) > 1)";
   1184         }
   1185 
   1186         Cursor c = resolver.query(Contacts.CONTENT_URI, projection, selection, null,
   1187                 Contacts.DISPLAY_NAME);
   1188         while(c.moveToNext()) {
   1189             long contactId = c.getLong(0);
   1190             Log.i("Contact   ", String.format("%5d %s", contactId, c.getString(1)));
   1191             dumpRawContacts(resolver, contactId);
   1192             Log.i("          ", ".");
   1193         }
   1194         c.close();
   1195     }
   1196 
   1197     private static void dumpRawContacts(ContentResolver resolver, long contactId) {
   1198         String[] projection = new String[] {
   1199                 RawContacts._ID,
   1200         };
   1201         Cursor c = resolver.query(RawContacts.CONTENT_URI, projection, RawContacts.CONTACT_ID + "="
   1202                 + contactId, null, null);
   1203         while(c.moveToNext()) {
   1204             long rawContactId = c.getLong(0);
   1205             Log.i("RawContact", String.format("      %-5d", rawContactId));
   1206             dumpData(resolver, rawContactId);
   1207         }
   1208         c.close();
   1209     }
   1210 
   1211     private static void dumpData(ContentResolver resolver, long rawContactId) {
   1212         String[] projection = new String[] {
   1213                 Data.MIMETYPE,
   1214                 Data.DATA1,
   1215                 Data.DATA2,
   1216                 Data.DATA3,
   1217         };
   1218         Cursor c = resolver.query(Data.CONTENT_URI, projection, Data.RAW_CONTACT_ID + "="
   1219                 + rawContactId, null, Data.MIMETYPE);
   1220         while(c.moveToNext()) {
   1221             String mimetype = c.getString(0);
   1222             if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
   1223                 Log.i("Photo     ", "");
   1224             } else {
   1225                 mimetype = mimetype.substring(mimetype.indexOf('/') + 1);
   1226                 Log.i("Data      ", String.format("            %-10s %s,%s,%s", mimetype,
   1227                         c.getString(1), c.getString(2), c.getString(3)));
   1228             }
   1229         }
   1230         c.close();
   1231     }
   1232 
   1233     protected void assertNetworkNotified(boolean expected) {
   1234         assertEquals(expected, (getContactsProvider()).isNetworkNotified());
   1235     }
   1236 
   1237     protected void assertProjection(Uri uri, String[] expectedProjection) {
   1238         Cursor cursor = mResolver.query(uri, null, "0", null, null);
   1239         String[] actualProjection = cursor.getColumnNames();
   1240         MoreAsserts.assertEquals("Incorrect projection for URI: " + uri,
   1241                 Sets.newHashSet(expectedProjection), Sets.newHashSet(actualProjection));
   1242         cursor.close();
   1243     }
   1244 
   1245     protected void assertRowCount(int expectedCount, Uri uri, String selection, String[] args) {
   1246         Cursor cursor = mResolver.query(uri, null, selection, args, null);
   1247 
   1248         try {
   1249             assertEquals(expectedCount, cursor.getCount());
   1250         } catch (Error e) {
   1251             TestUtils.dumpCursor(cursor);
   1252             throw e;
   1253         } finally {
   1254             cursor.close();
   1255         }
   1256     }
   1257 
   1258     /**
   1259      * A contact in the database, and the attributes used to create it.  Construct using
   1260      * {@link GoldenContactBuilder#build()}.
   1261      */
   1262     public final class GoldenContact {
   1263 
   1264         private final long rawContactId;
   1265 
   1266         private final long contactId;
   1267 
   1268         private final String givenName;
   1269 
   1270         private final String familyName;
   1271 
   1272         private final String nickname;
   1273 
   1274         private final byte[] photo;
   1275 
   1276         private final String company;
   1277 
   1278         private final String title;
   1279 
   1280         private final String phone;
   1281 
   1282         private final String email;
   1283 
   1284         private GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId) {
   1285 
   1286             this.rawContactId = rawContactId;
   1287             this.contactId = contactId;
   1288             givenName = builder.givenName;
   1289             familyName = builder.familyName;
   1290             nickname = builder.nickname;
   1291             photo = builder.photo;
   1292             company = builder.company;
   1293             title = builder.title;
   1294             phone = builder.phone;
   1295             email = builder.email;
   1296         }
   1297 
   1298         public void delete() {
   1299             Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
   1300             mResolver.delete(rawContactUri, null, null);
   1301         }
   1302 
   1303         /**
   1304          * Returns the index of the contact in table "raw_contacts"
   1305          */
   1306         public long getRawContactId() {
   1307             return rawContactId;
   1308         }
   1309 
   1310         /**
   1311          * Returns the index of the contact in table "contacts"
   1312          */
   1313         public long getContactId() {
   1314             return contactId;
   1315         }
   1316 
   1317         /**
   1318          * Returns the lookup key for the contact.
   1319          */
   1320         public String getLookupKey() {
   1321             return queryLookupKey(contactId);
   1322         }
   1323 
   1324         /**
   1325          * Returns the contact's given name.
   1326          */
   1327         public String getGivenName() {
   1328             return givenName;
   1329         }
   1330 
   1331         /**
   1332          * Returns the contact's family name.
   1333          */
   1334         public String getFamilyName() {
   1335             return familyName;
   1336         }
   1337 
   1338         /**
   1339          * Returns the contact's nickname.
   1340          */
   1341         public String getNickname() {
   1342             return nickname;
   1343         }
   1344 
   1345         /**
   1346          * Return's the contact's photo
   1347          */
   1348         public byte[] getPhoto() {
   1349             return photo;
   1350         }
   1351 
   1352         /**
   1353          * Return's the company at which the contact works.
   1354          */
   1355         public String getCompany() {
   1356             return company;
   1357         }
   1358 
   1359         /**
   1360          * Returns the contact's job title.
   1361          */
   1362         public String getTitle() {
   1363             return title;
   1364         }
   1365 
   1366         /**
   1367          * Returns the contact's phone number
   1368          */
   1369         public String getPhone() {
   1370             return phone;
   1371         }
   1372 
   1373         /**
   1374          * Returns the contact's email address
   1375          */
   1376         public String getEmail() {
   1377             return email;
   1378         }
   1379      }
   1380 
   1381     /**
   1382      * Builds {@link GoldenContact} objects.  Unspecified boolean objects default to false.
   1383      * Unspecified String objects default to null.
   1384      */
   1385     public final class GoldenContactBuilder {
   1386 
   1387         private String givenName;
   1388 
   1389         private String familyName;
   1390 
   1391         private String nickname;
   1392 
   1393         private byte[] photo;
   1394 
   1395         private String company;
   1396 
   1397         private String title;
   1398 
   1399         private String phone;
   1400 
   1401         private String email;
   1402 
   1403         /**
   1404          * The contact's given and family names.
   1405          *
   1406          * TODO(dplotnikov): inline, or should we require them to set both names if they set either?
   1407          */
   1408         public GoldenContactBuilder name(String givenName, String familyName) {
   1409             return givenName(givenName).familyName(familyName);
   1410         }
   1411 
   1412         /**
   1413          * The contact's given name.
   1414          */
   1415         public GoldenContactBuilder givenName(String value) {
   1416             givenName = value;
   1417             return this;
   1418         }
   1419 
   1420         /**
   1421          * The contact's family name.
   1422          */
   1423         public GoldenContactBuilder familyName(String value) {
   1424             familyName = value;
   1425             return this;
   1426         }
   1427 
   1428         /**
   1429          * The contact's nickname.
   1430          */
   1431         public GoldenContactBuilder nickname(String value) {
   1432             nickname = value;
   1433             return this;
   1434         }
   1435 
   1436         /**
   1437          * The contact's photo.
   1438          */
   1439         public GoldenContactBuilder photo(byte[] value) {
   1440             photo = value;
   1441             return this;
   1442         }
   1443 
   1444         /**
   1445          * The company at which the contact works.
   1446          */
   1447         public GoldenContactBuilder company(String value) {
   1448             company = value;
   1449             return this;
   1450         }
   1451 
   1452         /**
   1453          * The contact's job title.
   1454          */
   1455         public GoldenContactBuilder title(String value) {
   1456             title = value;
   1457             return this;
   1458         }
   1459 
   1460         /**
   1461          * The contact's phone number.
   1462          */
   1463         public GoldenContactBuilder phone(String value) {
   1464             phone = value;
   1465             return this;
   1466         }
   1467 
   1468         /**
   1469          * The contact's email address; also sets their IM status to {@link StatusUpdates#OFFLINE}
   1470          * with a presence of "Coding for Android".
   1471          */
   1472         public GoldenContactBuilder email(String value) {
   1473             email = value;
   1474             return this;
   1475         }
   1476 
   1477         /**
   1478          * Builds the {@link GoldenContact} specified by this builder.
   1479          */
   1480         public GoldenContact build() {
   1481 
   1482             final long groupId = createGroup(mAccount, "gsid1", "title1");
   1483 
   1484             long rawContactId = RawContactUtil.createRawContact(mResolver);
   1485             insertGroupMembership(rawContactId, groupId);
   1486 
   1487             if (givenName != null || familyName != null) {
   1488                 DataUtil.insertStructuredName(mResolver, rawContactId, givenName, familyName);
   1489             }
   1490             if (nickname != null) {
   1491                 insertNickname(rawContactId, nickname);
   1492             }
   1493             if (photo != null) {
   1494                 insertPhoto(rawContactId);
   1495             }
   1496             if (company != null || title != null) {
   1497                 insertOrganization(rawContactId);
   1498             }
   1499             if (email != null) {
   1500                 insertEmail(rawContactId);
   1501             }
   1502             if (phone != null) {
   1503                 insertPhone(rawContactId);
   1504             }
   1505 
   1506             long contactId = queryContactId(rawContactId);
   1507 
   1508             return new GoldenContact(this, rawContactId, contactId);
   1509         }
   1510 
   1511         private void insertPhoto(long rawContactId) {
   1512             ContentValues values = new ContentValues();
   1513             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1514             values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
   1515             values.put(Photo.PHOTO, photo);
   1516             mResolver.insert(Data.CONTENT_URI, values);
   1517         }
   1518 
   1519         private void insertOrganization(long rawContactId) {
   1520 
   1521             ContentValues values = new ContentValues();
   1522             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1523             values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
   1524             values.put(Organization.TYPE, Organization.TYPE_WORK);
   1525             if (company != null) {
   1526                 values.put(Organization.COMPANY, company);
   1527             }
   1528             if (title != null) {
   1529                 values.put(Organization.TITLE, title);
   1530             }
   1531             mResolver.insert(Data.CONTENT_URI, values);
   1532         }
   1533 
   1534         private void insertEmail(long rawContactId) {
   1535 
   1536             ContentValues values = new ContentValues();
   1537             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1538             values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
   1539             values.put(Email.TYPE, Email.TYPE_WORK);
   1540             values.put(Email.DATA, "foo (at) acme.com");
   1541             mResolver.insert(Data.CONTENT_URI, values);
   1542 
   1543             int protocol = Im.PROTOCOL_GOOGLE_TALK;
   1544 
   1545             values.clear();
   1546             values.put(StatusUpdates.PROTOCOL, protocol);
   1547             values.put(StatusUpdates.IM_HANDLE, email);
   1548             values.put(StatusUpdates.IM_ACCOUNT, "foo");
   1549             values.put(StatusUpdates.PRESENCE_STATUS, StatusUpdates.OFFLINE);
   1550             values.put(StatusUpdates.CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
   1551             values.put(StatusUpdates.PRESENCE_CUSTOM_STATUS, "Coding for Android");
   1552             mResolver.insert(StatusUpdates.CONTENT_URI, values);
   1553         }
   1554 
   1555         private void insertPhone(long rawContactId) {
   1556             ContentValues values = new ContentValues();
   1557             values.put(Data.RAW_CONTACT_ID, rawContactId);
   1558             values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1559             values.put(Data.IS_PRIMARY, 1);
   1560             values.put(Phone.TYPE, Phone.TYPE_HOME);
   1561             values.put(Phone.NUMBER, phone);
   1562             mResolver.insert(Data.CONTENT_URI, values);
   1563         }
   1564     }
   1565 }
   1566