1 /*
2  * Copyright (C) 2010 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 android.provider.cts.contacts;
18 
19 
20 import static android.provider.ContactsContract.CommonDataKinds;
21 
22 import android.content.ContentProviderClient;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.net.Uri;
26 import android.os.SystemClock;
27 import android.provider.ContactsContract;
28 import android.provider.ContactsContract.CommonDataKinds.Callable;
29 import android.provider.ContactsContract.CommonDataKinds.Contactables;
30 import android.provider.ContactsContract.CommonDataKinds.Email;
31 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
32 import android.provider.ContactsContract.CommonDataKinds.Organization;
33 import android.provider.ContactsContract.CommonDataKinds.Phone;
34 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
35 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
36 import android.provider.ContactsContract.Contacts;
37 import android.provider.ContactsContract.Contacts.Entity;
38 import android.provider.ContactsContract.Data;
39 import android.provider.ContactsContract.Directory;
40 import android.provider.ContactsContract.RawContacts;
41 import android.provider.ContactsContract.RawContactsEntity;
42 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
43 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestData;
44 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
45 import android.test.InstrumentationTestCase;
46 
47 import java.util.ArrayList;
48 
49 public class ContactsContract_DataTest extends InstrumentationTestCase {
50     private ContentResolver mResolver;
51     private ContactsContract_TestDataBuilder mBuilder;
52 
53     static final String[] DATA_PROJECTION = new String[]{
54             Data._ID,
55             Data.RAW_CONTACT_ID,
56             Data.CONTACT_ID,
57             Data.NAME_RAW_CONTACT_ID,
58             RawContacts.RAW_CONTACT_IS_USER_PROFILE,
59             Data.DATA1,
60             Data.DATA2,
61             Data.DATA3,
62             Data.DATA4,
63             Data.DATA5,
64             Data.DATA6,
65             Data.DATA7,
66             Data.DATA8,
67             Data.DATA9,
68             Data.DATA10,
69             Data.DATA11,
70             Data.DATA12,
71             Data.DATA13,
72             Data.DATA14,
73             Data.DATA15,
74             Data.CARRIER_PRESENCE,
75             Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
76             Data.PREFERRED_PHONE_ACCOUNT_ID,
77             Data.DATA_VERSION,
78             Data.IS_PRIMARY,
79             Data.IS_SUPER_PRIMARY,
80             Data.MIMETYPE,
81             Data.RES_PACKAGE,
82             Data.SYNC1,
83             Data.SYNC2,
84             Data.SYNC3,
85             Data.SYNC4,
86             GroupMembership.GROUP_SOURCE_ID,
87             Data.PRESENCE,
88             Data.CHAT_CAPABILITY,
89             Data.STATUS,
90             Data.STATUS_TIMESTAMP,
91             Data.STATUS_RES_PACKAGE,
92             Data.STATUS_LABEL,
93             Data.STATUS_ICON,
94             RawContacts.ACCOUNT_NAME,
95             RawContacts.ACCOUNT_TYPE,
96             RawContacts.DATA_SET,
97             RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
98             RawContacts.DIRTY,
99             RawContacts.SOURCE_ID,
100             RawContacts.VERSION,
101             Contacts.CUSTOM_RINGTONE,
102             Contacts.DISPLAY_NAME,
103             Contacts.DISPLAY_NAME_ALTERNATIVE,
104             Contacts.DISPLAY_NAME_SOURCE,
105             Contacts.IN_DEFAULT_DIRECTORY,
106             Contacts.IN_VISIBLE_GROUP,
107             Contacts.LAST_TIME_CONTACTED,
108             Contacts.LOOKUP_KEY,
109             Contacts.PHONETIC_NAME,
110             Contacts.PHONETIC_NAME_STYLE,
111             Contacts.PHOTO_ID,
112             Contacts.PHOTO_FILE_ID,
113             Contacts.PHOTO_URI,
114             Contacts.PHOTO_THUMBNAIL_URI,
115             Contacts.SEND_TO_VOICEMAIL,
116             Contacts.SORT_KEY_ALTERNATIVE,
117             Contacts.SORT_KEY_PRIMARY,
118             Contacts.STARRED,
119             Contacts.PINNED,
120             Contacts.TIMES_CONTACTED,
121             Contacts.HAS_PHONE_NUMBER,
122             Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
123             Contacts.CONTACT_PRESENCE,
124             Contacts.CONTACT_CHAT_CAPABILITY,
125             Contacts.CONTACT_STATUS,
126             Contacts.CONTACT_STATUS_TIMESTAMP,
127             Contacts.CONTACT_STATUS_RES_PACKAGE,
128             Contacts.CONTACT_STATUS_LABEL,
129             Contacts.CONTACT_STATUS_ICON,
130             Data.TIMES_USED,
131             Data.LAST_TIME_USED};
132 
133     static final String[] RAW_CONTACTS_ENTITY_PROJECTION = new String[]{
134     };
135 
136     static final String[] NTITY_PROJECTION = new String[]{
137     };
138 
139     private static ContentValues[] sContentValues = new ContentValues[7];
140     static {
141         ContentValues cv1 = new ContentValues();
cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale")142         cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale");
cv1.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)143         cv1.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
cv1.put(Email.DATA, "tamale@acme.com")144         cv1.put(Email.DATA, "tamale@acme.com");
cv1.put(Email.TYPE, Email.TYPE_HOME)145         cv1.put(Email.TYPE, Email.TYPE_HOME);
146         sContentValues[0] = cv1;
147 
148         ContentValues cv2 = new ContentValues();
cv2.put(Contacts.DISPLAY_NAME, "Hot Tamale")149         cv2.put(Contacts.DISPLAY_NAME, "Hot Tamale");
cv2.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)150         cv2.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
cv2.put(Phone.DATA, "510-123-5769")151         cv2.put(Phone.DATA, "510-123-5769");
cv2.put(Phone.TYPE, Phone.TYPE_HOME)152         cv2.put(Phone.TYPE, Phone.TYPE_HOME);
153         sContentValues[1] = cv2;
154 
155         ContentValues cv3 = new ContentValues();
cv3.put(Contacts.DISPLAY_NAME, "Hot Tamale")156         cv3.put(Contacts.DISPLAY_NAME, "Hot Tamale");
cv3.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)157         cv3.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
cv3.put(Email.DATA, "hot@google.com")158         cv3.put(Email.DATA, "hot@google.com");
cv3.put(Email.TYPE, Email.TYPE_WORK)159         cv3.put(Email.TYPE, Email.TYPE_WORK);
160         sContentValues[2] = cv3;
161 
162         ContentValues cv4 = new ContentValues();
cv4.put(Contacts.DISPLAY_NAME, "Cold Tamago")163         cv4.put(Contacts.DISPLAY_NAME, "Cold Tamago");
cv4.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)164         cv4.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
cv4.put(Email.DATA, "eggs@farmers.org")165         cv4.put(Email.DATA, "eggs@farmers.org");
cv4.put(Email.TYPE, Email.TYPE_HOME)166         cv4.put(Email.TYPE, Email.TYPE_HOME);
167         sContentValues[3] = cv4;
168 
169         ContentValues cv5 = new ContentValues();
cv5.put(Contacts.DISPLAY_NAME, "John Doe")170         cv5.put(Contacts.DISPLAY_NAME, "John Doe");
cv5.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)171         cv5.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
cv5.put(Email.DATA, "doeassociates@deer.com")172         cv5.put(Email.DATA, "doeassociates@deer.com");
cv5.put(Email.TYPE, Email.TYPE_WORK)173         cv5.put(Email.TYPE, Email.TYPE_WORK);
174         sContentValues[4] = cv5;
175 
176         ContentValues cv6 = new ContentValues();
cv6.put(Contacts.DISPLAY_NAME, "John Doe")177         cv6.put(Contacts.DISPLAY_NAME, "John Doe");
cv6.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)178         cv6.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
cv6.put(Phone.DATA, "518-354-1111")179         cv6.put(Phone.DATA, "518-354-1111");
cv6.put(Phone.TYPE, Phone.TYPE_HOME)180         cv6.put(Phone.TYPE, Phone.TYPE_HOME);
181         sContentValues[5] = cv6;
182 
183         ContentValues cv7 = new ContentValues();
cv7.put(Contacts.DISPLAY_NAME, "Cold Tamago")184         cv7.put(Contacts.DISPLAY_NAME, "Cold Tamago");
cv7.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE)185         cv7.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
cv7.put(SipAddress.DATA, "mysip@sipaddress.com")186         cv7.put(SipAddress.DATA, "mysip@sipaddress.com");
cv7.put(SipAddress.TYPE, SipAddress.TYPE_HOME)187         cv7.put(SipAddress.TYPE, SipAddress.TYPE_HOME);
188         sContentValues[6] = cv7;
189     }
190 
191     private TestRawContact[] mRawContacts = new TestRawContact[3];
192 
193     @Override
setUp()194     protected void setUp() throws Exception {
195         super.setUp();
196         mResolver = getInstrumentation().getTargetContext().getContentResolver();
197         ContentProviderClient provider =
198                 mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
199         mBuilder = new ContactsContract_TestDataBuilder(provider);
200     }
201 
202     @Override
tearDown()203     protected void tearDown() throws Exception {
204         super.tearDown();
205         mBuilder.cleanup();
206     }
207 
testGetLookupUriBySourceId()208     public void testGetLookupUriBySourceId() throws Exception {
209         TestRawContact rawContact = mBuilder.newRawContact()
210                 .with(RawContacts.ACCOUNT_TYPE, "test_type")
211                 .with(RawContacts.ACCOUNT_NAME, "test_name")
212                 .with(RawContacts.SOURCE_ID, "source_id")
213                 .insert();
214 
215         // TODO remove this. The method under test is currently broken: it will not
216         // work without at least one data row in the raw contact.
217         TestData data = rawContact.newDataRow(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
218                 .with(CommonDataKinds.StructuredName.DISPLAY_NAME, "test name")
219                 .insert();
220 
221         Uri lookupUri = Data.getContactLookupUri(mResolver, data.getUri());
222         assertNotNull("Could not produce a lookup URI", lookupUri);
223 
224         TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
225         assertEquals("Lookup URI matched the wrong contact",
226                 lookupContact.getId(), data.load().getRawContact().load().getContactId());
227     }
228 
testDataProjection()229     public void testDataProjection() throws Exception {
230         TestRawContact rawContact = mBuilder.newRawContact()
231                 .with(RawContacts.ACCOUNT_TYPE, "test_type")
232                 .with(RawContacts.ACCOUNT_NAME, "test_name")
233                 .with(RawContacts.SOURCE_ID, "source_id")
234                 .insert();
235         TestData data = rawContact.newDataRow(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
236                 .with(CommonDataKinds.StructuredName.DISPLAY_NAME, "test name")
237                 .insert();
238 
239         DatabaseAsserts.checkProjection(mResolver, Data.CONTENT_URI,
240                 DATA_PROJECTION,
241                 new long[]{data.load().getId()}
242         );
243     }
244 
testRawContactsEntityProjection()245     public void testRawContactsEntityProjection() throws Exception {
246         TestRawContact rawContact = mBuilder.newRawContact()
247                 .with(RawContacts.ACCOUNT_TYPE, "test_type")
248                 .with(RawContacts.ACCOUNT_NAME, "test_name")
249                 .with(RawContacts.SOURCE_ID, "source_id")
250                 .insert();
251         TestData data = rawContact.newDataRow(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
252                 .with(CommonDataKinds.StructuredName.DISPLAY_NAME, "test name")
253                 .insert();
254 
255         DatabaseAsserts.checkProjection(mResolver, RawContactsEntity.CONTENT_URI,
256                 new String[]{
257                         RawContacts._ID,
258                         RawContacts.CONTACT_ID,
259                         RawContacts.Entity.DATA_ID,
260                         RawContacts.DELETED,
261                         RawContacts.STARRED,
262                         RawContacts.RAW_CONTACT_IS_USER_PROFILE,
263                         RawContacts.ACCOUNT_NAME,
264                         RawContacts.ACCOUNT_TYPE,
265                         RawContacts.DATA_SET,
266                         RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
267                         RawContacts.DIRTY,
268                         RawContacts.SOURCE_ID,
269                         RawContacts.BACKUP_ID,
270                         RawContacts.VERSION,
271                         RawContacts.SYNC1,
272                         RawContacts.SYNC2,
273                         RawContacts.SYNC3,
274                         RawContacts.SYNC4,
275                         Data.DATA1,
276                         Data.DATA2,
277                         Data.DATA3,
278                         Data.DATA4,
279                         Data.DATA5,
280                         Data.DATA6,
281                         Data.DATA7,
282                         Data.DATA8,
283                         Data.DATA9,
284                         Data.DATA10,
285                         Data.DATA11,
286                         Data.DATA12,
287                         Data.DATA13,
288                         Data.DATA14,
289                         Data.DATA15,
290                         Data.CARRIER_PRESENCE,
291                         Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
292                         Data.PREFERRED_PHONE_ACCOUNT_ID,
293                         Data.DATA_VERSION,
294                         Data.IS_PRIMARY,
295                         Data.IS_SUPER_PRIMARY,
296                         Data.MIMETYPE,
297                         Data.RES_PACKAGE,
298                         Data.SYNC1,
299                         Data.SYNC2,
300                         Data.SYNC3,
301                         Data.SYNC4,
302                         GroupMembership.GROUP_SOURCE_ID},
303                 new long[]{rawContact.getId()}
304         );
305     }
306 
testEntityProjection()307     public void testEntityProjection() throws Exception {
308         TestRawContact rawContact = mBuilder.newRawContact()
309                 .with(RawContacts.ACCOUNT_TYPE, "test_type")
310                 .with(RawContacts.ACCOUNT_NAME, "test_name")
311                 .with(RawContacts.SOURCE_ID, "source_id")
312                 .insert();
313         TestData data = rawContact.newDataRow(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
314                 .with(CommonDataKinds.StructuredName.DISPLAY_NAME, "test name")
315                 .insert();
316         long contactId = rawContact.load().getContactId();
317 
318         DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_URI.buildUpon().appendPath(
319                         String.valueOf(contactId)).appendPath(
320                         Entity.CONTENT_DIRECTORY).build(),
321                 new String[]{
322                         Contacts.Entity._ID,
323                         Contacts.Entity.CONTACT_ID,
324                         Contacts.Entity.RAW_CONTACT_ID,
325                         Contacts.Entity.DATA_ID,
326                         Contacts.Entity.NAME_RAW_CONTACT_ID,
327                         Contacts.Entity.DELETED,
328                         Contacts.IS_USER_PROFILE,
329                         Contacts.CUSTOM_RINGTONE,
330                         Contacts.DISPLAY_NAME,
331                         Contacts.DISPLAY_NAME_ALTERNATIVE,
332                         Contacts.DISPLAY_NAME_SOURCE,
333                         Contacts.IN_DEFAULT_DIRECTORY,
334                         Contacts.IN_VISIBLE_GROUP,
335                         Contacts.LAST_TIME_CONTACTED,
336                         Contacts.LOOKUP_KEY,
337                         Contacts.PHONETIC_NAME,
338                         Contacts.PHONETIC_NAME_STYLE,
339                         Contacts.PHOTO_ID,
340                         Contacts.PHOTO_FILE_ID,
341                         Contacts.PHOTO_URI,
342                         Contacts.PHOTO_THUMBNAIL_URI,
343                         Contacts.SEND_TO_VOICEMAIL,
344                         Contacts.SORT_KEY_ALTERNATIVE,
345                         Contacts.SORT_KEY_PRIMARY,
346                         Contacts.STARRED,
347                         Contacts.PINNED,
348                         Contacts.TIMES_CONTACTED,
349                         Contacts.HAS_PHONE_NUMBER,
350                         Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
351                         Contacts.CONTACT_PRESENCE,
352                         Contacts.CONTACT_CHAT_CAPABILITY,
353                         Contacts.CONTACT_STATUS,
354                         Contacts.CONTACT_STATUS_TIMESTAMP,
355                         Contacts.CONTACT_STATUS_RES_PACKAGE,
356                         Contacts.CONTACT_STATUS_LABEL,
357                         Contacts.CONTACT_STATUS_ICON,
358                         RawContacts.ACCOUNT_NAME,
359                         RawContacts.ACCOUNT_TYPE,
360                         RawContacts.DATA_SET,
361                         RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
362                         RawContacts.DIRTY,
363                         RawContacts.SOURCE_ID,
364                         RawContacts.BACKUP_ID,
365                         RawContacts.VERSION,
366                         RawContacts.SYNC1,
367                         RawContacts.SYNC2,
368                         RawContacts.SYNC3,
369                         RawContacts.SYNC4,
370                         Data.DATA1,
371                         Data.DATA2,
372                         Data.DATA3,
373                         Data.DATA4,
374                         Data.DATA5,
375                         Data.DATA6,
376                         Data.DATA7,
377                         Data.DATA8,
378                         Data.DATA9,
379                         Data.DATA10,
380                         Data.DATA11,
381                         Data.DATA12,
382                         Data.DATA13,
383                         Data.DATA14,
384                         Data.DATA15,
385                         Data.CARRIER_PRESENCE,
386                         Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
387                         Data.PREFERRED_PHONE_ACCOUNT_ID,
388                         Data.DATA_VERSION,
389                         Data.IS_PRIMARY,
390                         Data.IS_SUPER_PRIMARY,
391                         Data.MIMETYPE,
392                         Data.RES_PACKAGE,
393                         Data.SYNC1,
394                         Data.SYNC2,
395                         Data.SYNC3,
396                         Data.SYNC4,
397                         GroupMembership.GROUP_SOURCE_ID,
398                         Data.PRESENCE,
399                         Data.CHAT_CAPABILITY,
400                         Data.STATUS,
401                         Data.STATUS_TIMESTAMP,
402                         Data.STATUS_RES_PACKAGE,
403                         Data.STATUS_LABEL,
404                         Data.STATUS_ICON,
405                         Data.TIMES_USED,
406                         Data.LAST_TIME_USED},
407                 new long[]{contactId}
408         );
409     }
410 
testGetLookupUriByDisplayName()411     public void testGetLookupUriByDisplayName() throws Exception {
412         TestRawContact rawContact = mBuilder.newRawContact()
413                 .with(RawContacts.ACCOUNT_TYPE, "test_type")
414                 .with(RawContacts.ACCOUNT_NAME, "test_name")
415                 .insert();
416         TestData data = rawContact.newDataRow(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
417                 .with(CommonDataKinds.StructuredName.DISPLAY_NAME, "test name")
418                 .insert();
419 
420         Uri lookupUri = Data.getContactLookupUri(mResolver, data.getUri());
421         assertNotNull("Could not produce a lookup URI", lookupUri);
422 
423         TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
424         assertEquals("Lookup URI matched the wrong contact",
425                 lookupContact.getId(), data.load().getRawContact().load().getContactId());
426     }
427 
testContactablesUri()428     public void testContactablesUri() throws Exception {
429         TestRawContact rawContact = mBuilder.newRawContact()
430                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
431                 .with(RawContacts.ACCOUNT_NAME, "test_name")
432                 .insert();
433         rawContact.newDataRow(CommonDataKinds.Email.CONTENT_ITEM_TYPE)
434                 .with(Email.DATA, "test@test.com")
435                 .with(Email.TYPE, Email.TYPE_WORK)
436                 .insert();
437         ContentValues cv = new ContentValues();
438         cv.put(Email.DATA, "test@test.com");
439         cv.put(Email.TYPE, Email.TYPE_WORK);
440 
441         Uri contentUri = ContactsContract.CommonDataKinds.Contactables.CONTENT_URI;
442         try {
443             assertCursorStoredValuesWithRawContactsFilter(contentUri,
444                     new long[] {rawContact.getId()}, cv);
445             rawContact.newDataRow(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
446                     .with(CommonDataKinds.StructuredPostal.DATA1, "100 Sesame Street")
447                     .insert();
448 
449             rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
450                     .with(Phone.DATA, "123456789")
451                     .with(Phone.TYPE, Phone.TYPE_MOBILE)
452                     .insert();
453 
454             ContentValues cv2 = new ContentValues();
455             cv.put(Phone.DATA, "123456789");
456             cv.put(Phone.TYPE, Phone.TYPE_MOBILE);
457 
458             // Contactables Uri should return only email and phone data items.
459             DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, contentUri, null,
460                     Data.RAW_CONTACT_ID + "=?", new String[] {String.valueOf(rawContact.getId())},
461                     null, false, cv, cv2);
462         } finally {
463             // Clean up
464             rawContact.delete();
465         }
466     }
467 
testContactablesFilterByLastName_returnsCorrectDataRows()468     public void testContactablesFilterByLastName_returnsCorrectDataRows() throws Exception {
469         long[] ids = setupContactablesTestData();
470         Uri filterUri = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "tamale");
471         assertCursorStoredValuesWithRawContactsFilter(filterUri, ids,
472                 ContactablesTestHelper.getContentValues(0));
473     }
474 
testContactablesFilterByFirstName_returnsCorrectDataRows()475     public void testContactablesFilterByFirstName_returnsCorrectDataRows() throws Exception {
476         long[] ids = setupContactablesTestData();
477         Uri filterUri = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "hot");
478         assertCursorStoredValuesWithRawContactsFilter(filterUri, ids,
479                 ContactablesTestHelper.getContentValues(0));
480         Uri filterUri2 = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "tam");
481         assertCursorStoredValuesWithRawContactsFilter(filterUri2, ids,
482                 ContactablesTestHelper.getContentValues(0, 1));
483     }
484 
testContactablesFilterByPhonePrefix_returnsCorrectDataRows()485     public void testContactablesFilterByPhonePrefix_returnsCorrectDataRows() throws Exception {
486         long[] ids = setupContactablesTestData();
487         Uri filterUri = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "518");
488         assertCursorStoredValuesWithRawContactsFilter(filterUri, ids,
489                 ContactablesTestHelper.getContentValues(2));
490         Uri filterUri2 = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "51");
491         assertCursorStoredValuesWithRawContactsFilter(filterUri2, ids,
492                 ContactablesTestHelper.getContentValues(0, 2));
493     }
494 
testContactablesFilterByEmailPrefix_returnsCorrectDataRows()495     public void testContactablesFilterByEmailPrefix_returnsCorrectDataRows() throws Exception {
496         long[] ids = setupContactablesTestData();
497         Uri filterUri = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "doeassoc");
498         assertCursorStoredValuesWithRawContactsFilter(filterUri, ids,
499                 ContactablesTestHelper.getContentValues(2));
500     }
501 
testContactablesFilter_doesNotExist_returnsCorrectDataRows()502     public void testContactablesFilter_doesNotExist_returnsCorrectDataRows() throws Exception {
503         long[] ids = setupContactablesTestData();
504         Uri filterUri = Uri.withAppendedPath(Contactables.CONTENT_FILTER_URI, "doesnotexist");
505         assertCursorStoredValuesWithRawContactsFilter(filterUri, ids, new ContentValues[0]);
506     }
507 
508     /**
509      * Verifies that Callable.CONTENT_URI returns only data items that can be called (i.e.
510      * phone numbers and sip addresses)
511      */
testCallableUri_returnsCorrectDataRows()512     public void testCallableUri_returnsCorrectDataRows() throws Exception {
513         long[] ids = setupContactablesTestData();
514         Uri uri = Callable.CONTENT_URI;
515         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1],
516                 sContentValues[5], sContentValues[6]);
517     }
518 
testCallableFilterByNameOrOrganization_returnsCorrectDataRows()519     public void testCallableFilterByNameOrOrganization_returnsCorrectDataRows() throws Exception {
520         long[] ids = setupContactablesTestData();
521         Uri uri = Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, "doe");
522         // Only callables belonging to John Doe (name) and Cold Tamago (organization) are returned.
523         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[5],
524                 sContentValues[6]);
525     }
526 
testCallableFilterByNumber_returnsCorrectDataRows()527     public void testCallableFilterByNumber_returnsCorrectDataRows() throws Exception {
528         long[] ids = setupContactablesTestData();
529         Uri uri = Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, "510");
530         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1]);
531     }
532 
testCallableFilterBySipAddress_returnsCorrectDataRows()533     public void testCallableFilterBySipAddress_returnsCorrectDataRows() throws Exception {
534         long[] ids = setupContactablesTestData();
535         Uri uri = Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, "mysip");
536         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[6]);
537     }
538 
testEnterpriseCallableFilterByNameOrOrganization_returnsCorrectDataRows()539     public void testEnterpriseCallableFilterByNameOrOrganization_returnsCorrectDataRows()
540             throws Exception {
541         long[] ids = setupContactablesTestData();
542         Uri uri = Uri.withAppendedPath(Callable.ENTERPRISE_CONTENT_FILTER_URI, "doe").buildUpon()
543                 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
544                         String.valueOf(Directory.DEFAULT))
545                 .build();
546         // Only callables belonging to John Doe (name) and Cold Tamago (organization) are returned.
547         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[5],
548                 sContentValues[6]);
549     }
550 
testEnterpriseCallableFilterByNumber_returnsCorrectDataRows()551     public void testEnterpriseCallableFilterByNumber_returnsCorrectDataRows() throws Exception {
552         long[] ids = setupContactablesTestData();
553         Uri uri = Uri.withAppendedPath(Callable.ENTERPRISE_CONTENT_FILTER_URI, "510").buildUpon()
554                 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
555                         String.valueOf(Directory.DEFAULT))
556                 .build();
557         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1]);
558     }
559 
testEnterpriseCallableFilterBySipAddress_returnsCorrectDataRows()560     public void testEnterpriseCallableFilterBySipAddress_returnsCorrectDataRows()
561             throws Exception {
562         long[] ids = setupContactablesTestData();
563         Uri uri = Uri.withAppendedPath(Callable.ENTERPRISE_CONTENT_FILTER_URI, "mysip").buildUpon()
564                 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
565                         String.valueOf(Directory.DEFAULT))
566                 .build();
567         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[6]);
568     }
569 
testDataInsert_updatesContactLastUpdatedTimestamp()570     public void testDataInsert_updatesContactLastUpdatedTimestamp() {
571         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
572         long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
573 
574         SystemClock.sleep(1);
575         createData(ids.mRawContactId);
576 
577         long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
578         assertTrue(newTime > baseTime);
579 
580         // Clean up
581         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
582     }
583 
testDataDelete_updatesContactLastUpdatedTimestamp()584     public void testDataDelete_updatesContactLastUpdatedTimestamp() {
585         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
586 
587         long dataId = createData(ids.mRawContactId);
588 
589         long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
590 
591         SystemClock.sleep(1);
592         DataUtil.delete(mResolver, dataId);
593 
594         long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
595         assertTrue(newTime > baseTime);
596 
597         // Clean up
598         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
599     }
600 
601     /**
602      * Tests that specifying the {@link android.provider.ContactsContract#REMOVE_DUPLICATE_ENTRIES}
603      * boolean parameter correctly results in deduped phone numbers.
604      */
testPhoneQuery_removeDuplicateEntries()605     public void testPhoneQuery_removeDuplicateEntries() throws Exception{
606         long[] ids = setupContactablesTestData();
607 
608         // Insert duplicate data entry for raw contact 3. (existing phone number 518-354-1111)
609         mRawContacts[2].newDataRow(Phone.CONTENT_ITEM_TYPE)
610                 .with(Phone.DATA, "518-354-1111")
611                 .with(Phone.TYPE, Phone.TYPE_HOME)
612                 .insert();
613 
614         ContentValues dupe = new ContentValues();
615         dupe.put(Contacts.DISPLAY_NAME, "John Doe");
616         dupe.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
617         dupe.put(Phone.DATA, "518-354-1111");
618         dupe.put(Phone.TYPE, Phone.TYPE_HOME);
619 
620         // Query for all phone numbers in the contacts database (without deduping).
621         // The phone number above should be listed twice, in its duplicated forms.
622         assertCursorStoredValuesWithRawContactsFilter(Phone.CONTENT_URI, ids, sContentValues[1],
623                 sContentValues[5], dupe);
624 
625         // Now query for all phone numbers in the contacts database but request deduping.
626         // The phone number should now be listed only once.
627         Uri uri = Phone.CONTENT_URI.buildUpon().
628                 appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build();
629         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[1],
630                 sContentValues[5]);
631     }
632 
633     /**
634      * Tests that specifying the {@link android.provider.ContactsContract#REMOVE_DUPLICATE_ENTRIES}
635      * boolean parameter correctly results in deduped email addresses.
636      */
testEmailQuery_removeDuplicateEntries()637     public void testEmailQuery_removeDuplicateEntries() throws Exception{
638         long[] ids = setupContactablesTestData();
639 
640         // Insert duplicate data entry for raw contact 3. (existing email doeassociates@deer.com)
641         mRawContacts[2].newDataRow(Email.CONTENT_ITEM_TYPE)
642                 .with(Email.DATA, "doeassociates@deer.com")
643                 .with(Email.TYPE, Email.TYPE_WORK)
644                 .insert();
645 
646         ContentValues dupe = new ContentValues();
647         dupe.put(Contacts.DISPLAY_NAME, "John Doe");
648         dupe.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
649         dupe.put(Email.DATA, "doeassociates@deer.com");
650         dupe.put(Email.TYPE, Email.TYPE_WORK);
651 
652         // Query for all email addresses in the contacts database (without deduping).
653         // The email address above should be listed twice, in its duplicated forms.
654         assertCursorStoredValuesWithRawContactsFilter(Email.CONTENT_URI, ids, sContentValues[0],
655                 sContentValues[2], sContentValues[3], sContentValues[4], dupe);
656 
657         // Now query for all email addresses in the contacts database but request deduping.
658         // The email address should now be listed only once.
659         Uri uri = Email.CONTENT_URI.buildUpon().
660                 appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build();
661         assertCursorStoredValuesWithRawContactsFilter(uri, ids, sContentValues[0],
662                 sContentValues[2], sContentValues[3], sContentValues[4]);
663     }
664 
testDataUpdate_updatesContactLastUpdatedTimestamp()665     public void testDataUpdate_updatesContactLastUpdatedTimestamp() {
666         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
667         long dataId = createData(ids.mRawContactId);
668         long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
669 
670         SystemClock.sleep(1);
671         ContentValues values = new ContentValues();
672         values.put(CommonDataKinds.Phone.NUMBER, "555-5555");
673         DataUtil.update(mResolver, dataId, values);
674 
675         long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
676         assertTrue("Expected contact " + ids.mContactId + " last updated to be greater than " +
677                 baseTime + ". But was " + newTime, newTime > baseTime);
678 
679         // Clean up
680         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
681     }
682 
createData(long rawContactId)683     private long createData(long rawContactId) {
684         ContentValues values = new ContentValues();
685         values.put(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
686         values.put(CommonDataKinds.Phone.NUMBER, "1-800-GOOG-411");
687         values.put(CommonDataKinds.Phone.TYPE, CommonDataKinds.Phone.TYPE_CUSTOM);
688         values.put(CommonDataKinds.Phone.LABEL, "free directory assistance");
689         return DataUtil.insertData(mResolver, rawContactId, values);
690     }
691 
assertCursorStoredValuesWithRawContactsFilter(Uri uri, long[] rawContactsId, ContentValues... expected)692     private void assertCursorStoredValuesWithRawContactsFilter(Uri uri, long[] rawContactsId,
693             ContentValues... expected) {
694         // We need this helper function to add a filter for specific raw contacts because
695         // otherwise tests will fail if performed on a device with existing contacts data
696         StringBuilder sb = new StringBuilder();
697         sb.append(Data.RAW_CONTACT_ID + " in ");
698         sb.append("(");
699         for (int i = 0; i < rawContactsId.length; i++) {
700             if (i != 0) sb.append(",");
701             sb.append(rawContactsId[i]);
702         }
703         sb.append(")");
704         DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, uri, null, sb.toString(),
705                 null, null, false, expected);
706     }
707 
708 
setupContactablesTestData()709     private long[] setupContactablesTestData() throws Exception {
710         TestRawContact rawContact = mBuilder.newRawContact()
711                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
712                 .with(RawContacts.ACCOUNT_NAME, "test_name")
713                 .insert();
714         rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
715                 .with(StructuredName.DISPLAY_NAME, "Hot Tamale")
716                 .insert();
717         rawContact.newDataRow(Email.CONTENT_ITEM_TYPE)
718                 .with(Email.DATA, "tamale@acme.com")
719                 .with(Email.TYPE, Email.TYPE_HOME)
720                 .insert();
721         rawContact.newDataRow(Email.CONTENT_ITEM_TYPE)
722                 .with(Email.DATA, "hot@google.com")
723                 .with(Email.TYPE, Email.TYPE_WORK)
724                 .insert();
725         rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
726                 .with(Phone.DATA, "510-123-5769")
727                 .with(Email.TYPE, Phone.TYPE_HOME)
728                 .insert();
729         mRawContacts[0] = rawContact;
730 
731         TestRawContact rawContact2 = mBuilder.newRawContact()
732                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
733                 .with(RawContacts.ACCOUNT_NAME, "test_name")
734                 .insert();
735         rawContact2.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
736                 .with(StructuredName.DISPLAY_NAME, "Cold Tamago")
737                 .insert();
738         rawContact2.newDataRow(Email.CONTENT_ITEM_TYPE)
739                 .with(Email.DATA, "eggs@farmers.org")
740                 .with(Email.TYPE, Email.TYPE_HOME)
741                 .insert();
742         rawContact2.newDataRow(SipAddress.CONTENT_ITEM_TYPE)
743                 .with(SipAddress.DATA, "mysip@sipaddress.com")
744                 .with(SipAddress.TYPE, SipAddress.TYPE_HOME)
745                 .insert();
746         rawContact2.newDataRow(Organization.CONTENT_ITEM_TYPE)
747                 .with(Organization.COMPANY, "Doe Corp")
748                 .insert();
749         mRawContacts[1] = rawContact2;
750 
751         TestRawContact rawContact3 = mBuilder.newRawContact()
752                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
753                 .with(RawContacts.ACCOUNT_NAME, "test_name")
754                 .insert();
755         rawContact3.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
756                 .with(StructuredName.DISPLAY_NAME, "John Doe")
757                 .insert();
758         rawContact3.newDataRow(Email.CONTENT_ITEM_TYPE)
759                 .with(Email.DATA, "doeassociates@deer.com")
760                 .with(Email.TYPE, Email.TYPE_WORK)
761                 .insert();
762         rawContact3.newDataRow(Phone.CONTENT_ITEM_TYPE)
763                 .with(Phone.DATA, "518-354-1111")
764                 .with(Phone.TYPE, Phone.TYPE_HOME)
765                 .insert();
766         rawContact3.newDataRow(Organization.CONTENT_ITEM_TYPE)
767                 .with(Organization.DATA, "Doe Industries")
768                 .insert();
769         mRawContacts[2] = rawContact3;
770         return new long[] {rawContact.getId(), rawContact2.getId(), rawContact3.getId()};
771     }
772 
773     // Provides functionality to set up content values for the Contactables tests
774     private static class ContactablesTestHelper {
775 
776         /**
777          * @return An arraylist of contentValues that correspond to the provided raw contacts
778          */
getContentValues(int... rawContacts)779         public static ContentValues[] getContentValues(int... rawContacts) {
780             ArrayList<ContentValues> cv = new ArrayList<ContentValues>();
781             for (int i = 0; i < rawContacts.length; i++) {
782                 switch (rawContacts[i]) {
783                     case 0:
784                         // rawContact 0 "Hot Tamale" contains ContentValues 0, 1, and 2
785                         cv.add(sContentValues[0]);
786                         cv.add(sContentValues[1]);
787                         cv.add(sContentValues[2]);
788                         break;
789                     case 1:
790                         // rawContact 1 "Cold Tamago" contains ContentValues 3
791                         cv.add(sContentValues[3]);
792                         break;
793                     case 2:
794                         // rawContact 1 "John Doe" contains ContentValues 4, 5
795                         cv.add(sContentValues[4]);
796                         cv.add(sContentValues[5]);
797                         break;
798                 }
799             }
800             ContentValues[] toReturn = new ContentValues[cv.size()];
801             for (int i = 0; i < cv.size(); i++) {
802                 toReturn[i] = cv.get(i);
803             }
804             return toReturn;
805         }
806     }
807 }
808 
809