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