• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.contacts;
18 
19 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
20 
21 import android.accounts.Account;
22 import android.content.ContentProvider;
23 import android.content.ContentResolver;
24 import android.content.ContentUris;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Entity;
28 import android.content.res.Resources;
29 import android.database.Cursor;
30 import android.net.Uri;
31 import android.provider.ContactsContract;
32 import android.provider.ContactsContract.AggregationExceptions;
33 import android.provider.ContactsContract.Contacts;
34 import android.provider.ContactsContract.Data;
35 import android.provider.ContactsContract.Groups;
36 import android.provider.ContactsContract.RawContacts;
37 import android.provider.ContactsContract.Settings;
38 import android.provider.ContactsContract.StatusUpdates;
39 import android.provider.ContactsContract.CommonDataKinds.Email;
40 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
41 import android.provider.ContactsContract.CommonDataKinds.Im;
42 import android.provider.ContactsContract.CommonDataKinds.Nickname;
43 import android.provider.ContactsContract.CommonDataKinds.Organization;
44 import android.provider.ContactsContract.CommonDataKinds.Phone;
45 import android.provider.ContactsContract.CommonDataKinds.Photo;
46 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
47 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
48 import android.test.AndroidTestCase;
49 import android.test.mock.MockContentResolver;
50 import android.util.Log;
51 
52 import java.io.ByteArrayOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Comparator;
58 import java.util.Iterator;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.Map.Entry;
62 
63 /**
64  * A common superclass for {@link ContactsProvider2}-related tests.
65  */
66 public abstract class BaseContactsProvider2Test extends AndroidTestCase {
67 
68     protected static final String PACKAGE = "ContactsProvider2Test";
69     public static final String READ_ONLY_ACCOUNT_TYPE =
70             SynchronousContactsProvider2.READ_ONLY_ACCOUNT_TYPE;
71 
72     protected ContactsActor mActor;
73     protected MockContentResolver mResolver;
74     protected Account mAccount = new Account("account1", "account type1");
75     protected Account mAccountTwo = new Account("account2", "account type2");
76 
77     private byte[] mTestPhoto;
78 
79     protected final static Long NO_LONG = new Long(0);
80     protected final static String NO_STRING = new String("");
81     protected final static Account NO_ACCOUNT = new Account("a", "b");
82 
getProviderClass()83     protected Class<? extends ContentProvider> getProviderClass() {
84         return SynchronousContactsProvider2.class;
85     }
86 
getAuthority()87     protected String getAuthority() {
88         return ContactsContract.AUTHORITY;
89     }
90 
91     @Override
setUp()92     protected void setUp() throws Exception {
93         super.setUp();
94 
95         mActor = new ContactsActor(getContext(), PACKAGE_GREY, getProviderClass(), getAuthority());
96         mResolver = mActor.resolver;
97         if (mActor.provider instanceof SynchronousContactsProvider2) {
98             ((SynchronousContactsProvider2) mActor.provider)
99                     .getDatabaseHelper(mActor.context).wipeData();
100         }
101     }
102 
103     @Override
tearDown()104     protected void tearDown() throws Exception {
105         if (mActor.provider instanceof SynchronousContactsProvider2) {
106             ((SynchronousContactsProvider2) mActor.provider)
107                     .getDatabaseHelper(mActor.context).close();
108         }
109         super.tearDown();
110     }
111 
getMockContext()112     public Context getMockContext() {
113         return mActor.context;
114     }
115 
addAuthority(String authority)116     public void addAuthority(String authority) {
117         mActor.addAuthority(authority);
118     }
119 
addProvider(Class<? extends ContentProvider> providerClass, String authority)120     public ContentProvider addProvider(Class<? extends ContentProvider> providerClass,
121             String authority) throws Exception {
122         return mActor.addProvider(providerClass, authority);
123     }
124 
getProvider()125     public ContentProvider getProvider() {
126         return mActor.provider;
127     }
128 
maybeAddAccountQueryParameters(Uri uri, Account account)129     protected Uri maybeAddAccountQueryParameters(Uri uri, Account account) {
130         if (account == null) {
131             return uri;
132         }
133         return uri.buildUpon()
134                 .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
135                 .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
136                 .build();
137     }
138 
createRawContact()139     protected long createRawContact() {
140         return createRawContact(null);
141     }
142 
createRawContactWithName()143     protected long createRawContactWithName() {
144         return createRawContactWithName(null);
145     }
146 
createRawContactWithName(Account account)147     protected long createRawContactWithName(Account account) {
148         return createRawContactWithName("John", "Doe", account);
149     }
150 
createRawContactWithName(String firstName, String lastName)151     protected long createRawContactWithName(String firstName, String lastName) {
152         return createRawContactWithName(firstName, lastName, null);
153     }
154 
createRawContactWithName(String firstName, String lastName, Account account)155     protected long createRawContactWithName(String firstName, String lastName, Account account) {
156         long rawContactId = createRawContact(account);
157         insertStructuredName(rawContactId, firstName, lastName);
158         return rawContactId;
159     }
160 
setCallerIsSyncAdapter(Uri uri, Account account)161     protected Uri setCallerIsSyncAdapter(Uri uri, Account account) {
162         if (account == null) {
163             return uri;
164         }
165         final Uri.Builder builder = uri.buildUpon();
166         builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
167         builder.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
168         builder.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true");
169         return builder.build();
170     }
171 
createRawContact(Account account, String... extras)172     protected long createRawContact(Account account, String... extras) {
173         ContentValues values = new ContentValues();
174         for (int i = 0; i < extras.length; ) {
175             values.put(extras[i], extras[i + 1]);
176             i += 2;
177         }
178         final Uri uri = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account);
179         Uri contactUri = mResolver.insert(uri, values);
180         return ContentUris.parseId(contactUri);
181     }
182 
createGroup(Account account, String sourceId, String title)183     protected long createGroup(Account account, String sourceId, String title) {
184         return createGroup(account, sourceId, title, 1);
185     }
186 
createGroup(Account account, String sourceId, String title, int visible)187     protected long createGroup(Account account, String sourceId, String title, int visible) {
188         ContentValues values = new ContentValues();
189         values.put(Groups.SOURCE_ID, sourceId);
190         values.put(Groups.TITLE, title);
191         values.put(Groups.GROUP_VISIBLE, visible);
192         final Uri uri = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account);
193         return ContentUris.parseId(mResolver.insert(uri, values));
194     }
195 
createSettings(Account account, String shouldSync, String ungroupedVisible)196     protected void createSettings(Account account, String shouldSync, String ungroupedVisible) {
197         ContentValues values = new ContentValues();
198         values.put(Settings.ACCOUNT_NAME, account.name);
199         values.put(Settings.ACCOUNT_TYPE, account.type);
200         values.put(Settings.SHOULD_SYNC, shouldSync);
201         values.put(Settings.UNGROUPED_VISIBLE, ungroupedVisible);
202         mResolver.insert(Settings.CONTENT_URI, values);
203     }
204 
insertStructuredName(long rawContactId, String givenName, String familyName)205     protected Uri insertStructuredName(long rawContactId, String givenName, String familyName) {
206         ContentValues values = new ContentValues();
207         StringBuilder sb = new StringBuilder();
208         if (givenName != null) {
209             sb.append(givenName);
210         }
211         if (givenName != null && familyName != null) {
212             sb.append(" ");
213         }
214         if (familyName != null) {
215             sb.append(familyName);
216         }
217         values.put(StructuredName.DISPLAY_NAME, sb.toString());
218         values.put(StructuredName.GIVEN_NAME, givenName);
219         values.put(StructuredName.FAMILY_NAME, familyName);
220 
221         return insertStructuredName(rawContactId, values);
222     }
223 
insertStructuredName(long rawContactId, ContentValues values)224     protected Uri insertStructuredName(long rawContactId, ContentValues values) {
225         values.put(Data.RAW_CONTACT_ID, rawContactId);
226         values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
227         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
228         return resultUri;
229     }
230 
insertOrganization(long rawContactId, ContentValues values)231     protected Uri insertOrganization(long rawContactId, ContentValues values) {
232         return insertOrganization(rawContactId, values, false);
233     }
234 
insertOrganization(long rawContactId, ContentValues values, boolean primary)235     protected Uri insertOrganization(long rawContactId, ContentValues values, boolean primary) {
236         values.put(Data.RAW_CONTACT_ID, rawContactId);
237         values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
238         values.put(Organization.TYPE, Organization.TYPE_WORK);
239         if (primary) {
240             values.put(Data.IS_PRIMARY, 1);
241         }
242 
243         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
244         return resultUri;
245     }
246 
insertPhoneNumber(long rawContactId, String phoneNumber)247     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber) {
248         return insertPhoneNumber(rawContactId, phoneNumber, false);
249     }
250 
insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary)251     protected Uri insertPhoneNumber(long rawContactId, String phoneNumber, boolean primary) {
252         ContentValues values = new ContentValues();
253         values.put(Data.RAW_CONTACT_ID, rawContactId);
254         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
255         values.put(Phone.NUMBER, phoneNumber);
256         values.put(Phone.TYPE, Phone.TYPE_HOME);
257         if (primary) {
258             values.put(Data.IS_PRIMARY, 1);
259         }
260 
261         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
262         return resultUri;
263     }
264 
insertEmail(long rawContactId, String email)265     protected Uri insertEmail(long rawContactId, String email) {
266         return insertEmail(rawContactId, email, false);
267     }
268 
insertEmail(long rawContactId, String email, boolean primary)269     protected Uri insertEmail(long rawContactId, String email, boolean primary) {
270         return insertEmail(rawContactId, email, primary, Email.TYPE_HOME, null);
271     }
272 
insertEmail(long rawContactId, String email, boolean primary, int type, String label)273     protected Uri insertEmail(long rawContactId, String email, boolean primary, int type,
274             String label) {
275         ContentValues values = new ContentValues();
276         values.put(Data.RAW_CONTACT_ID, rawContactId);
277         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
278         values.put(Email.DATA, email);
279         values.put(Email.TYPE, type);
280         values.put(Email.LABEL, label);
281         if (primary) {
282             values.put(Data.IS_PRIMARY, 1);
283         }
284 
285         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
286         return resultUri;
287     }
288 
insertNickname(long rawContactId, String nickname)289     protected Uri insertNickname(long rawContactId, String nickname) {
290         ContentValues values = new ContentValues();
291         values.put(Data.RAW_CONTACT_ID, rawContactId);
292         values.put(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
293         values.put(Nickname.NAME, nickname);
294         values.put(Nickname.TYPE, Nickname.TYPE_OTHER_NAME);
295 
296         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
297         return resultUri;
298     }
299 
insertPostalAddress(long rawContactId, String formattedAddress)300     protected Uri insertPostalAddress(long rawContactId, String formattedAddress) {
301         ContentValues values = new ContentValues();
302         values.put(Data.RAW_CONTACT_ID, rawContactId);
303         values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
304         values.put(StructuredPostal.FORMATTED_ADDRESS, formattedAddress);
305 
306         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
307         return resultUri;
308     }
309 
insertPhoto(long rawContactId)310     protected Uri insertPhoto(long rawContactId) {
311         ContentValues values = new ContentValues();
312         values.put(Data.RAW_CONTACT_ID, rawContactId);
313         values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
314         values.put(Photo.PHOTO, loadTestPhoto());
315         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
316         return resultUri;
317     }
318 
insertGroupMembership(long rawContactId, String sourceId)319     protected Uri insertGroupMembership(long rawContactId, String sourceId) {
320         ContentValues values = new ContentValues();
321         values.put(Data.RAW_CONTACT_ID, rawContactId);
322         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
323         values.put(GroupMembership.GROUP_SOURCE_ID, sourceId);
324         return mResolver.insert(Data.CONTENT_URI, values);
325     }
326 
insertGroupMembership(long rawContactId, Long groupId)327     protected Uri insertGroupMembership(long rawContactId, Long groupId) {
328         ContentValues values = new ContentValues();
329         values.put(Data.RAW_CONTACT_ID, rawContactId);
330         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
331         values.put(GroupMembership.GROUP_ROW_ID, groupId);
332         return mResolver.insert(Data.CONTENT_URI, values);
333     }
334 
insertStatusUpdate(int protocol, String customProtocol, String handle, int presence, String status, int chatMode)335     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
336             int presence, String status, int chatMode) {
337         return insertStatusUpdate(protocol, customProtocol, handle, presence, status, 0, chatMode);
338     }
339 
insertStatusUpdate(int protocol, String customProtocol, String handle, int presence, String status, long timestamp, int chatMode)340     protected Uri insertStatusUpdate(int protocol, String customProtocol, String handle,
341             int presence, String status, long timestamp, int chatMode) {
342         ContentValues values = new ContentValues();
343         values.put(StatusUpdates.PROTOCOL, protocol);
344         values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol);
345         values.put(StatusUpdates.IM_HANDLE, handle);
346         if (presence != 0) {
347             values.put(StatusUpdates.PRESENCE, presence);
348             values.put(StatusUpdates.CHAT_CAPABILITY, chatMode);
349         }
350         if (status != null) {
351             values.put(StatusUpdates.STATUS, status);
352         }
353         if (timestamp != 0) {
354             values.put(StatusUpdates.STATUS_TIMESTAMP, timestamp);
355         }
356 
357         Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values);
358         return resultUri;
359     }
360 
insertImHandle(long rawContactId, int protocol, String customProtocol, String handle)361     protected Uri insertImHandle(long rawContactId, int protocol, String customProtocol,
362             String handle) {
363         ContentValues values = new ContentValues();
364         values.put(Data.RAW_CONTACT_ID, rawContactId);
365         values.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
366         values.put(Im.PROTOCOL, protocol);
367         values.put(Im.CUSTOM_PROTOCOL, customProtocol);
368         values.put(Im.DATA, handle);
369         values.put(Im.TYPE, Im.TYPE_HOME);
370 
371         Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
372         return resultUri;
373     }
374 
setContactAccount(long rawContactId, String accountType, String accountName)375     protected void setContactAccount(long rawContactId, String accountType, String accountName) {
376         ContentValues values = new ContentValues();
377         values.put(RawContacts.ACCOUNT_TYPE, accountType);
378         values.put(RawContacts.ACCOUNT_NAME, accountName);
379 
380         mResolver.update(ContentUris.withAppendedId(
381                 RawContacts.CONTENT_URI, rawContactId), values, null, null);
382     }
383 
setAggregationException(int type, long rawContactId1, long rawContactId2)384     protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
385         ContentValues values = new ContentValues();
386         values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
387         values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
388         values.put(AggregationExceptions.TYPE, type);
389         assertEquals(1, mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null));
390     }
391 
queryRawContact(long rawContactId)392     protected Cursor queryRawContact(long rawContactId) {
393         return mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
394                 null, null, null, null);
395     }
396 
queryContact(long contactId)397     protected Cursor queryContact(long contactId) {
398         return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
399                 null, null, null, null);
400     }
401 
queryContact(long contactId, String[] projection)402     protected Cursor queryContact(long contactId, String[] projection) {
403         return mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
404                 projection, null, null, null);
405     }
406 
queryContactId(long rawContactId)407     protected long queryContactId(long rawContactId) {
408         Cursor c = queryRawContact(rawContactId);
409         assertTrue(c.moveToFirst());
410         long contactId = c.getLong(c.getColumnIndex(RawContacts.CONTACT_ID));
411         c.close();
412         return contactId;
413     }
414 
queryPhotoId(long contactId)415     protected long queryPhotoId(long contactId) {
416         Cursor c = queryContact(contactId);
417         assertTrue(c.moveToFirst());
418         long photoId = c.getInt(c.getColumnIndex(Contacts.PHOTO_ID));
419         c.close();
420         return photoId;
421     }
422 
queryDisplayName(long contactId)423     protected String queryDisplayName(long contactId) {
424         Cursor c = queryContact(contactId);
425         assertTrue(c.moveToFirst());
426         String displayName = c.getString(c.getColumnIndex(Contacts.DISPLAY_NAME));
427         c.close();
428         return displayName;
429     }
430 
queryLookupKey(long contactId)431     private String queryLookupKey(long contactId) {
432         Cursor c = queryContact(contactId);
433         assertTrue(c.moveToFirst());
434         String lookupKey = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY));
435         c.close();
436         return lookupKey;
437     }
438 
assertAggregated(long rawContactId1, long rawContactId2)439     protected void assertAggregated(long rawContactId1, long rawContactId2) {
440         long contactId1 = queryContactId(rawContactId1);
441         long contactId2 = queryContactId(rawContactId2);
442         assertTrue(contactId1 == contactId2);
443     }
444 
assertAggregated(long rawContactId1, long rawContactId2, String expectedDisplayName)445     protected void assertAggregated(long rawContactId1, long rawContactId2,
446             String expectedDisplayName) {
447         long contactId1 = queryContactId(rawContactId1);
448         long contactId2 = queryContactId(rawContactId2);
449         assertTrue(contactId1 == contactId2);
450 
451         String displayName = queryDisplayName(contactId1);
452         assertEquals(expectedDisplayName, displayName);
453     }
454 
assertNotAggregated(long rawContactId1, long rawContactId2)455     protected void assertNotAggregated(long rawContactId1, long rawContactId2) {
456         long contactId1 = queryContactId(rawContactId1);
457         long contactId2 = queryContactId(rawContactId2);
458         assertTrue(contactId1 != contactId2);
459     }
460 
assertStructuredName(long rawContactId, String prefix, String givenName, String middleName, String familyName, String suffix)461     protected void assertStructuredName(long rawContactId, String prefix, String givenName,
462             String middleName, String familyName, String suffix) {
463         Uri uri =
464                 Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
465                 RawContacts.Data.CONTENT_DIRECTORY);
466 
467         final String[] projection = new String[] {
468                 StructuredName.PREFIX, StructuredName.GIVEN_NAME, StructuredName.MIDDLE_NAME,
469                 StructuredName.FAMILY_NAME, StructuredName.SUFFIX
470         };
471 
472         Cursor c = mResolver.query(uri, projection, Data.MIMETYPE + "='"
473                 + StructuredName.CONTENT_ITEM_TYPE + "'", null, null);
474 
475         assertTrue(c.moveToFirst());
476         assertEquals(prefix, c.getString(0));
477         assertEquals(givenName, c.getString(1));
478         assertEquals(middleName, c.getString(2));
479         assertEquals(familyName, c.getString(3));
480         assertEquals(suffix, c.getString(4));
481         c.close();
482     }
483 
assertSingleGroup(Long rowId, Account account, String sourceId, String title)484     protected long assertSingleGroup(Long rowId, Account account, String sourceId, String title) {
485         Cursor c = mResolver.query(Groups.CONTENT_URI, null, null, null, null);
486         try {
487             assertTrue(c.moveToNext());
488             long actualRowId = assertGroup(c, rowId, account, sourceId, title);
489             assertFalse(c.moveToNext());
490             return actualRowId;
491         } finally {
492             c.close();
493         }
494     }
495 
assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId, String sourceId)496     protected long assertSingleGroupMembership(Long rowId, Long rawContactId, Long groupRowId,
497             String sourceId) {
498         Cursor c = mResolver.query(ContactsContract.Data.CONTENT_URI, null, null, null, null);
499         try {
500             assertTrue(c.moveToNext());
501             long actualRowId = assertGroupMembership(c, rowId, rawContactId, groupRowId, sourceId);
502             assertFalse(c.moveToNext());
503             return actualRowId;
504         } finally {
505             c.close();
506         }
507     }
508 
assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId, String sourceId)509     protected long assertGroupMembership(Cursor c, Long rowId, Long rawContactId, Long groupRowId,
510             String sourceId) {
511         assertNullOrEquals(c, rowId, Data._ID);
512         assertNullOrEquals(c, rawContactId, GroupMembership.RAW_CONTACT_ID);
513         assertNullOrEquals(c, groupRowId, GroupMembership.GROUP_ROW_ID);
514         assertNullOrEquals(c, sourceId, GroupMembership.GROUP_SOURCE_ID);
515         return c.getLong(c.getColumnIndexOrThrow("_id"));
516     }
517 
assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title)518     protected long assertGroup(Cursor c, Long rowId, Account account, String sourceId, String title) {
519         assertNullOrEquals(c, rowId, Groups._ID);
520         assertNullOrEquals(c, account);
521         assertNullOrEquals(c, sourceId, Groups.SOURCE_ID);
522         assertNullOrEquals(c, title, Groups.TITLE);
523         return c.getLong(c.getColumnIndexOrThrow("_id"));
524     }
525 
assertNullOrEquals(Cursor c, Account account)526     private void assertNullOrEquals(Cursor c, Account account) {
527         if (account == NO_ACCOUNT) {
528             return;
529         }
530         if (account == null) {
531             assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME)));
532             assertTrue(c.isNull(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE)));
533         } else {
534             assertEquals(account.name, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_NAME)));
535             assertEquals(account.type, c.getString(c.getColumnIndexOrThrow(Groups.ACCOUNT_TYPE)));
536         }
537     }
538 
assertNullOrEquals(Cursor c, Long value, String columnName)539     private void assertNullOrEquals(Cursor c, Long value, String columnName) {
540         if (value != NO_LONG) {
541             if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName)));
542             else assertEquals((long) value, c.getLong(c.getColumnIndexOrThrow(columnName)));
543         }
544     }
545 
assertNullOrEquals(Cursor c, String value, String columnName)546     private void assertNullOrEquals(Cursor c, String value, String columnName) {
547         if (value != NO_STRING) {
548             if (value == null) assertTrue(c.isNull(c.getColumnIndexOrThrow(columnName)));
549             else assertEquals(value, c.getString(c.getColumnIndexOrThrow(columnName)));
550         }
551     }
552 
assertDataRow(ContentValues actual, String expectedMimetype, Object... expectedArguments)553     protected void assertDataRow(ContentValues actual, String expectedMimetype,
554             Object... expectedArguments) {
555         assertEquals(actual.toString(), expectedMimetype, actual.getAsString(Data.MIMETYPE));
556         for (int i = 0; i < expectedArguments.length; i += 2) {
557             String columnName = (String) expectedArguments[i];
558             Object expectedValue = expectedArguments[i + 1];
559             if (expectedValue instanceof Uri) {
560                 expectedValue = ContentUris.parseId((Uri) expectedValue);
561             }
562             if (expectedValue == null) {
563                 assertNull(actual.toString(), actual.get(columnName));
564             }
565             if (expectedValue instanceof Long) {
566                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
567                         expectedValue, actual.getAsLong(columnName));
568             } else if (expectedValue instanceof Integer) {
569                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
570                         expectedValue, actual.getAsInteger(columnName));
571             } else if (expectedValue instanceof String) {
572                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
573                         expectedValue, actual.getAsString(columnName));
574             } else {
575                 assertEquals("mismatch at " + columnName + " from " + actual.toString(),
576                         expectedValue, actual.get(columnName));
577             }
578         }
579     }
580 
581     protected static class IdComparator implements Comparator<ContentValues> {
compare(ContentValues o1, ContentValues o2)582         public int compare(ContentValues o1, ContentValues o2) {
583             long id1 = o1.getAsLong(ContactsContract.Data._ID);
584             long id2 = o2.getAsLong(ContactsContract.Data._ID);
585             if (id1 == id2) return 0;
586             return (id1 < id2) ? -1 : 1;
587         }
588     }
589 
asSortedContentValuesArray( ArrayList<Entity.NamedContentValues> subValues)590     protected ContentValues[] asSortedContentValuesArray(
591             ArrayList<Entity.NamedContentValues> subValues) {
592         ContentValues[] result = new ContentValues[subValues.size()];
593         int i = 0;
594         for (Entity.NamedContentValues subValue : subValues) {
595             result[i] = subValue.values;
596             i++;
597         }
598         Arrays.sort(result, new IdComparator());
599         return result;
600     }
601 
assertDirty(Uri uri, boolean state)602     protected void assertDirty(Uri uri, boolean state) {
603         Cursor c = mResolver.query(uri, new String[]{"dirty"}, null, null, null);
604         assertTrue(c.moveToNext());
605         assertEquals(state, c.getLong(0) != 0);
606         assertFalse(c.moveToNext());
607         c.close();
608     }
609 
getVersion(Uri uri)610     protected long getVersion(Uri uri) {
611         Cursor c = mResolver.query(uri, new String[]{"version"}, null, null, null);
612         assertTrue(c.moveToNext());
613         long version = c.getLong(0);
614         assertFalse(c.moveToNext());
615         c.close();
616         return version;
617     }
618 
clearDirty(Uri uri)619     protected void clearDirty(Uri uri) {
620         ContentValues values = new ContentValues();
621         values.put("dirty", 0);
622         mResolver.update(uri, values, null, null);
623     }
624 
storeValue(Uri contentUri, long id, String column, String value)625     protected void storeValue(Uri contentUri, long id, String column, String value) {
626         storeValue(ContentUris.withAppendedId(contentUri, id), column, value);
627     }
628 
storeValue(Uri contentUri, String column, String value)629     protected void storeValue(Uri contentUri, String column, String value) {
630         ContentValues values = new ContentValues();
631         values.put(column, value);
632 
633         mResolver.update(contentUri, values, null, null);
634     }
635 
storeValue(Uri contentUri, long id, String column, long value)636     protected void storeValue(Uri contentUri, long id, String column, long value) {
637         storeValue(ContentUris.withAppendedId(contentUri, id), column, value);
638     }
639 
storeValue(Uri contentUri, String column, long value)640     protected void storeValue(Uri contentUri, String column, long value) {
641         ContentValues values = new ContentValues();
642         values.put(column, value);
643 
644         mResolver.update(contentUri, values, null, null);
645     }
646 
assertStoredValue(Uri contentUri, long id, String column, Object expectedValue)647     protected void assertStoredValue(Uri contentUri, long id, String column, Object expectedValue) {
648         assertStoredValue(ContentUris.withAppendedId(contentUri, id), column, expectedValue);
649     }
650 
assertStoredValue(Uri rowUri, String column, Object expectedValue)651     protected void assertStoredValue(Uri rowUri, String column, Object expectedValue) {
652         String value = getStoredValue(rowUri, column);
653         if (expectedValue == null) {
654             assertNull("Column value " + column, value);
655         } else {
656             assertEquals("Column value " + column, String.valueOf(expectedValue), value);
657         }
658     }
659 
assertStoredValue(Uri rowUri, String selection, String[] selectionArgs, String column, Object expectedValue)660     protected void assertStoredValue(Uri rowUri, String selection, String[] selectionArgs,
661             String column, Object expectedValue) {
662         String value = getStoredValue(rowUri, selection, selectionArgs, column);
663         if (expectedValue == null) {
664             assertNull("Column value " + column, value);
665         } else {
666             assertEquals("Column value " + column, String.valueOf(expectedValue), value);
667         }
668     }
669 
getStoredValue(Uri rowUri, String column)670     protected String getStoredValue(Uri rowUri, String column) {
671         return getStoredValue(rowUri, null, null, column);
672     }
673 
getStoredValue(Uri uri, String selection, String[] selectionArgs, String column)674     protected String getStoredValue(Uri uri, String selection, String[] selectionArgs,
675             String column) {
676         String value = null;
677         Cursor c = mResolver.query(uri, new String[] { column }, selection, selectionArgs, null);
678         try {
679             if (c.moveToFirst()) {
680                 value = c.getString(c.getColumnIndex(column));
681             }
682         } finally {
683             c.close();
684         }
685         return value;
686     }
687 
assertStoredValues(Uri rowUri, ContentValues expectedValues)688     protected void assertStoredValues(Uri rowUri, ContentValues expectedValues) {
689         assertStoredValues(rowUri, null, null, expectedValues);
690     }
691 
assertStoredValues(Uri rowUri, String selection, String[] selectionArgs, ContentValues expectedValues)692     protected void assertStoredValues(Uri rowUri, String selection, String[] selectionArgs,
693             ContentValues expectedValues) {
694         Cursor c = mResolver.query(rowUri, null, selection, selectionArgs, null);
695         try {
696             assertEquals("Record count", 1, c.getCount());
697             c.moveToFirst();
698             assertCursorValues(c, expectedValues);
699         } finally {
700             c.close();
701         }
702     }
703 
assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues)704     protected void assertStoredValuesWithProjection(Uri rowUri, ContentValues expectedValues) {
705         Cursor c = mResolver.query(rowUri, buildProjection(expectedValues), null, null, null);
706         try {
707             assertEquals("Record count", 1, c.getCount());
708             c.moveToFirst();
709             assertCursorValues(c, expectedValues);
710         } finally {
711             c.close();
712         }
713     }
714 
715     /**
716      * Constructs a selection (where clause) out of all supplied values, uses it
717      * to query the provider and verifies that a single row is returned and it
718      * has the same values as requested.
719      */
assertSelection(Uri uri, ContentValues values, String idColumn, long id)720     protected void assertSelection(Uri uri, ContentValues values, String idColumn, long id) {
721         assertSelection(uri, values, idColumn, id, null);
722     }
723 
assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn, long id)724     public void assertSelectionWithProjection(Uri uri, ContentValues values, String idColumn,
725             long id) {
726         assertSelection(uri, values, idColumn, id, buildProjection(values));
727     }
728 
assertSelection(Uri uri, ContentValues values, String idColumn, long id, String[] projection)729     private void assertSelection(Uri uri, ContentValues values, String idColumn, long id,
730             String[] projection) {
731         StringBuilder sb = new StringBuilder();
732         ArrayList<String> selectionArgs = new ArrayList<String>(values.size());
733         if (idColumn != null) {
734             sb.append(idColumn).append("=").append(id);
735         }
736         Set<Map.Entry<String, Object>> entries = values.valueSet();
737         for (Map.Entry<String, Object> entry : entries) {
738             String column = entry.getKey();
739             Object value = entry.getValue();
740             if (sb.length() != 0) {
741                 sb.append(" AND ");
742             }
743             sb.append(column);
744             if (value == null) {
745                 sb.append(" IS NULL");
746             } else {
747                 sb.append("=?");
748                 selectionArgs.add(String.valueOf(value));
749             }
750         }
751 
752         Cursor c = mResolver.query(uri, projection, sb.toString(), selectionArgs.toArray(new String[0]),
753                 null);
754         try {
755             assertEquals("Record count", 1, c.getCount());
756             c.moveToFirst();
757             assertCursorValues(c, values);
758         } finally {
759             c.close();
760         }
761     }
762 
assertCursorValues(Cursor cursor, ContentValues expectedValues)763     protected void assertCursorValues(Cursor cursor, ContentValues expectedValues) {
764         Set<Map.Entry<String, Object>> entries = expectedValues.valueSet();
765         for (Map.Entry<String, Object> entry : entries) {
766             String column = entry.getKey();
767             int index = cursor.getColumnIndex(column);
768             assertTrue("No such column: " + column, index != -1);
769             Object expectedValue = expectedValues.get(column);
770             String value;
771             if (expectedValue instanceof byte[]) {
772                 expectedValue = Hex.encodeHex((byte[])expectedValue, false);
773                 value = Hex.encodeHex(cursor.getBlob(index), false);
774             } else {
775                 expectedValue = expectedValues.getAsString(column);
776                 value = cursor.getString(index);
777             }
778             assertEquals("Column value " + column, expectedValue, value);
779         }
780     }
781 
buildProjection(ContentValues values)782     private String[] buildProjection(ContentValues values) {
783         String[] projection = new String[values.size()];
784         Iterator<Entry<String, Object>> iter = values.valueSet().iterator();
785         for (int i = 0; i < projection.length; i++) {
786             projection[i] = iter.next().getKey();
787         }
788         return projection;
789     }
790 
getCount(Uri uri, String selection, String[] selectionArgs)791     protected int getCount(Uri uri, String selection, String[] selectionArgs) {
792         Cursor c = mResolver.query(uri, null, selection, selectionArgs, null);
793         try {
794             return c.getCount();
795         } finally {
796             c.close();
797         }
798     }
799 
loadTestPhoto()800     protected byte[] loadTestPhoto() {
801         if (mTestPhoto == null) {
802             final Resources resources = getContext().getResources();
803             InputStream is = resources
804                     .openRawResource(com.android.internal.R.drawable.ic_contact_picture);
805             ByteArrayOutputStream os = new ByteArrayOutputStream();
806             byte[] buffer = new byte[1000];
807             int count;
808             try {
809                 while ((count = is.read(buffer)) != -1) {
810                     os.write(buffer, 0, count);
811                 }
812             } catch (IOException e) {
813                 throw new RuntimeException(e);
814             }
815             mTestPhoto = os.toByteArray();
816         }
817         return mTestPhoto;
818     }
819 
dump(ContentResolver resolver, boolean aggregatedOnly)820     public static void dump(ContentResolver resolver, boolean aggregatedOnly) {
821         String[] projection = new String[] {
822                 Contacts._ID,
823                 Contacts.DISPLAY_NAME
824         };
825         String selection = null;
826         if (aggregatedOnly) {
827             selection = Contacts._ID
828                     + " IN (SELECT contact_id" +
829                     		" FROM raw_contacts GROUP BY contact_id HAVING count(*) > 1)";
830         }
831 
832         Cursor c = resolver.query(Contacts.CONTENT_URI, projection, selection, null,
833                 Contacts.DISPLAY_NAME);
834         while(c.moveToNext()) {
835             long contactId = c.getLong(0);
836             Log.i("Contact   ", String.format("%5d %s", contactId, c.getString(1)));
837             dumpRawContacts(resolver, contactId);
838             Log.i("          ", ".");
839         }
840         c.close();
841     }
842 
dumpRawContacts(ContentResolver resolver, long contactId)843     private static void dumpRawContacts(ContentResolver resolver, long contactId) {
844         String[] projection = new String[] {
845                 RawContacts._ID,
846         };
847         Cursor c = resolver.query(RawContacts.CONTENT_URI, projection, RawContacts.CONTACT_ID + "="
848                 + contactId, null, null);
849         while(c.moveToNext()) {
850             long rawContactId = c.getLong(0);
851             Log.i("RawContact", String.format("      %-5d", rawContactId));
852             dumpData(resolver, rawContactId);
853         }
854         c.close();
855     }
856 
dumpData(ContentResolver resolver, long rawContactId)857     private static void dumpData(ContentResolver resolver, long rawContactId) {
858         String[] projection = new String[] {
859                 Data.MIMETYPE,
860                 Data.DATA1,
861                 Data.DATA2,
862                 Data.DATA3,
863         };
864         Cursor c = resolver.query(Data.CONTENT_URI, projection, Data.RAW_CONTACT_ID + "="
865                 + rawContactId, null, Data.MIMETYPE);
866         while(c.moveToNext()) {
867             String mimetype = c.getString(0);
868             if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
869                 Log.i("Photo     ", "");
870             } else {
871                 mimetype = mimetype.substring(mimetype.indexOf('/') + 1);
872                 Log.i("Data      ", String.format("            %-10s %s,%s,%s", mimetype,
873                         c.getString(1), c.getString(2), c.getString(3)));
874             }
875         }
876         c.close();
877     }
878 
assertNetworkNotified(boolean expected)879     protected void assertNetworkNotified(boolean expected) {
880         assertEquals(expected, ((SynchronousContactsProvider2)mActor.provider).isNetworkNotified());
881     }
882 
883     /**
884      * A contact in the database, and the attributes used to create it.  Construct using
885      * {@link GoldenContactBuilder#build()}.
886      */
887     public final class GoldenContact {
888 
889         private final long rawContactId;
890 
891         private final long contactId;
892 
893         private final String givenName;
894 
895         private final String familyName;
896 
897         private final String nickname;
898 
899         private final byte[] photo;
900 
901         private final String company;
902 
903         private final String title;
904 
905         private final String phone;
906 
907         private final String email;
908 
GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId)909         private GoldenContact(GoldenContactBuilder builder, long rawContactId, long contactId) {
910 
911             this.rawContactId = rawContactId;
912             this.contactId = contactId;
913             givenName = builder.givenName;
914             familyName = builder.familyName;
915             nickname = builder.nickname;
916             photo = builder.photo;
917             company = builder.company;
918             title = builder.title;
919             phone = builder.phone;
920             email = builder.email;
921         }
922 
delete()923         public void delete() {
924             Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
925             mResolver.delete(rawContactUri, null, null);
926         }
927 
928         /**
929          * Returns the index of the contact in table "raw_contacts"
930          */
getRawContactId()931         public long getRawContactId() {
932             return rawContactId;
933         }
934 
935         /**
936          * Returns the index of the contact in table "contacts"
937          */
getContactId()938         public long getContactId() {
939             return contactId;
940         }
941 
942         /**
943          * Returns the lookup key for the contact.
944          */
getLookupKey()945         public String getLookupKey() {
946             return queryLookupKey(contactId);
947         }
948 
949         /**
950          * Returns the contact's given name.
951          */
getGivenName()952         public String getGivenName() {
953             return givenName;
954         }
955 
956         /**
957          * Returns the contact's family name.
958          */
getFamilyName()959         public String getFamilyName() {
960             return familyName;
961         }
962 
963         /**
964          * Returns the contact's nickname.
965          */
getNickname()966         public String getNickname() {
967             return nickname;
968         }
969 
970         /**
971          * Return's the contact's photo
972          */
getPhoto()973         public byte[] getPhoto() {
974             return photo;
975         }
976 
977         /**
978          * Return's the company at which the contact works.
979          */
getCompany()980         public String getCompany() {
981             return company;
982         }
983 
984         /**
985          * Returns the contact's job title.
986          */
getTitle()987         public String getTitle() {
988             return title;
989         }
990 
991         /**
992          * Returns the contact's phone number
993          */
getPhone()994         public String getPhone() {
995             return phone;
996         }
997 
998         /**
999          * Returns the contact's email address
1000          */
getEmail()1001         public String getEmail() {
1002             return email;
1003         }
1004      }
1005 
1006     /**
1007      * Builds {@link GoldenContact} objects.  Unspecified boolean objects default to false.
1008      * Unspecified String objects default to null.
1009      */
1010     public final class GoldenContactBuilder {
1011 
1012         private String givenName;
1013 
1014         private String familyName;
1015 
1016         private String nickname;
1017 
1018         private byte[] photo;
1019 
1020         private String company;
1021 
1022         private String title;
1023 
1024         private String phone;
1025 
1026         private String email;
1027 
1028         /**
1029          * The contact's given and family names.
1030          *
1031          * TODO(dplotnikov): inline, or should we require them to set both names if they set either?
1032          */
name(String givenName, String familyName)1033         public GoldenContactBuilder name(String givenName, String familyName) {
1034             return givenName(givenName).familyName(familyName);
1035         }
1036 
1037         /**
1038          * The contact's given name.
1039          */
givenName(String value)1040         public GoldenContactBuilder givenName(String value) {
1041             givenName = value;
1042             return this;
1043         }
1044 
1045         /**
1046          * The contact's family name.
1047          */
familyName(String value)1048         public GoldenContactBuilder familyName(String value) {
1049             familyName = value;
1050             return this;
1051         }
1052 
1053         /**
1054          * The contact's nickname.
1055          */
nickname(String value)1056         public GoldenContactBuilder nickname(String value) {
1057             nickname = value;
1058             return this;
1059         }
1060 
1061         /**
1062          * The contact's photo.
1063          */
photo(byte[] value)1064         public GoldenContactBuilder photo(byte[] value) {
1065             photo = value;
1066             return this;
1067         }
1068 
1069         /**
1070          * The company at which the contact works.
1071          */
company(String value)1072         public GoldenContactBuilder company(String value) {
1073             company = value;
1074             return this;
1075         }
1076 
1077         /**
1078          * The contact's job title.
1079          */
title(String value)1080         public GoldenContactBuilder title(String value) {
1081             title = value;
1082             return this;
1083         }
1084 
1085         /**
1086          * The contact's phone number.
1087          */
phone(String value)1088         public GoldenContactBuilder phone(String value) {
1089             phone = value;
1090             return this;
1091         }
1092 
1093         /**
1094          * The contact's email address; also sets their IM status to {@link StatusUpdates#OFFLINE}
1095          * with a presence of "Coding for Android".
1096          */
email(String value)1097         public GoldenContactBuilder email(String value) {
1098             email = value;
1099             return this;
1100         }
1101 
1102         /**
1103          * Builds the {@link GoldenContact} specified by this builder.
1104          */
build()1105         public GoldenContact build() {
1106 
1107             final long groupId = createGroup(mAccount, "gsid1", "title1");
1108 
1109             long rawContactId = createRawContact();
1110             insertGroupMembership(rawContactId, groupId);
1111 
1112             if (givenName != null || familyName != null) {
1113                 insertStructuredName(rawContactId, givenName, familyName);
1114             }
1115             if (nickname != null) {
1116                 insertNickname(rawContactId, nickname);
1117             }
1118             if (photo != null) {
1119                 insertPhoto(rawContactId);
1120             }
1121             if (company != null || title != null) {
1122                 insertOrganization(rawContactId);
1123             }
1124             if (email != null) {
1125                 insertEmail(rawContactId);
1126             }
1127             if (phone != null) {
1128                 insertPhone(rawContactId);
1129             }
1130 
1131             long contactId = queryContactId(rawContactId);
1132 
1133             return new GoldenContact(this, rawContactId, contactId);
1134         }
1135 
insertPhoto(long rawContactId)1136         private void insertPhoto(long rawContactId) {
1137             ContentValues values = new ContentValues();
1138             values.put(Data.RAW_CONTACT_ID, rawContactId);
1139             values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
1140             values.put(Photo.PHOTO, photo);
1141             mResolver.insert(Data.CONTENT_URI, values);
1142         }
1143 
insertOrganization(long rawContactId)1144         private void insertOrganization(long rawContactId) {
1145 
1146             ContentValues values = new ContentValues();
1147             values.put(Data.RAW_CONTACT_ID, rawContactId);
1148             values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
1149             values.put(Organization.TYPE, Organization.TYPE_WORK);
1150             if (company != null) {
1151                 values.put(Organization.COMPANY, company);
1152             }
1153             if (title != null) {
1154                 values.put(Organization.TITLE, title);
1155             }
1156             mResolver.insert(Data.CONTENT_URI, values);
1157         }
1158 
insertEmail(long rawContactId)1159         private void insertEmail(long rawContactId) {
1160 
1161             ContentValues values = new ContentValues();
1162             values.put(Data.RAW_CONTACT_ID, rawContactId);
1163             values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1164             values.put(Email.TYPE, Email.TYPE_WORK);
1165             values.put(Email.DATA, "foo@acme.com");
1166             mResolver.insert(Data.CONTENT_URI, values);
1167 
1168             int protocol = Im.PROTOCOL_GOOGLE_TALK;
1169 
1170             values.clear();
1171             values.put(StatusUpdates.PROTOCOL, protocol);
1172             values.put(StatusUpdates.IM_HANDLE, email);
1173             values.put(StatusUpdates.IM_ACCOUNT, "foo");
1174             values.put(StatusUpdates.PRESENCE_STATUS, StatusUpdates.OFFLINE);
1175             values.put(StatusUpdates.CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
1176             values.put(StatusUpdates.PRESENCE_CUSTOM_STATUS, "Coding for Android");
1177             mResolver.insert(StatusUpdates.CONTENT_URI, values);
1178         }
1179 
insertPhone(long rawContactId)1180         private void insertPhone(long rawContactId) {
1181             ContentValues values = new ContentValues();
1182             values.put(Data.RAW_CONTACT_ID, rawContactId);
1183             values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
1184             values.put(Data.IS_PRIMARY, 1);
1185             values.put(Phone.TYPE, Phone.TYPE_HOME);
1186             values.put(Phone.NUMBER, phone);
1187             mResolver.insert(Data.CONTENT_URI, values);
1188         }
1189     }
1190 }
1191