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