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