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