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