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