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