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