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