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