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