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