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 import android.content.ContentProviderClient;
20 import android.content.ContentResolver;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.net.Uri;
28 import android.os.SystemClock;
29 import android.provider.ContactsContract;
30 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
31 import android.provider.ContactsContract.Contacts;
32 import android.provider.ContactsContract.Directory;
33 import android.provider.ContactsContract.RawContacts;
34 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
35 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
36 import android.provider.cts.contacts.account.StaticAccountAuthenticator;
37 import android.test.AndroidTestCase;
38 
39 import java.util.List;
40 
41 public class ContactsContract_ContactsTest extends AndroidTestCase {
42 
43     private StaticAccountAuthenticator mAuthenticator;
44     private ContentResolver mResolver;
45     private ContactsContract_TestDataBuilder mBuilder;
46 
47     @Override
setUp()48     protected void setUp() throws Exception {
49         super.setUp();
50         mResolver = getContext().getContentResolver();
51         ContentProviderClient provider =
52                 mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
53         mBuilder = new ContactsContract_TestDataBuilder(provider);
54 
55         mAuthenticator = new StaticAccountAuthenticator(getContext());
56     }
57 
58     @Override
tearDown()59     protected void tearDown() throws Exception {
60         super.tearDown();
61         mBuilder.cleanup();
62     }
63 
testMarkAsContacted()64     public void testMarkAsContacted() throws Exception {
65         TestRawContact rawContact = mBuilder.newRawContact().insert().load();
66         TestContact contact = rawContact.getContact().load();
67 
68         assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
69         assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
70 
71         assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
72         assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
73 
74         // Note we no longer support contact affinity as of Q, so times_contacted and
75         // last_time_contacted are always 0.
76 
77         for (int i = 1; i < 10; i++) {
78             Contacts.markAsContacted(mResolver, contact.getId());
79             contact.load();
80             rawContact.load();
81 
82             assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
83             assertEquals("#" + i, 0, contact.getLong(Contacts.TIMES_CONTACTED));
84 
85             assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
86             assertEquals("#" + i, 0, rawContact.getLong(Contacts.TIMES_CONTACTED));
87         }
88     }
89 
testContentUri()90     public void testContentUri() {
91         Context context = getContext();
92         PackageManager packageManager = context.getPackageManager();
93         Intent intent = new Intent(Intent.ACTION_VIEW, ContactsContract.Contacts.CONTENT_URI);
94         List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
95         assertFalse("Device does not support the activity intent: " + intent,
96                 resolveInfos.isEmpty());
97     }
98 
testLookupUri()99     public void testLookupUri() throws Exception {
100         TestRawContact rawContact = mBuilder.newRawContact().insert().load();
101         TestContact contact = rawContact.getContact().load();
102 
103         Uri contactUri = contact.getUri();
104         long contactId = contact.getId();
105         String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
106 
107         Uri lookupUri = Contacts.getLookupUri(contactId, lookupKey);
108         assertEquals(ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
109                 lookupKey), contactId), lookupUri);
110 
111         Uri nullLookupUri = Contacts.getLookupUri(contactId, null);
112         assertNull(nullLookupUri);
113 
114         Uri emptyLookupUri = Contacts.getLookupUri(contactId, "");
115         assertNull(emptyLookupUri);
116 
117         Uri lookupUri2 = Contacts.getLookupUri(mResolver, contactUri);
118         assertEquals(lookupUri, lookupUri2);
119 
120         Uri contactUri2 = Contacts.lookupContact(mResolver, lookupUri);
121         assertEquals(contactUri, contactUri2);
122     }
123 
testInsert_isUnsupported()124     public void testInsert_isUnsupported() {
125         DatabaseAsserts.assertInsertIsUnsupported(mResolver, Contacts.CONTENT_URI);
126     }
127 
testContactDelete_removesContactRecord()128     public void testContactDelete_removesContactRecord() {
129         assertContactCreateDelete();
130     }
131 
testContactDelete_hasDeleteLog()132     public void testContactDelete_hasDeleteLog() {
133         long start = System.currentTimeMillis();
134         DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
135         DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, start);
136 
137         // Clean up. Must also remove raw contact.
138         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
139     }
140 
testContactDelete_marksRawContactsForDeletion()141     public void testContactDelete_marksRawContactsForDeletion() {
142         DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
143 
144         String[] projection = new String[] {
145                 RawContacts.DIRTY,
146                 RawContacts.DELETED
147         };
148         List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
149                 projection);
150         for (String[] arr : records) {
151             assertEquals("1", arr[0]);
152             assertEquals("1", arr[1]);
153         }
154 
155         // Clean up
156         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
157     }
158 
testContactDelete_localContactDeletedImmediately()159     public void testContactDelete_localContactDeletedImmediately() {
160         // Create a raw contact in the local (null) account
161         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(
162                 mResolver, null);
163 
164         ContactUtil.delete(mResolver, ids.mContactId);
165 
166         // Assert that the local raw contact is removed from the database and
167         // not merely marked DELETED=1.
168         assertNull(RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId, null));
169 
170         // Nothing to clean up
171     }
172 
testContactDelete_allLocalContactsDeletedImmediately()173     public void testContactDelete_allLocalContactsDeletedImmediately() {
174         // Create two raw contacts in the local (null) account
175         DatabaseAsserts.ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(
176                 mResolver, null, "John Smith");
177         DatabaseAsserts.ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(
178                 mResolver, null, "John Smith");
179 
180         // Aggregate the two raw contacts together
181         ContactUtil.setAggregationException(mResolver,
182                 ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER, ids1.mRawContactId,
183                 ids2.mRawContactId);
184 
185         // Assert that the contacts were aggregated together
186         long contactId1 = RawContactUtil.queryContactIdByRawContactId(mResolver,
187                 ids1.mRawContactId);
188         long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver,
189                 ids2.mRawContactId);
190         assertEquals(contactId1, contactId2);
191 
192         // Delete the contact
193         ContactUtil.delete(mResolver, contactId1);
194 
195         // Assert that both of the local raw contacts were removed from the database and
196         // not merely marked DELETED=1.
197         assertNull(RawContactUtil.queryByRawContactId(mResolver, ids1.mRawContactId, null));
198         assertNull(RawContactUtil.queryByRawContactId(mResolver, ids2.mRawContactId, null));
199 
200         // Nothing to clean up
201     }
202 
testContactDelete_localContactDeletedImmediatelyWhenAggregatedWithNonLocal()203     public void testContactDelete_localContactDeletedImmediatelyWhenAggregatedWithNonLocal() {
204         // Create a raw contact in the local (null) account
205         DatabaseAsserts.ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(
206                 mResolver, null, "John Smith");
207 
208         // Create a raw contact in a non-local account with the same name
209         DatabaseAsserts.ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(
210                 mResolver, StaticAccountAuthenticator.ACCOUNT_1, "John Smith");
211 
212         // Aggregate the two raw contacts together
213         ContactUtil.setAggregationException(mResolver,
214                 ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER, ids1.mRawContactId,
215                 ids2.mRawContactId);
216 
217         // Assert that the contacts were aggregated together
218         long contactId1 = RawContactUtil.queryContactIdByRawContactId(mResolver,
219                 ids1.mRawContactId);
220         long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver,
221                 ids2.mRawContactId);
222         assertEquals(contactId1, contactId2);
223 
224         // Delete the contact
225         ContactUtil.delete(mResolver, contactId1);
226 
227         // Assert that the local raw contact was removed from the database
228         assertNull(RawContactUtil.queryByRawContactId(mResolver, ids1.mRawContactId, null));
229 
230         // Assert that the non-local raw contact was marked DELETED=1
231         String[] projection = new String[]{
232                 RawContacts.DIRTY,
233                 RawContacts.DELETED
234         };
235         List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids2.mContactId,
236                 projection);
237         for (String[] arr : records) {
238             assertEquals("1", arr[0]);
239             assertEquals("1", arr[1]);
240         }
241 
242         // Clean up
243         RawContactUtil.delete(mResolver, ids2.mRawContactId, true);
244     }
245 
testContactUpdate_updatesContactUpdatedTimestamp()246     public void testContactUpdate_updatesContactUpdatedTimestamp() {
247         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
248 
249         long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
250 
251         ContentValues values = new ContentValues();
252         values.put(ContactsContract.Contacts.STARRED, 1);
253 
254         SystemClock.sleep(1);
255         ContactUtil.update(mResolver, ids.mContactId, values);
256 
257         long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
258         assertTrue(newTime > baseTime);
259 
260         // Clean up
261         RawContactUtil.delete(mResolver, ids.mRawContactId, true);
262     }
263 
264     /**
265      * Note we no longer support contact affinity as of Q, so times_contacted and
266      * last_time_contacted are always 0.
267      */
testContactUpdate_usageStats()268     public void testContactUpdate_usageStats() throws Exception {
269         final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
270         final TestContact contact = rawContact.getContact().load();
271 
272         contact.load();
273         assertEquals(0L, contact.getLong(Contacts.TIMES_CONTACTED));
274         assertEquals(0L, contact.getLong(Contacts.LAST_TIME_CONTACTED));
275 
276         final long now = System.currentTimeMillis();
277 
278         ContentValues values = new ContentValues();
279         values.clear();
280         values.put(Contacts.TIMES_CONTACTED, 3);
281         values.put(Contacts.LAST_TIME_CONTACTED, now);
282         ContactUtil.update(mResolver, contact.getId(), values);
283 
284         contact.load();
285         assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
286         assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
287 
288         // This is also the same as markAsContacted().
289         values.clear();
290         values.put(Contacts.LAST_TIME_CONTACTED, now);
291         ContactUtil.update(mResolver, contact.getId(), values);
292 
293         contact.load();
294         assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
295         assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
296 
297         values.clear();
298         values.put(Contacts.TIMES_CONTACTED, 10);
299 
300         ContactUtil.update(mResolver, contact.getId(), values);
301 
302         contact.load();
303         assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
304         assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
305     }
306 
307     /**
308      * Make sure the rounded usage stats values are also what the callers would see in where
309      * clauses.
310      *
311      * This tests both contacts and raw_contacts.
312      */
testContactUpdateDelete_usageStats_visibilityInWhere()313     public void testContactUpdateDelete_usageStats_visibilityInWhere() throws Exception {
314         final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
315         final TestContact contact = rawContact.getContact().load();
316 
317         // To make things more predictable, inline markAsContacted here with a known timestamp.
318         final long now = (System.currentTimeMillis() / 86400 * 86400) + 86400 * 5 + 123;
319 
320         ContentValues values = new ContentValues();
321         values.put(Contacts.LAST_TIME_CONTACTED, now);
322 
323         // This makes the internal TIMES_CONTACTED 35.  But the visible value is still 30.
324         for (int i = 0; i < 35; i++) {
325             ContactUtil.update(mResolver, contact.getId(), values);
326         }
327 
328         contact.load();
329         rawContact.load();
330 
331         assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
332         assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
333 
334         assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
335         assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
336     }
337 
338     /** Make sure local contacts are visible by default. */
testContactQuery_localContactVisibleByDefault()339     public void testContactQuery_localContactVisibleByDefault() throws Exception {
340         // Raw contacts without an account specified are created in the local account
341         final TestRawContact localRawContact = mBuilder.newRawContact().insert().load();
342         final TestContact contact = localRawContact.getContact().load();
343 
344         assertEquals(RawContacts.getLocalAccountName(mContext),
345                 localRawContact.getString(RawContacts.ACCOUNT_NAME));
346         assertEquals(RawContacts.getLocalAccountType(mContext),
347                 localRawContact.getString(RawContacts.ACCOUNT_TYPE));
348         assertNull(localRawContact.getString(RawContacts.DATA_SET));
349         assertEquals(1, contact.getLong(Contacts.IN_VISIBLE_GROUP));
350     }
351 
testProjection()352     public void testProjection() throws Exception {
353         final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
354         rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
355                 .with(StructuredName.GIVEN_NAME, "xxx")
356                 .insert();
357 
358         final TestContact contact = rawContact.getContact().load();
359         final long contactId = contact.getId();
360         final String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
361 
362         final String[] PROJECTION = new String[]{
363                 Contacts._ID,
364                 Contacts.DISPLAY_NAME,
365                 Contacts.DISPLAY_NAME_PRIMARY,
366                 Contacts.DISPLAY_NAME_ALTERNATIVE,
367                 Contacts.DISPLAY_NAME_SOURCE,
368                 Contacts.PHONETIC_NAME,
369                 Contacts.PHONETIC_NAME_STYLE,
370                 Contacts.SORT_KEY_PRIMARY,
371                 Contacts.SORT_KEY_ALTERNATIVE,
372                 Contacts.LAST_TIME_CONTACTED,
373                 Contacts.TIMES_CONTACTED,
374                 Contacts.STARRED,
375                 Contacts.PINNED,
376                 Contacts.IN_DEFAULT_DIRECTORY,
377                 Contacts.IN_VISIBLE_GROUP,
378                 Contacts.PHOTO_ID,
379                 Contacts.PHOTO_FILE_ID,
380                 Contacts.PHOTO_URI,
381                 Contacts.PHOTO_THUMBNAIL_URI,
382                 Contacts.CUSTOM_RINGTONE,
383                 Contacts.HAS_PHONE_NUMBER,
384                 Contacts.SEND_TO_VOICEMAIL,
385                 Contacts.IS_USER_PROFILE,
386                 Contacts.LOOKUP_KEY,
387                 Contacts.NAME_RAW_CONTACT_ID,
388                 Contacts.CONTACT_PRESENCE,
389                 Contacts.CONTACT_CHAT_CAPABILITY,
390                 Contacts.CONTACT_STATUS,
391                 Contacts.CONTACT_STATUS_TIMESTAMP,
392                 Contacts.CONTACT_STATUS_RES_PACKAGE,
393                 Contacts.CONTACT_STATUS_LABEL,
394                 Contacts.CONTACT_STATUS_ICON,
395                 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
396         };
397 
398         // Contacts.CONTENT_URI
399         DatabaseAsserts.checkProjection(mResolver,
400                 Contacts.CONTENT_URI,
401                 PROJECTION,
402                 new long[]{contact.getId()}
403         );
404 
405         // Contacts.CONTENT_FILTER_URI
406         DatabaseAsserts.checkProjection(mResolver,
407                 Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx").build(),
408                 PROJECTION,
409                 new long[]{contact.getId()}
410         );
411 
412         // Contacts.CONTENT_FILTER_URI
413         DatabaseAsserts.checkProjection(mResolver,
414                 Contacts.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx")
415                         .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
416                                 String.valueOf(Directory.DEFAULT)).build(),
417                 PROJECTION,
418                 new long[]{contact.getId()}
419         );
420 
421         // Contacts.CONTENT_LOOKUP_URI
422         DatabaseAsserts.checkProjection(mResolver,
423                 Contacts.getLookupUri(contactId, lookupKey),
424                 PROJECTION,
425                 new long[]{contact.getId()}
426         );
427     }
428 
429     /**
430      * Create a contact.  Delete it.  And assert that the contact record is no longer present.
431      *
432      * @return The contact id and raw contact id that was created.
433      */
assertContactCreateDelete()434     private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
435         DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
436 
437         SystemClock.sleep(1);
438         ContactUtil.delete(mResolver, ids.mContactId);
439 
440         assertFalse(ContactUtil.recordExistsForContactId(mResolver, ids.mContactId));
441 
442         return ids;
443     }
444 }
445