ContactsProvider2Test.java revision 45ae7eaf0e2c9459ccbeeb5eb5977f055c4ed8ec
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.providers.contacts;
18
19import com.android.common.contacts.DataUsageStatUpdater;
20import com.android.internal.util.ArrayUtils;
21import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
22import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
23import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
24import com.android.providers.contacts.tests.R;
25import com.google.android.collect.Lists;
26
27import android.accounts.Account;
28import android.content.ContentProviderOperation;
29import android.content.ContentProviderResult;
30import android.content.ContentUris;
31import android.content.ContentValues;
32import android.content.Entity;
33import android.content.EntityIterator;
34import android.content.res.AssetFileDescriptor;
35import android.database.Cursor;
36import android.database.sqlite.SQLiteDatabase;
37import android.net.Uri;
38import android.provider.ContactsContract;
39import android.provider.ContactsContract.AggregationExceptions;
40import android.provider.ContactsContract.CommonDataKinds.Email;
41import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
42import android.provider.ContactsContract.CommonDataKinds.Im;
43import android.provider.ContactsContract.CommonDataKinds.Organization;
44import android.provider.ContactsContract.CommonDataKinds.Phone;
45import android.provider.ContactsContract.CommonDataKinds.Photo;
46import android.provider.ContactsContract.CommonDataKinds.StructuredName;
47import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
48import android.provider.ContactsContract.ContactCounts;
49import android.provider.ContactsContract.Contacts;
50import android.provider.ContactsContract.Data;
51import android.provider.ContactsContract.DataUsageFeedback;
52import android.provider.ContactsContract.Directory;
53import android.provider.ContactsContract.DisplayNameSources;
54import android.provider.ContactsContract.DisplayPhoto;
55import android.provider.ContactsContract.FullNameStyle;
56import android.provider.ContactsContract.Groups;
57import android.provider.ContactsContract.PhoneLookup;
58import android.provider.ContactsContract.PhoneticNameStyle;
59import android.provider.ContactsContract.Profile;
60import android.provider.ContactsContract.ProviderStatus;
61import android.provider.ContactsContract.RawContacts;
62import android.provider.ContactsContract.RawContactsEntity;
63import android.provider.ContactsContract.SearchSnippetColumns;
64import android.provider.ContactsContract.Settings;
65import android.provider.ContactsContract.StatusUpdates;
66import android.provider.ContactsContract.StreamItemPhotos;
67import android.provider.ContactsContract.StreamItems;
68import android.provider.LiveFolders;
69import android.provider.OpenableColumns;
70import android.test.MoreAsserts;
71import android.test.suitebuilder.annotation.LargeTest;
72import android.text.TextUtils;
73
74import java.io.FileInputStream;
75import java.io.IOException;
76import java.io.InputStream;
77import java.io.OutputStream;
78import java.text.Collator;
79import java.util.ArrayList;
80import java.util.Arrays;
81import java.util.List;
82import java.util.Locale;
83
84/**
85 * Unit tests for {@link ContactsProvider2}.
86 *
87 * Run the test like this:
88 * <code>
89 * adb shell am instrument -e class com.android.providers.contacts.ContactsProvider2Test -w \
90 *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
91 * </code>
92 */
93@LargeTest
94public class ContactsProvider2Test extends BaseContactsProvider2Test {
95
96    private static final Account ACCOUNT_1 = new Account("account_name_1", "account_type_1");
97    private static final Account ACCOUNT_2 = new Account("account_name_2", "account_type_2");
98
99    public void testContactsProjection() {
100        assertProjection(Contacts.CONTENT_URI, new String[]{
101                Contacts._ID,
102                Contacts.DISPLAY_NAME_PRIMARY,
103                Contacts.DISPLAY_NAME_ALTERNATIVE,
104                Contacts.DISPLAY_NAME_SOURCE,
105                Contacts.PHONETIC_NAME,
106                Contacts.PHONETIC_NAME_STYLE,
107                Contacts.SORT_KEY_PRIMARY,
108                Contacts.SORT_KEY_ALTERNATIVE,
109                Contacts.LAST_TIME_CONTACTED,
110                Contacts.TIMES_CONTACTED,
111                Contacts.STARRED,
112                Contacts.IN_VISIBLE_GROUP,
113                Contacts.PHOTO_ID,
114                Contacts.PHOTO_FILE_ID,
115                Contacts.PHOTO_URI,
116                Contacts.PHOTO_THUMBNAIL_URI,
117                Contacts.CUSTOM_RINGTONE,
118                Contacts.HAS_PHONE_NUMBER,
119                Contacts.SEND_TO_VOICEMAIL,
120                Contacts.IS_USER_PROFILE,
121                Contacts.LOOKUP_KEY,
122                Contacts.NAME_RAW_CONTACT_ID,
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        });
131    }
132
133    public void testContactsWithSnippetProjection() {
134        assertProjection(Contacts.CONTENT_FILTER_URI.buildUpon().appendPath("nothing").build(),
135            new String[]{
136                Contacts._ID,
137                Contacts.DISPLAY_NAME_PRIMARY,
138                Contacts.DISPLAY_NAME_ALTERNATIVE,
139                Contacts.DISPLAY_NAME_SOURCE,
140                Contacts.PHONETIC_NAME,
141                Contacts.PHONETIC_NAME_STYLE,
142                Contacts.SORT_KEY_PRIMARY,
143                Contacts.SORT_KEY_ALTERNATIVE,
144                Contacts.LAST_TIME_CONTACTED,
145                Contacts.TIMES_CONTACTED,
146                Contacts.STARRED,
147                Contacts.IN_VISIBLE_GROUP,
148                Contacts.PHOTO_ID,
149                Contacts.PHOTO_FILE_ID,
150                Contacts.PHOTO_URI,
151                Contacts.PHOTO_THUMBNAIL_URI,
152                Contacts.CUSTOM_RINGTONE,
153                Contacts.HAS_PHONE_NUMBER,
154                Contacts.SEND_TO_VOICEMAIL,
155                Contacts.IS_USER_PROFILE,
156                Contacts.LOOKUP_KEY,
157                Contacts.NAME_RAW_CONTACT_ID,
158                Contacts.CONTACT_PRESENCE,
159                Contacts.CONTACT_CHAT_CAPABILITY,
160                Contacts.CONTACT_STATUS,
161                Contacts.CONTACT_STATUS_TIMESTAMP,
162                Contacts.CONTACT_STATUS_RES_PACKAGE,
163                Contacts.CONTACT_STATUS_LABEL,
164                Contacts.CONTACT_STATUS_ICON,
165
166                SearchSnippetColumns.SNIPPET,
167        });
168    }
169
170    public void testRawContactsProjection() {
171        assertProjection(RawContacts.CONTENT_URI, new String[]{
172                RawContacts._ID,
173                RawContacts.CONTACT_ID,
174                RawContacts.ACCOUNT_NAME,
175                RawContacts.ACCOUNT_TYPE,
176                RawContacts.SOURCE_ID,
177                RawContacts.VERSION,
178                RawContacts.RAW_CONTACT_IS_USER_PROFILE,
179                RawContacts.DIRTY,
180                RawContacts.DELETED,
181                RawContacts.DISPLAY_NAME_PRIMARY,
182                RawContacts.DISPLAY_NAME_ALTERNATIVE,
183                RawContacts.DISPLAY_NAME_SOURCE,
184                RawContacts.PHONETIC_NAME,
185                RawContacts.PHONETIC_NAME_STYLE,
186                RawContacts.NAME_VERIFIED,
187                RawContacts.SORT_KEY_PRIMARY,
188                RawContacts.SORT_KEY_ALTERNATIVE,
189                RawContacts.TIMES_CONTACTED,
190                RawContacts.LAST_TIME_CONTACTED,
191                RawContacts.CUSTOM_RINGTONE,
192                RawContacts.SEND_TO_VOICEMAIL,
193                RawContacts.STARRED,
194                RawContacts.AGGREGATION_MODE,
195                RawContacts.SYNC1,
196                RawContacts.SYNC2,
197                RawContacts.SYNC3,
198                RawContacts.SYNC4,
199        });
200    }
201
202    public void testDataProjection() {
203        assertProjection(Data.CONTENT_URI, new String[]{
204                Data._ID,
205                Data.RAW_CONTACT_ID,
206                Data.DATA_VERSION,
207                Data.IS_PRIMARY,
208                Data.IS_SUPER_PRIMARY,
209                Data.RES_PACKAGE,
210                Data.MIMETYPE,
211                Data.DATA1,
212                Data.DATA2,
213                Data.DATA3,
214                Data.DATA4,
215                Data.DATA5,
216                Data.DATA6,
217                Data.DATA7,
218                Data.DATA8,
219                Data.DATA9,
220                Data.DATA10,
221                Data.DATA11,
222                Data.DATA12,
223                Data.DATA13,
224                Data.DATA14,
225                Data.DATA15,
226                Data.SYNC1,
227                Data.SYNC2,
228                Data.SYNC3,
229                Data.SYNC4,
230                Data.CONTACT_ID,
231                Data.PRESENCE,
232                Data.CHAT_CAPABILITY,
233                Data.STATUS,
234                Data.STATUS_TIMESTAMP,
235                Data.STATUS_RES_PACKAGE,
236                Data.STATUS_LABEL,
237                Data.STATUS_ICON,
238                RawContacts.ACCOUNT_NAME,
239                RawContacts.ACCOUNT_TYPE,
240                RawContacts.SOURCE_ID,
241                RawContacts.VERSION,
242                RawContacts.DIRTY,
243                RawContacts.NAME_VERIFIED,
244                RawContacts.RAW_CONTACT_IS_USER_PROFILE,
245                Contacts._ID,
246                Contacts.DISPLAY_NAME_PRIMARY,
247                Contacts.DISPLAY_NAME_ALTERNATIVE,
248                Contacts.DISPLAY_NAME_SOURCE,
249                Contacts.PHONETIC_NAME,
250                Contacts.PHONETIC_NAME_STYLE,
251                Contacts.SORT_KEY_PRIMARY,
252                Contacts.SORT_KEY_ALTERNATIVE,
253                Contacts.LAST_TIME_CONTACTED,
254                Contacts.TIMES_CONTACTED,
255                Contacts.STARRED,
256                Contacts.IN_VISIBLE_GROUP,
257                Contacts.PHOTO_ID,
258                Contacts.PHOTO_FILE_ID,
259                Contacts.PHOTO_URI,
260                Contacts.PHOTO_THUMBNAIL_URI,
261                Contacts.CUSTOM_RINGTONE,
262                Contacts.SEND_TO_VOICEMAIL,
263                Contacts.LOOKUP_KEY,
264                Contacts.NAME_RAW_CONTACT_ID,
265                Contacts.HAS_PHONE_NUMBER,
266                Contacts.CONTACT_PRESENCE,
267                Contacts.CONTACT_CHAT_CAPABILITY,
268                Contacts.CONTACT_STATUS,
269                Contacts.CONTACT_STATUS_TIMESTAMP,
270                Contacts.CONTACT_STATUS_RES_PACKAGE,
271                Contacts.CONTACT_STATUS_LABEL,
272                Contacts.CONTACT_STATUS_ICON,
273                GroupMembership.GROUP_SOURCE_ID,
274        });
275    }
276
277    public void testDistinctDataProjection() {
278        assertProjection(Phone.CONTENT_FILTER_URI.buildUpon().appendPath("123").build(),
279            new String[]{
280                Data._ID,
281                Data.DATA_VERSION,
282                Data.IS_PRIMARY,
283                Data.IS_SUPER_PRIMARY,
284                Data.RES_PACKAGE,
285                Data.MIMETYPE,
286                Data.DATA1,
287                Data.DATA2,
288                Data.DATA3,
289                Data.DATA4,
290                Data.DATA5,
291                Data.DATA6,
292                Data.DATA7,
293                Data.DATA8,
294                Data.DATA9,
295                Data.DATA10,
296                Data.DATA11,
297                Data.DATA12,
298                Data.DATA13,
299                Data.DATA14,
300                Data.DATA15,
301                Data.SYNC1,
302                Data.SYNC2,
303                Data.SYNC3,
304                Data.SYNC4,
305                Data.CONTACT_ID,
306                Data.PRESENCE,
307                Data.CHAT_CAPABILITY,
308                Data.STATUS,
309                Data.STATUS_TIMESTAMP,
310                Data.STATUS_RES_PACKAGE,
311                Data.STATUS_LABEL,
312                Data.STATUS_ICON,
313                RawContacts.RAW_CONTACT_IS_USER_PROFILE,
314                Contacts._ID,
315                Contacts.DISPLAY_NAME_PRIMARY,
316                Contacts.DISPLAY_NAME_ALTERNATIVE,
317                Contacts.DISPLAY_NAME_SOURCE,
318                Contacts.PHONETIC_NAME,
319                Contacts.PHONETIC_NAME_STYLE,
320                Contacts.SORT_KEY_PRIMARY,
321                Contacts.SORT_KEY_ALTERNATIVE,
322                Contacts.LAST_TIME_CONTACTED,
323                Contacts.TIMES_CONTACTED,
324                Contacts.STARRED,
325                Contacts.IN_VISIBLE_GROUP,
326                Contacts.PHOTO_ID,
327                Contacts.PHOTO_FILE_ID,
328                Contacts.PHOTO_URI,
329                Contacts.PHOTO_THUMBNAIL_URI,
330                Contacts.HAS_PHONE_NUMBER,
331                Contacts.CUSTOM_RINGTONE,
332                Contacts.SEND_TO_VOICEMAIL,
333                Contacts.LOOKUP_KEY,
334                Contacts.CONTACT_PRESENCE,
335                Contacts.CONTACT_CHAT_CAPABILITY,
336                Contacts.CONTACT_STATUS,
337                Contacts.CONTACT_STATUS_TIMESTAMP,
338                Contacts.CONTACT_STATUS_RES_PACKAGE,
339                Contacts.CONTACT_STATUS_LABEL,
340                Contacts.CONTACT_STATUS_ICON,
341                GroupMembership.GROUP_SOURCE_ID,
342        });
343    }
344
345    public void testEntityProjection() {
346        assertProjection(
347            Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, 0),
348                    Contacts.Entity.CONTENT_DIRECTORY),
349            new String[]{
350                Contacts.Entity._ID,
351                Contacts.Entity.DATA_ID,
352                Contacts.Entity.RAW_CONTACT_ID,
353                Data.DATA_VERSION,
354                Data.IS_PRIMARY,
355                Data.IS_SUPER_PRIMARY,
356                Data.RES_PACKAGE,
357                Data.MIMETYPE,
358                Data.DATA1,
359                Data.DATA2,
360                Data.DATA3,
361                Data.DATA4,
362                Data.DATA5,
363                Data.DATA6,
364                Data.DATA7,
365                Data.DATA8,
366                Data.DATA9,
367                Data.DATA10,
368                Data.DATA11,
369                Data.DATA12,
370                Data.DATA13,
371                Data.DATA14,
372                Data.DATA15,
373                Data.SYNC1,
374                Data.SYNC2,
375                Data.SYNC3,
376                Data.SYNC4,
377                Data.CONTACT_ID,
378                Data.PRESENCE,
379                Data.CHAT_CAPABILITY,
380                Data.STATUS,
381                Data.STATUS_TIMESTAMP,
382                Data.STATUS_RES_PACKAGE,
383                Data.STATUS_LABEL,
384                Data.STATUS_ICON,
385                RawContacts.ACCOUNT_NAME,
386                RawContacts.ACCOUNT_TYPE,
387                RawContacts.SOURCE_ID,
388                RawContacts.VERSION,
389                RawContacts.DELETED,
390                RawContacts.DIRTY,
391                RawContacts.NAME_VERIFIED,
392                RawContacts.SYNC1,
393                RawContacts.SYNC2,
394                RawContacts.SYNC3,
395                RawContacts.SYNC4,
396                Contacts._ID,
397                Contacts.DISPLAY_NAME_PRIMARY,
398                Contacts.DISPLAY_NAME_ALTERNATIVE,
399                Contacts.DISPLAY_NAME_SOURCE,
400                Contacts.PHONETIC_NAME,
401                Contacts.PHONETIC_NAME_STYLE,
402                Contacts.SORT_KEY_PRIMARY,
403                Contacts.SORT_KEY_ALTERNATIVE,
404                Contacts.LAST_TIME_CONTACTED,
405                Contacts.TIMES_CONTACTED,
406                Contacts.STARRED,
407                Contacts.IN_VISIBLE_GROUP,
408                Contacts.PHOTO_ID,
409                Contacts.PHOTO_FILE_ID,
410                Contacts.PHOTO_URI,
411                Contacts.PHOTO_THUMBNAIL_URI,
412                Contacts.CUSTOM_RINGTONE,
413                Contacts.SEND_TO_VOICEMAIL,
414                Contacts.IS_USER_PROFILE,
415                Contacts.LOOKUP_KEY,
416                Contacts.NAME_RAW_CONTACT_ID,
417                Contacts.HAS_PHONE_NUMBER,
418                Contacts.CONTACT_PRESENCE,
419                Contacts.CONTACT_CHAT_CAPABILITY,
420                Contacts.CONTACT_STATUS,
421                Contacts.CONTACT_STATUS_TIMESTAMP,
422                Contacts.CONTACT_STATUS_RES_PACKAGE,
423                Contacts.CONTACT_STATUS_LABEL,
424                Contacts.CONTACT_STATUS_ICON,
425                GroupMembership.GROUP_SOURCE_ID,
426        });
427    }
428
429    public void testRawEntityProjection() {
430        assertProjection(RawContactsEntity.CONTENT_URI, new String[]{
431                RawContacts.Entity.DATA_ID,
432                RawContacts._ID,
433                RawContacts.CONTACT_ID,
434                RawContacts.ACCOUNT_NAME,
435                RawContacts.ACCOUNT_TYPE,
436                RawContacts.SOURCE_ID,
437                RawContacts.VERSION,
438                RawContacts.DIRTY,
439                RawContacts.NAME_VERIFIED,
440                RawContacts.DELETED,
441                RawContacts.SYNC1,
442                RawContacts.SYNC2,
443                RawContacts.SYNC3,
444                RawContacts.SYNC4,
445                RawContacts.STARRED,
446                RawContacts.RAW_CONTACT_IS_USER_PROFILE,
447                Data.DATA_VERSION,
448                Data.IS_PRIMARY,
449                Data.IS_SUPER_PRIMARY,
450                Data.RES_PACKAGE,
451                Data.MIMETYPE,
452                Data.DATA1,
453                Data.DATA2,
454                Data.DATA3,
455                Data.DATA4,
456                Data.DATA5,
457                Data.DATA6,
458                Data.DATA7,
459                Data.DATA8,
460                Data.DATA9,
461                Data.DATA10,
462                Data.DATA11,
463                Data.DATA12,
464                Data.DATA13,
465                Data.DATA14,
466                Data.DATA15,
467                Data.SYNC1,
468                Data.SYNC2,
469                Data.SYNC3,
470                Data.SYNC4,
471                GroupMembership.GROUP_SOURCE_ID,
472        });
473    }
474
475    public void testPhoneLookupProjection() {
476        assertProjection(PhoneLookup.CONTENT_FILTER_URI.buildUpon().appendPath("123").build(),
477            new String[]{
478                PhoneLookup._ID,
479                PhoneLookup.LOOKUP_KEY,
480                PhoneLookup.DISPLAY_NAME,
481                PhoneLookup.LAST_TIME_CONTACTED,
482                PhoneLookup.TIMES_CONTACTED,
483                PhoneLookup.STARRED,
484                PhoneLookup.IN_VISIBLE_GROUP,
485                PhoneLookup.PHOTO_ID,
486                PhoneLookup.PHOTO_URI,
487                PhoneLookup.PHOTO_THUMBNAIL_URI,
488                PhoneLookup.CUSTOM_RINGTONE,
489                PhoneLookup.HAS_PHONE_NUMBER,
490                PhoneLookup.SEND_TO_VOICEMAIL,
491                PhoneLookup.NUMBER,
492                PhoneLookup.TYPE,
493                PhoneLookup.LABEL,
494                PhoneLookup.NORMALIZED_NUMBER,
495        });
496    }
497
498    public void testGroupsProjection() {
499        assertProjection(Groups.CONTENT_URI, new String[]{
500                Groups._ID,
501                Groups.ACCOUNT_NAME,
502                Groups.ACCOUNT_TYPE,
503                Groups.SOURCE_ID,
504                Groups.DIRTY,
505                Groups.VERSION,
506                Groups.RES_PACKAGE,
507                Groups.TITLE,
508                Groups.TITLE_RES,
509                Groups.GROUP_VISIBLE,
510                Groups.SYSTEM_ID,
511                Groups.DELETED,
512                Groups.NOTES,
513                Groups.ACTION,
514                Groups.ACTION_URI,
515                Groups.SHOULD_SYNC,
516                Groups.FAVORITES,
517                Groups.AUTO_ADD,
518                Groups.GROUP_IS_READ_ONLY,
519                Groups.SYNC1,
520                Groups.SYNC2,
521                Groups.SYNC3,
522                Groups.SYNC4,
523        });
524    }
525
526    public void testGroupsSummaryProjection() {
527        assertProjection(Groups.CONTENT_SUMMARY_URI, new String[]{
528                Groups._ID,
529                Groups.ACCOUNT_NAME,
530                Groups.ACCOUNT_TYPE,
531                Groups.SOURCE_ID,
532                Groups.DIRTY,
533                Groups.VERSION,
534                Groups.RES_PACKAGE,
535                Groups.TITLE,
536                Groups.TITLE_RES,
537                Groups.GROUP_VISIBLE,
538                Groups.SYSTEM_ID,
539                Groups.DELETED,
540                Groups.NOTES,
541                Groups.ACTION,
542                Groups.ACTION_URI,
543                Groups.SHOULD_SYNC,
544                Groups.FAVORITES,
545                Groups.AUTO_ADD,
546                Groups.GROUP_IS_READ_ONLY,
547                Groups.SYNC1,
548                Groups.SYNC2,
549                Groups.SYNC3,
550                Groups.SYNC4,
551                Groups.SUMMARY_COUNT,
552                Groups.SUMMARY_WITH_PHONES,
553        });
554    }
555
556    public void testAggregateExceptionProjection() {
557        assertProjection(AggregationExceptions.CONTENT_URI, new String[]{
558                AggregationExceptionColumns._ID,
559                AggregationExceptions.TYPE,
560                AggregationExceptions.RAW_CONTACT_ID1,
561                AggregationExceptions.RAW_CONTACT_ID2,
562        });
563    }
564
565    public void testSettingsProjection() {
566        assertProjection(Settings.CONTENT_URI, new String[]{
567                Settings.ACCOUNT_NAME,
568                Settings.ACCOUNT_TYPE,
569                Settings.UNGROUPED_VISIBLE,
570                Settings.SHOULD_SYNC,
571                Settings.ANY_UNSYNCED,
572                Settings.UNGROUPED_COUNT,
573                Settings.UNGROUPED_WITH_PHONES,
574        });
575    }
576
577    public void testStatusUpdatesProjection() {
578        assertProjection(StatusUpdates.CONTENT_URI, new String[]{
579                PresenceColumns.RAW_CONTACT_ID,
580                StatusUpdates.DATA_ID,
581                StatusUpdates.IM_ACCOUNT,
582                StatusUpdates.IM_HANDLE,
583                StatusUpdates.PROTOCOL,
584                StatusUpdates.CUSTOM_PROTOCOL,
585                StatusUpdates.PRESENCE,
586                StatusUpdates.CHAT_CAPABILITY,
587                StatusUpdates.STATUS,
588                StatusUpdates.STATUS_TIMESTAMP,
589                StatusUpdates.STATUS_RES_PACKAGE,
590                StatusUpdates.STATUS_ICON,
591                StatusUpdates.STATUS_LABEL,
592        });
593    }
594
595    public void testLiveFoldersProjection() {
596        assertProjection(
597            Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "live_folders/contacts"),
598            new String[]{
599                LiveFolders._ID,
600                LiveFolders.NAME,
601        });
602    }
603
604    public void testDirectoryProjection() {
605        assertProjection(Directory.CONTENT_URI, new String[]{
606                Directory._ID,
607                Directory.PACKAGE_NAME,
608                Directory.TYPE_RESOURCE_ID,
609                Directory.DISPLAY_NAME,
610                Directory.DIRECTORY_AUTHORITY,
611                Directory.ACCOUNT_TYPE,
612                Directory.ACCOUNT_NAME,
613                Directory.EXPORT_SUPPORT,
614                Directory.SHORTCUT_SUPPORT,
615                Directory.PHOTO_SUPPORT,
616        });
617    }
618
619    public void testRawContactsInsert() {
620        ContentValues values = new ContentValues();
621
622        values.put(RawContacts.ACCOUNT_NAME, "a");
623        values.put(RawContacts.ACCOUNT_TYPE, "b");
624        values.put(RawContacts.SOURCE_ID, "c");
625        values.put(RawContacts.VERSION, 42);
626        values.put(RawContacts.DIRTY, 1);
627        values.put(RawContacts.DELETED, 1);
628        values.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
629        values.put(RawContacts.CUSTOM_RINGTONE, "d");
630        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
631        values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
632        values.put(RawContacts.STARRED, 1);
633        values.put(RawContacts.SYNC1, "e");
634        values.put(RawContacts.SYNC2, "f");
635        values.put(RawContacts.SYNC3, "g");
636        values.put(RawContacts.SYNC4, "h");
637
638        Uri rowUri = mResolver.insert(RawContacts.CONTENT_URI, values);
639        long rawContactId = ContentUris.parseId(rowUri);
640
641        assertStoredValues(rowUri, values);
642        assertSelection(RawContacts.CONTENT_URI, values, RawContacts._ID, rawContactId);
643        assertNetworkNotified(true);
644    }
645
646    public void testDataDirectoryWithLookupUri() {
647        ContentValues values = new ContentValues();
648
649        long rawContactId = createRawContactWithName();
650        insertPhoneNumber(rawContactId, "555-GOOG-411");
651        insertEmail(rawContactId, "google@android.com");
652
653        long contactId = queryContactId(rawContactId);
654        String lookupKey = queryLookupKey(contactId);
655
656        // Complete and valid lookup URI
657        Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
658        Uri dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY);
659
660        assertDataRows(dataUri, values);
661
662        // Complete but stale lookup URI
663        lookupUri = ContactsContract.Contacts.getLookupUri(contactId + 1, lookupKey);
664        dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY);
665        assertDataRows(dataUri, values);
666
667        // Incomplete lookup URI (lookup key only, no contact ID)
668        dataUri = Uri.withAppendedPath(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
669                lookupKey), Contacts.Data.CONTENT_DIRECTORY);
670        assertDataRows(dataUri, values);
671    }
672
673    private void assertDataRows(Uri dataUri, ContentValues values) {
674        Cursor cursor = mResolver.query(dataUri, new String[]{ Data.DATA1 }, null, null, Data._ID);
675        assertEquals(3, cursor.getCount());
676        cursor.moveToFirst();
677        values.put(Data.DATA1, "John Doe");
678        assertCursorValues(cursor, values);
679
680        cursor.moveToNext();
681        values.put(Data.DATA1, "555-GOOG-411");
682        assertCursorValues(cursor, values);
683
684        cursor.moveToNext();
685        values.put(Data.DATA1, "google@android.com");
686        assertCursorValues(cursor, values);
687
688        cursor.close();
689    }
690
691    public void testContactEntitiesWithIdBasedUri() {
692        ContentValues values = new ContentValues();
693        Account account1 = new Account("act1", "actype1");
694        Account account2 = new Account("act2", "actype2");
695
696        long rawContactId1 = createRawContactWithName(account1);
697        insertImHandle(rawContactId1, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk");
698        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", StatusUpdates.IDLE, "Busy", 90,
699                StatusUpdates.CAPABILITY_HAS_CAMERA);
700
701        long rawContactId2 = createRawContact(account2);
702        setAggregationException(
703                AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
704
705        long contactId = queryContactId(rawContactId1);
706
707        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
708        Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
709
710        assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
711    }
712
713    public void testContactEntitiesWithLookupUri() {
714        ContentValues values = new ContentValues();
715        Account account1 = new Account("act1", "actype1");
716        Account account2 = new Account("act2", "actype2");
717
718        long rawContactId1 = createRawContactWithName(account1);
719        insertImHandle(rawContactId1, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk");
720        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", StatusUpdates.IDLE, "Busy", 90,
721                StatusUpdates.CAPABILITY_HAS_CAMERA);
722
723        long rawContactId2 = createRawContact(account2);
724        setAggregationException(
725                AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
726
727        long contactId = queryContactId(rawContactId1);
728        String lookupKey = queryLookupKey(contactId);
729
730        // First try with a matching contact ID
731        Uri contactLookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
732        Uri entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY);
733        assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
734
735        // Now try with a contact ID mismatch
736        contactLookupUri = ContactsContract.Contacts.getLookupUri(contactId + 1, lookupKey);
737        entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY);
738        assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
739
740        // Now try without an ID altogether
741        contactLookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
742        entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY);
743        assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2);
744    }
745
746    private void assertEntityRows(Uri entityUri, long contactId, long rawContactId1,
747            long rawContactId2) {
748        ContentValues values = new ContentValues();
749
750        Cursor cursor = mResolver.query(entityUri, null, null, null,
751                Contacts.Entity.RAW_CONTACT_ID + "," + Contacts.Entity.DATA_ID);
752        assertEquals(3, cursor.getCount());
753
754        // First row - name
755        cursor.moveToFirst();
756        values.put(Contacts.Entity.CONTACT_ID, contactId);
757        values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId1);
758        values.put(Contacts.Entity.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
759        values.put(Contacts.Entity.DATA1, "John Doe");
760        values.put(Contacts.Entity.ACCOUNT_NAME, "act1");
761        values.put(Contacts.Entity.ACCOUNT_TYPE, "actype1");
762        values.put(Contacts.Entity.DISPLAY_NAME, "John Doe");
763        values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John");
764        values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1);
765        values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
766        values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE);
767        values.put(Contacts.Entity.CONTACT_STATUS, "Busy");
768        values.putNull(Contacts.Entity.PRESENCE);
769        assertCursorValues(cursor, values);
770
771        // Second row - IM
772        cursor.moveToNext();
773        values.put(Contacts.Entity.CONTACT_ID, contactId);
774        values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId1);
775        values.put(Contacts.Entity.MIMETYPE, Im.CONTENT_ITEM_TYPE);
776        values.put(Contacts.Entity.DATA1, "gtalk");
777        values.put(Contacts.Entity.ACCOUNT_NAME, "act1");
778        values.put(Contacts.Entity.ACCOUNT_TYPE, "actype1");
779        values.put(Contacts.Entity.DISPLAY_NAME, "John Doe");
780        values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John");
781        values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1);
782        values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
783        values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE);
784        values.put(Contacts.Entity.CONTACT_STATUS, "Busy");
785        values.put(Contacts.Entity.PRESENCE, StatusUpdates.IDLE);
786        assertCursorValues(cursor, values);
787
788        // Third row - second raw contact, not data
789        cursor.moveToNext();
790        values.put(Contacts.Entity.CONTACT_ID, contactId);
791        values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId2);
792        values.putNull(Contacts.Entity.MIMETYPE);
793        values.putNull(Contacts.Entity.DATA_ID);
794        values.putNull(Contacts.Entity.DATA1);
795        values.put(Contacts.Entity.ACCOUNT_NAME, "act2");
796        values.put(Contacts.Entity.ACCOUNT_TYPE, "actype2");
797        values.put(Contacts.Entity.DISPLAY_NAME, "John Doe");
798        values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John");
799        values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1);
800        values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
801        values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE);
802        values.put(Contacts.Entity.CONTACT_STATUS, "Busy");
803        values.putNull(Contacts.Entity.PRESENCE);
804        assertCursorValues(cursor, values);
805
806        cursor.close();
807    }
808
809    public void testDataInsert() {
810        long rawContactId = createRawContactWithName("John", "Doe");
811
812        ContentValues values = new ContentValues();
813        putDataValues(values, rawContactId);
814        Uri dataUri = mResolver.insert(Data.CONTENT_URI, values);
815        long dataId = ContentUris.parseId(dataUri);
816
817        long contactId = queryContactId(rawContactId);
818        values.put(RawContacts.CONTACT_ID, contactId);
819        assertStoredValues(dataUri, values);
820
821        assertSelection(Data.CONTENT_URI, values, Data._ID, dataId);
822
823        // Access the same data through the directory under RawContacts
824        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
825        Uri rawContactDataUri =
826                Uri.withAppendedPath(rawContactUri, RawContacts.Data.CONTENT_DIRECTORY);
827        assertSelection(rawContactDataUri, values, Data._ID, dataId);
828
829        // Access the same data through the directory under Contacts
830        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
831        Uri contactDataUri = Uri.withAppendedPath(contactUri, Contacts.Data.CONTENT_DIRECTORY);
832        assertSelection(contactDataUri, values, Data._ID, dataId);
833        assertNetworkNotified(true);
834    }
835
836    public void testRawContactDataQuery() {
837        Account account1 = new Account("a", "b");
838        Account account2 = new Account("c", "d");
839        long rawContactId1 = createRawContact(account1);
840        Uri dataUri1 = insertStructuredName(rawContactId1, "John", "Doe");
841        long rawContactId2 = createRawContact(account2);
842        Uri dataUri2 = insertStructuredName(rawContactId2, "Jane", "Doe");
843
844        Uri uri1 = maybeAddAccountQueryParameters(dataUri1, account1);
845        Uri uri2 = maybeAddAccountQueryParameters(dataUri2, account2);
846        assertStoredValue(uri1, Data._ID, ContentUris.parseId(dataUri1)) ;
847        assertStoredValue(uri2, Data._ID, ContentUris.parseId(dataUri2)) ;
848    }
849
850    public void testPhonesQuery() {
851
852        ContentValues values = new ContentValues();
853        values.put(RawContacts.CUSTOM_RINGTONE, "d");
854        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
855        values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
856        values.put(RawContacts.TIMES_CONTACTED, 54321);
857        values.put(RawContacts.STARRED, 1);
858
859        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
860        long rawContactId = ContentUris.parseId(rawContactUri);
861
862        insertStructuredName(rawContactId, "Meghan", "Knox");
863        Uri uri = insertPhoneNumber(rawContactId, "18004664411");
864        long phoneId = ContentUris.parseId(uri);
865
866
867        long contactId = queryContactId(rawContactId);
868        values.clear();
869        values.put(Data._ID, phoneId);
870        values.put(Data.RAW_CONTACT_ID, rawContactId);
871        values.put(RawContacts.CONTACT_ID, contactId);
872        values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
873        values.put(Phone.NUMBER, "18004664411");
874        values.put(Phone.TYPE, Phone.TYPE_HOME);
875        values.putNull(Phone.LABEL);
876        values.put(Contacts.DISPLAY_NAME, "Meghan Knox");
877        values.put(Contacts.CUSTOM_RINGTONE, "d");
878        values.put(Contacts.SEND_TO_VOICEMAIL, 1);
879        values.put(Contacts.LAST_TIME_CONTACTED, 12345);
880        values.put(Contacts.TIMES_CONTACTED, 54321);
881        values.put(Contacts.STARRED, 1);
882
883        assertStoredValues(ContentUris.withAppendedId(Phone.CONTENT_URI, phoneId), values);
884        assertSelection(Phone.CONTENT_URI, values, Data._ID, phoneId);
885    }
886
887    public void testPhonesFilterQuery() {
888        long rawContactId1 = createRawContactWithName("Hot", "Tamale", ACCOUNT_1);
889        insertPhoneNumber(rawContactId1, "1-800-466-4411");
890
891        long rawContactId2 = createRawContactWithName("Chilled", "Guacamole", ACCOUNT_2);
892        insertPhoneNumber(rawContactId2, "1-800-466-5432");
893
894        Uri filterUri1 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "tamale");
895        ContentValues values = new ContentValues();
896        values.put(Contacts.DISPLAY_NAME, "Hot Tamale");
897        values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
898        values.put(Phone.NUMBER, "1-800-466-4411");
899        values.put(Phone.TYPE, Phone.TYPE_HOME);
900        values.putNull(Phone.LABEL);
901        assertStoredValuesWithProjection(filterUri1, values);
902
903        Uri filterUri2 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "1-800-GOOG-411");
904        assertStoredValues(filterUri2, values);
905
906        Uri filterUri3 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "18004664");
907        assertStoredValues(filterUri3, values);
908
909        Uri filterUri4 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "encilada");
910        assertEquals(0, getCount(filterUri4, null, null));
911
912        Uri filterUri5 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "*");
913        assertEquals(0, getCount(filterUri5, null, null));
914    }
915
916    public void testPhoneLookup() {
917        ContentValues values = new ContentValues();
918        values.put(RawContacts.CUSTOM_RINGTONE, "d");
919        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
920
921        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
922        long rawContactId = ContentUris.parseId(rawContactUri);
923
924        insertStructuredName(rawContactId, "Hot", "Tamale");
925        insertPhoneNumber(rawContactId, "18004664411");
926
927        Uri lookupUri1 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "8004664411");
928
929        values.clear();
930        values.put(PhoneLookup._ID, queryContactId(rawContactId));
931        values.put(PhoneLookup.DISPLAY_NAME, "Hot Tamale");
932        values.put(PhoneLookup.NUMBER, "18004664411");
933        values.put(PhoneLookup.TYPE, Phone.TYPE_HOME);
934        values.putNull(PhoneLookup.LABEL);
935        values.put(PhoneLookup.CUSTOM_RINGTONE, "d");
936        values.put(PhoneLookup.SEND_TO_VOICEMAIL, 1);
937        assertStoredValues(lookupUri1, values);
938
939        // In the context that 8004664411 is a valid number, "4664411" as a
940        // call id should not match to "8004664411"
941        Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "4664411");
942        assertEquals(0, getCount(lookupUri2, null, null));
943    }
944
945    public void testPhoneLookupUseCases() {
946        ContentValues values = new ContentValues();
947        Uri rawContactUri;
948        long rawContactId;
949        Uri lookupUri2;
950
951        values.put(RawContacts.CUSTOM_RINGTONE, "d");
952        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
953
954        // International format in contacts
955        rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
956        rawContactId = ContentUris.parseId(rawContactUri);
957
958        insertStructuredName(rawContactId, "Hot", "Tamale");
959        insertPhoneNumber(rawContactId, "+1-650-861-0000");
960
961        values.clear();
962
963        // match with international format
964        lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0000");
965        assertEquals(1, getCount(lookupUri2, null, null));
966
967        // match with national format
968        lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0000");
969        assertEquals(1, getCount(lookupUri2, null, null));
970
971        // National format in contacts
972        values.clear();
973        values.put(RawContacts.CUSTOM_RINGTONE, "d");
974        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
975        rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
976        rawContactId = ContentUris.parseId(rawContactUri);
977
978        insertStructuredName(rawContactId, "Hot1", "Tamale");
979        insertPhoneNumber(rawContactId, "650-861-0001");
980
981        values.clear();
982
983        // match with international format
984        lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0001");
985        assertEquals(2, getCount(lookupUri2, null, null));
986
987        // match with national format
988        lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0001");
989        assertEquals(2, getCount(lookupUri2, null, null));
990
991        // Local format in contacts
992        values.clear();
993        values.put(RawContacts.CUSTOM_RINGTONE, "d");
994        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
995        rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
996        rawContactId = ContentUris.parseId(rawContactUri);
997
998        insertStructuredName(rawContactId, "Hot2", "Tamale");
999        insertPhoneNumber(rawContactId, "861-0002");
1000
1001        values.clear();
1002
1003        // match with international format
1004        lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0002");
1005        assertEquals(1, getCount(lookupUri2, null, null));
1006
1007        // match with national format
1008        lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0002");
1009        assertEquals(1, getCount(lookupUri2, null, null));
1010    }
1011
1012    public void testPhoneUpdate() {
1013        ContentValues values = new ContentValues();
1014        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
1015        long rawContactId = ContentUris.parseId(rawContactUri);
1016
1017        insertStructuredName(rawContactId, "Hot", "Tamale");
1018        Uri phoneUri = insertPhoneNumber(rawContactId, "18004664411");
1019
1020        Uri lookupUri1 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "8004664411");
1021        assertStoredValue(lookupUri1, PhoneLookup.DISPLAY_NAME, "Hot Tamale");
1022
1023        values.clear();
1024        values.put(Phone.NUMBER, "18004664422");
1025        mResolver.update(phoneUri, values, null, null);
1026
1027        Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "8004664422");
1028        assertStoredValue(lookupUri2, PhoneLookup.DISPLAY_NAME, "Hot Tamale");
1029
1030        // Setting number to null will remove the phone lookup record
1031        values.clear();
1032        values.putNull(Phone.NUMBER);
1033        mResolver.update(phoneUri, values, null, null);
1034
1035        assertEquals(0, getCount(lookupUri2, null, null));
1036
1037        // Let's restore that phone lookup record
1038        values.clear();
1039        values.put(Phone.NUMBER, "18004664422");
1040        mResolver.update(phoneUri, values, null, null);
1041        assertStoredValue(lookupUri2, PhoneLookup.DISPLAY_NAME, "Hot Tamale");
1042        assertNetworkNotified(true);
1043    }
1044
1045    public void testEmailsQuery() {
1046        ContentValues values = new ContentValues();
1047        values.put(RawContacts.CUSTOM_RINGTONE, "d");
1048        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
1049        values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
1050        values.put(RawContacts.TIMES_CONTACTED, 54321);
1051        values.put(RawContacts.STARRED, 1);
1052
1053        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
1054        long rawContactId = ContentUris.parseId(rawContactUri);
1055
1056        insertStructuredName(rawContactId, "Meghan", "Knox");
1057        Uri uri = insertEmail(rawContactId, "meghan@acme.com");
1058        long emailId = ContentUris.parseId(uri);
1059
1060        long contactId = queryContactId(rawContactId);
1061        values.clear();
1062        values.put(Data._ID, emailId);
1063        values.put(Data.RAW_CONTACT_ID, rawContactId);
1064        values.put(RawContacts.CONTACT_ID, contactId);
1065        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1066        values.put(Email.DATA, "meghan@acme.com");
1067        values.put(Email.TYPE, Email.TYPE_HOME);
1068        values.putNull(Email.LABEL);
1069        values.put(Contacts.DISPLAY_NAME, "Meghan Knox");
1070        values.put(Contacts.CUSTOM_RINGTONE, "d");
1071        values.put(Contacts.SEND_TO_VOICEMAIL, 1);
1072        values.put(Contacts.LAST_TIME_CONTACTED, 12345);
1073        values.put(Contacts.TIMES_CONTACTED, 54321);
1074        values.put(Contacts.STARRED, 1);
1075
1076        assertStoredValues(ContentUris.withAppendedId(Email.CONTENT_URI, emailId), values);
1077        assertSelection(Email.CONTENT_URI, values, Data._ID, emailId);
1078    }
1079
1080    public void testEmailsLookupQuery() {
1081        long rawContactId = createRawContactWithName("Hot", "Tamale");
1082        insertEmail(rawContactId, "tamale@acme.com");
1083
1084        Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, "tamale@acme.com");
1085        ContentValues values = new ContentValues();
1086        values.put(Contacts.DISPLAY_NAME, "Hot Tamale");
1087        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1088        values.put(Email.DATA, "tamale@acme.com");
1089        values.put(Email.TYPE, Email.TYPE_HOME);
1090        values.putNull(Email.LABEL);
1091        assertStoredValues(filterUri1, values);
1092
1093        Uri filterUri2 = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, "Ta<TaMale@acme.com>");
1094        assertStoredValues(filterUri2, values);
1095
1096        Uri filterUri3 = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, "encilada@acme.com");
1097        assertEquals(0, getCount(filterUri3, null, null));
1098    }
1099
1100    public void testEmailsFilterQuery() {
1101        long rawContactId1 = createRawContactWithName("Hot", "Tamale", ACCOUNT_1);
1102        insertEmail(rawContactId1, "tamale@acme.com");
1103        insertEmail(rawContactId1, "tamale@acme.com");
1104
1105        long rawContactId2 = createRawContactWithName("Hot", "Tamale", ACCOUNT_2);
1106        insertEmail(rawContactId2, "tamale@acme.com");
1107
1108        Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "tam");
1109        ContentValues values = new ContentValues();
1110        values.put(Contacts.DISPLAY_NAME, "Hot Tamale");
1111        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1112        values.put(Email.DATA, "tamale@acme.com");
1113        values.put(Email.TYPE, Email.TYPE_HOME);
1114        values.putNull(Email.LABEL);
1115        assertStoredValuesWithProjection(filterUri1, values);
1116
1117        Uri filterUri2 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "hot");
1118        assertStoredValuesWithProjection(filterUri2, values);
1119
1120        Uri filterUri3 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "hot tamale");
1121        assertStoredValuesWithProjection(filterUri3, values);
1122
1123        Uri filterUri4 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "tamale@acme");
1124        assertStoredValuesWithProjection(filterUri4, values);
1125
1126        Uri filterUri5 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "encilada");
1127        assertEquals(0, getCount(filterUri5, null, null));
1128    }
1129
1130    /**
1131     * Tests if ContactsProvider2 returns addresses according to registration order.
1132     */
1133    public void testEmailFilterDefaultSortOrder() {
1134        long rawContactId1 = createRawContact();
1135        insertEmail(rawContactId1, "address1@email.com");
1136        insertEmail(rawContactId1, "address2@email.com");
1137        insertEmail(rawContactId1, "address3@email.com");
1138        ContentValues v1 = new ContentValues();
1139        v1.put(Email.ADDRESS, "address1@email.com");
1140        ContentValues v2 = new ContentValues();
1141        v2.put(Email.ADDRESS, "address2@email.com");
1142        ContentValues v3 = new ContentValues();
1143        v3.put(Email.ADDRESS, "address3@email.com");
1144
1145        Uri filterUri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address");
1146        assertStoredValuesOrderly(filterUri, new ContentValues[] { v1, v2, v3 });
1147    }
1148
1149    /**
1150     * Tests if ContactsProvider2 returns primary addresses before the other addresses.
1151     */
1152    public void testEmailFilterPrimaryAddress() {
1153        long rawContactId1 = createRawContact();
1154        insertEmail(rawContactId1, "address1@email.com");
1155        insertEmail(rawContactId1, "address2@email.com", true);
1156        ContentValues v1 = new ContentValues();
1157        v1.put(Email.ADDRESS, "address1@email.com");
1158        ContentValues v2 = new ContentValues();
1159        v2.put(Email.ADDRESS, "address2@email.com");
1160
1161        Uri filterUri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address");
1162        assertStoredValuesOrderly(filterUri, new ContentValues[] { v2, v1 });
1163    }
1164
1165    /**
1166     * Tests if ContactsProvider2 has email address associated with a primary account before the
1167     * other address.
1168     */
1169    public void testEmailFilterPrimaryAccount() {
1170        long rawContactId1 = createRawContact(ACCOUNT_1);
1171        insertEmail(rawContactId1, "account1@email.com");
1172        long rawContactId2 = createRawContact(ACCOUNT_2);
1173        insertEmail(rawContactId2, "account2@email.com");
1174        ContentValues v1 = new ContentValues();
1175        v1.put(Email.ADDRESS, "account1@email.com");
1176        ContentValues v2 = new ContentValues();
1177        v2.put(Email.ADDRESS, "account2@email.com");
1178
1179        Uri filterUri1 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc")
1180                .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_1.name)
1181                .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_TYPE, ACCOUNT_1.type)
1182                .build();
1183        assertStoredValuesOrderly(filterUri1, new ContentValues[] { v1, v2 });
1184
1185        Uri filterUri2 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc")
1186                .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_2.name)
1187                .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_TYPE, ACCOUNT_2.type)
1188                .build();
1189        assertStoredValuesOrderly(filterUri2, new ContentValues[] { v2, v1 });
1190
1191        // Just with PRIMARY_ACCOUNT_NAME
1192        Uri filterUri3 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc")
1193                .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_1.name)
1194                .build();
1195        assertStoredValuesOrderly(filterUri3, new ContentValues[] { v1, v2 });
1196
1197        Uri filterUri4 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc")
1198                .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_2.name)
1199                .build();
1200        assertStoredValuesOrderly(filterUri4, new ContentValues[] { v2, v1 });
1201    }
1202
1203    /** Tests {@link DataUsageFeedback} correctly promotes a data row instead of a raw contact. */
1204    public void testEmailFilterSortOrderWithFeedback() {
1205        long rawContactId1 = createRawContact();
1206        String address1 = "address1@email.com";
1207        insertEmail(rawContactId1, address1);
1208        long rawContactId2 = createRawContact();
1209        String address2 = "address2@email.com";
1210        insertEmail(rawContactId2, address2);
1211        String address3 = "address3@email.com";
1212        ContentUris.parseId(insertEmail(rawContactId2, address3));
1213
1214        ContentValues v1 = new ContentValues();
1215        v1.put(Email.ADDRESS, "address1@email.com");
1216        ContentValues v2 = new ContentValues();
1217        v2.put(Email.ADDRESS, "address2@email.com");
1218        ContentValues v3 = new ContentValues();
1219        v3.put(Email.ADDRESS, "address3@email.com");
1220
1221        Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address");
1222        Uri filterUri2 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("address")
1223                .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
1224                        DataUsageFeedback.USAGE_TYPE_CALL)
1225                .build();
1226        Uri filterUri3 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("address")
1227                .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
1228                        DataUsageFeedback.USAGE_TYPE_LONG_TEXT)
1229                .build();
1230        Uri filterUri4 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("address")
1231                .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
1232                        DataUsageFeedback.USAGE_TYPE_SHORT_TEXT)
1233                .build();
1234        assertStoredValuesOrderly(filterUri1, new ContentValues[] { v1, v2, v3 });
1235        assertStoredValuesOrderly(filterUri2, new ContentValues[] { v1, v2, v3 });
1236        assertStoredValuesOrderly(filterUri3, new ContentValues[] { v1, v2, v3 });
1237        assertStoredValuesOrderly(filterUri4, new ContentValues[] { v1, v2, v3 });
1238
1239        sendFeedback(address3, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, v3);
1240
1241        // account3@email.com should be the first. account2@email.com should also be promoted as
1242        // it has same contact id.
1243        assertStoredValuesOrderly(filterUri1, new ContentValues[] { v3, v1, v2 });
1244        assertStoredValuesOrderly(filterUri3, new ContentValues[] { v3, v1, v2 });
1245    }
1246
1247    /**
1248     * Tests {@link DataUsageFeedback} correctly bucketize contacts using each
1249     * {@link DataUsageStatColumns#LAST_TIME_USED}
1250     */
1251    public void testEmailFilterSortOrderWithOldHistory() {
1252        long rawContactId1 = createRawContact();
1253        long dataId1 = ContentUris.parseId(insertEmail(rawContactId1, "address1@email.com"));
1254        long dataId2 = ContentUris.parseId(insertEmail(rawContactId1, "address2@email.com"));
1255        long dataId3 = ContentUris.parseId(insertEmail(rawContactId1, "address3@email.com"));
1256        long dataId4 = ContentUris.parseId(insertEmail(rawContactId1, "address4@email.com"));
1257
1258        Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address");
1259
1260        ContentValues v1 = new ContentValues();
1261        v1.put(Email.ADDRESS, "address1@email.com");
1262        ContentValues v2 = new ContentValues();
1263        v2.put(Email.ADDRESS, "address2@email.com");
1264        ContentValues v3 = new ContentValues();
1265        v3.put(Email.ADDRESS, "address3@email.com");
1266        ContentValues v4 = new ContentValues();
1267        v4.put(Email.ADDRESS, "address4@email.com");
1268
1269        final ContactsProvider2 provider = (ContactsProvider2) getProvider();
1270
1271        long nowInMillis = System.currentTimeMillis();
1272        long yesterdayInMillis = (nowInMillis - 24 * 60 * 60 * 1000);
1273        long sevenDaysAgoInMillis = (nowInMillis - 7 * 24 * 60 * 60 * 1000);
1274        long oneYearAgoInMillis = (nowInMillis - 365L * 24 * 60 * 60 * 1000);
1275
1276        // address4 is contacted just once yesterday.
1277        provider.updateDataUsageStat(Arrays.asList(dataId4),
1278                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, yesterdayInMillis);
1279
1280        // address3 is contacted twice 1 week ago.
1281        provider.updateDataUsageStat(Arrays.asList(dataId3),
1282                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, sevenDaysAgoInMillis);
1283        provider.updateDataUsageStat(Arrays.asList(dataId3),
1284                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, sevenDaysAgoInMillis);
1285
1286        // address2 is contacted three times 1 year ago.
1287        provider.updateDataUsageStat(Arrays.asList(dataId2),
1288                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, oneYearAgoInMillis);
1289        provider.updateDataUsageStat(Arrays.asList(dataId2),
1290                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, oneYearAgoInMillis);
1291        provider.updateDataUsageStat(Arrays.asList(dataId2),
1292                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, oneYearAgoInMillis);
1293
1294        // auto-complete should prefer recently contacted methods
1295        assertStoredValuesOrderly(filterUri1, new ContentValues[] { v4, v3, v2, v1 });
1296
1297        // Pretend address2 is contacted right now
1298        provider.updateDataUsageStat(Arrays.asList(dataId2),
1299                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, nowInMillis);
1300
1301        // Now address2 is the most recently used address
1302        assertStoredValuesOrderly(filterUri1, new ContentValues[] { v2, v4, v3, v1 });
1303
1304        // Pretend address1 is contacted right now
1305        provider.updateDataUsageStat(Arrays.asList(dataId1),
1306                DataUsageFeedback.USAGE_TYPE_LONG_TEXT, nowInMillis);
1307
1308        // address2 is preferred to address1 as address2 is used 4 times in total
1309        assertStoredValuesOrderly(filterUri1, new ContentValues[] { v2, v1, v4, v3 });
1310    }
1311
1312    public void testPostalsQuery() {
1313        long rawContactId = createRawContactWithName("Alice", "Nextore");
1314        Uri dataUri = insertPostalAddress(rawContactId, "1600 Amphiteatre Ave, Mountain View");
1315        long dataId = ContentUris.parseId(dataUri);
1316
1317        long contactId = queryContactId(rawContactId);
1318        ContentValues values = new ContentValues();
1319        values.put(Data._ID, dataId);
1320        values.put(Data.RAW_CONTACT_ID, rawContactId);
1321        values.put(RawContacts.CONTACT_ID, contactId);
1322        values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
1323        values.put(StructuredPostal.FORMATTED_ADDRESS, "1600 Amphiteatre Ave, Mountain View");
1324        values.put(Contacts.DISPLAY_NAME, "Alice Nextore");
1325
1326        assertStoredValues(ContentUris.withAppendedId(StructuredPostal.CONTENT_URI, dataId),
1327                values);
1328        assertSelection(StructuredPostal.CONTENT_URI, values, Data._ID, dataId);
1329    }
1330
1331    public void testQueryContactData() {
1332        ContentValues values = new ContentValues();
1333        long contactId = createContact(values, "John", "Doe",
1334                "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
1335                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
1336        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1337
1338        assertStoredValues(contactUri, values);
1339        assertSelection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
1340    }
1341
1342    public void testQueryContactWithStatusUpdate() {
1343        ContentValues values = new ContentValues();
1344        long contactId = createContact(values, "John", "Doe",
1345                "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
1346                StatusUpdates.CAPABILITY_HAS_CAMERA);
1347        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
1348        values.put(Contacts.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
1349        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1350        assertStoredValuesWithProjection(contactUri, values);
1351        assertSelectionWithProjection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
1352    }
1353
1354    public void testQueryContactFilterByName() {
1355        ContentValues values = new ContentValues();
1356        long rawContactId = createRawContact(values, "18004664411",
1357                "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
1358                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO |
1359                StatusUpdates.CAPABILITY_HAS_VOICE);
1360
1361        ContentValues nameValues = new ContentValues();
1362        nameValues.put(StructuredName.GIVEN_NAME, "Stu");
1363        nameValues.put(StructuredName.FAMILY_NAME, "Goulash");
1364        nameValues.put(StructuredName.PHONETIC_FAMILY_NAME, "goo");
1365        nameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "LASH");
1366        Uri nameUri = insertStructuredName(rawContactId, nameValues);
1367
1368        long contactId = queryContactId(rawContactId);
1369        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
1370
1371        Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goulash");
1372        assertStoredValuesWithProjection(filterUri1, values);
1373
1374        assertContactFilter(contactId, "goolash");
1375        assertContactFilter(contactId, "lash");
1376
1377        assertContactFilterNoResult("goolish");
1378
1379        // Phonetic name with given/family reversed should not match
1380        assertContactFilterNoResult("lashgoo");
1381
1382        nameValues.clear();
1383        nameValues.put(StructuredName.PHONETIC_FAMILY_NAME, "ga");
1384        nameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "losh");
1385
1386        mResolver.update(nameUri, nameValues, null, null);
1387
1388        assertContactFilter(contactId, "galosh");
1389
1390        assertContactFilterNoResult("goolish");
1391    }
1392
1393    public void testQueryContactFilterByEmailAddress() {
1394        ContentValues values = new ContentValues();
1395        long rawContactId = createRawContact(values, "18004664411",
1396                "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
1397                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO |
1398                StatusUpdates.CAPABILITY_HAS_VOICE);
1399
1400        insertStructuredName(rawContactId, "James", "Bond");
1401
1402        long contactId = queryContactId(rawContactId);
1403        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
1404
1405        Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goog411@acme.com");
1406        assertStoredValuesWithProjection(filterUri1, values);
1407
1408        assertContactFilter(contactId, "goog");
1409        assertContactFilter(contactId, "goog411");
1410        assertContactFilter(contactId, "goog411@");
1411        assertContactFilter(contactId, "goog411@acme");
1412        assertContactFilter(contactId, "goog411@acme.com");
1413
1414        assertContactFilterNoResult("goog411@acme.combo");
1415        assertContactFilterNoResult("goog411@le.com");
1416        assertContactFilterNoResult("goolish");
1417    }
1418
1419    public void testQueryContactFilterByPhoneNumber() {
1420        ContentValues values = new ContentValues();
1421        long rawContactId = createRawContact(values, "18004664411",
1422                "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
1423                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO |
1424                StatusUpdates.CAPABILITY_HAS_VOICE);
1425
1426        insertStructuredName(rawContactId, "James", "Bond");
1427
1428        long contactId = queryContactId(rawContactId);
1429        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
1430
1431        Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "18004664411");
1432        assertStoredValuesWithProjection(filterUri1, values);
1433
1434        assertContactFilter(contactId, "18004664411");
1435        assertContactFilter(contactId, "1800466");
1436        assertContactFilter(contactId, "+18004664411");
1437        assertContactFilter(contactId, "8004664411");
1438
1439        assertContactFilterNoResult("78004664411");
1440        assertContactFilterNoResult("18004664412");
1441        assertContactFilterNoResult("8884664411");
1442    }
1443
1444    /**
1445     * Checks ContactsProvider2 works well with strequent Uris. The provider should return starred
1446     * contacts and frequently used contacts.
1447     */
1448    public void testQueryContactStrequent() {
1449        ContentValues values1 = new ContentValues();
1450        final String email1 = "a@acme.com";
1451        final int timesContacted1 = 0;
1452        createContact(values1, "Noah", "Tever", "18004664411",
1453                email1, StatusUpdates.OFFLINE, timesContacted1, 0, 0,
1454                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
1455        final String phoneNumber2 = "18004664412";
1456        ContentValues values2 = new ContentValues();
1457        createContact(values2, "Sam", "Times", phoneNumber2,
1458                "b@acme.com", StatusUpdates.INVISIBLE, 3, 0, 0,
1459                StatusUpdates.CAPABILITY_HAS_CAMERA);
1460        ContentValues values3 = new ContentValues();
1461        final String phoneNumber3 = "18004664413";
1462        final int timesContacted3 = 5;
1463        createContact(values3, "Lotta", "Calling", phoneNumber3,
1464                "c@acme.com", StatusUpdates.AWAY, timesContacted3, 0, 0,
1465                StatusUpdates.CAPABILITY_HAS_VIDEO);
1466        ContentValues values4 = new ContentValues();
1467        createContact(values4, "Fay", "Veritt", "18004664414",
1468                "d@acme.com", StatusUpdates.AVAILABLE, 0, 1, 0,
1469                StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE);
1470
1471        // Starred contacts should be returned. TIMES_CONTACTED should be ignored and only data
1472        // usage feedback should be used for "frequently contacted" listing.
1473        assertStoredValues(Contacts.CONTENT_STREQUENT_URI, values4);
1474
1475        // Send feedback for the 3rd phone number, pretending we called that person via phone.
1476        sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
1477
1478        // After the feedback, 3rd contact should be shown after starred one.
1479        assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
1480                new ContentValues[] { values4, values3 });
1481
1482        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
1483        // Twice.
1484        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
1485
1486        // After the feedback, 1st and 3rd contacts should be shown after starred one.
1487        assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
1488                new ContentValues[] { values4, values1, values3 });
1489
1490        // With phone-only parameter, the 1st contact shouldn't be returned, since it is only
1491        // about email, not phone-call.
1492        Uri phoneOnlyStrequentUri = Contacts.CONTENT_STREQUENT_URI.buildUpon()
1493                .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true")
1494                .build();
1495        assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values4, values3 });
1496
1497        // Send feedback for the 2rd phone number, pretending we send the person a SMS message.
1498        sendFeedback(phoneNumber2, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
1499
1500        // SMS feedback shouldn't affect phone-only results.
1501        assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values4, values3 });
1502
1503        Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_STREQUENT_FILTER_URI, "fay");
1504        assertStoredValues(filterUri, values4);
1505    }
1506
1507    /**
1508     * Checks ContactsProvider2 works well with frequent Uri. The provider should return frequently
1509     * contacted person ordered by number of times contacted.
1510     */
1511    public void testQueryContactFrequent() {
1512        ContentValues values1 = new ContentValues();
1513        final String email1 = "a@acme.com";
1514        createContact(values1, "Noah", "Tever", "18004664411",
1515                email1, StatusUpdates.OFFLINE, 0, 0, 0, 0);
1516        ContentValues values2 = new ContentValues();
1517        final String email2 = "b@acme.com";
1518        createContact(values2, "Sam", "Times", "18004664412",
1519                email2, StatusUpdates.INVISIBLE, 0, 0, 0, 0);
1520        ContentValues values3 = new ContentValues();
1521        final String phoneNumber3 = "18004664413";
1522        createContact(values3, "Lotta", "Calling", phoneNumber3,
1523                "c@acme.com", StatusUpdates.AWAY, 0, 0, 0, 0);
1524        ContentValues values4 = new ContentValues();
1525        createContact(values4, "Fay", "Veritt", "18004664414",
1526                "d@acme.com", StatusUpdates.AVAILABLE, 0, 1, 0, 0);
1527
1528        sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
1529
1530        assertStoredValues(Contacts.CONTENT_FREQUENT_URI, values1);
1531
1532        // Pretend email was sent to the address twice.
1533        sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
1534        sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
1535
1536        assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values2, values1});
1537
1538        // Three times
1539        sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
1540        sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
1541        sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
1542
1543        assertStoredValues(Contacts.CONTENT_FREQUENT_URI,
1544                new ContentValues[] {values3, values2, values1});
1545    }
1546
1547    public void testQueryContactGroup() {
1548        long groupId = createGroup(null, "testGroup", "Test Group");
1549
1550        ContentValues values1 = new ContentValues();
1551        createContact(values1, "Best", "West", "18004664411",
1552                "west@acme.com", StatusUpdates.OFFLINE, 0, 0, groupId,
1553                StatusUpdates.CAPABILITY_HAS_CAMERA);
1554
1555        ContentValues values2 = new ContentValues();
1556        createContact(values2, "Rest", "East", "18004664422",
1557                "east@acme.com", StatusUpdates.AVAILABLE, 0, 0, 0,
1558                StatusUpdates.CAPABILITY_HAS_VOICE);
1559
1560        Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, "Test Group");
1561        Cursor c = mResolver.query(filterUri1, null, null, null, Contacts._ID);
1562        assertEquals(1, c.getCount());
1563        c.moveToFirst();
1564        assertCursorValues(c, values1);
1565        c.close();
1566
1567        Uri filterUri2 = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, "Test Group");
1568        c = mResolver.query(filterUri2, null, Contacts.DISPLAY_NAME + "=?",
1569                new String[] { "Best West" }, Contacts._ID);
1570        assertEquals(1, c.getCount());
1571        c.close();
1572
1573        Uri filterUri3 = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, "Next Group");
1574        c = mResolver.query(filterUri3, null, null, null, Contacts._ID);
1575        assertEquals(0, c.getCount());
1576        c.close();
1577    }
1578
1579    public void testQueryProfileRequiresReadPermission() {
1580        mActor.removePermissions("android.permission.READ_PROFILE");
1581
1582        createBasicProfileContact(new ContentValues());
1583
1584        // Queries for the profile should fail.
1585        Cursor c = null;
1586
1587        // Case 1: Retrieving profile contact.
1588        try {
1589            c = mResolver.query(Profile.CONTENT_URI, null, null, null, Contacts._ID);
1590            fail("Querying for the profile without READ_PROFILE access should fail.");
1591        } catch (SecurityException expected) {
1592        } finally {
1593            if (c != null) {
1594                c.close();
1595            }
1596        }
1597
1598        // Case 2: Retrieving profile data.
1599        try {
1600            c = mResolver.query(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
1601                    null, null, null, Contacts._ID);
1602            fail("Querying for the profile data without READ_PROFILE access should fail.");
1603        } catch (SecurityException expected) {
1604        } finally {
1605            if (c != null) {
1606                c.close();
1607            }
1608        }
1609
1610        // Case 3: Retrieving profile entities.
1611        try {
1612            c = mResolver.query(Profile.CONTENT_URI.buildUpon()
1613                    .appendPath("entities").build(), null, null, null, Contacts._ID);
1614            fail("Querying for the profile entities without READ_PROFILE access should fail.");
1615        } catch (SecurityException expected) {
1616        } finally {
1617            if (c != null) {
1618                c.close();
1619            }
1620        }
1621    }
1622
1623    public void testQueryProfileByContactIdRequiresReadPermission() {
1624        long profileRawContactId = createBasicProfileContact(new ContentValues());
1625        long profileContactId = queryContactId(profileRawContactId);
1626
1627        mActor.removePermissions("android.permission.READ_PROFILE");
1628
1629        // A query for the profile contact by ID should fail.
1630        Cursor c = null;
1631        try {
1632            c = mResolver.query(ContentUris.withAppendedId(Contacts.CONTENT_URI, profileContactId),
1633                    null, null, null, Contacts._ID);
1634            fail("Querying for the profile by contact ID without READ_PROFILE access should fail.");
1635        } catch (SecurityException expected) {
1636        } finally {
1637            if (c != null) {
1638                c.close();
1639            }
1640        }
1641    }
1642
1643    public void testQueryProfileByRawContactIdRequiresReadPermission() {
1644        long profileRawContactId = createBasicProfileContact(new ContentValues());
1645
1646        // Remove profile read permission and attempt to retrieve the raw contact.
1647        mActor.removePermissions("android.permission.READ_PROFILE");
1648        Cursor c = null;
1649        try {
1650            c = mResolver.query(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
1651                    profileRawContactId), null, null, null, RawContacts._ID);
1652            fail("Querying for the raw contact profile without READ_PROFILE access should fail.");
1653        } catch (SecurityException expected) {
1654        } finally {
1655            if (c != null) {
1656                c.close();
1657            }
1658        }
1659    }
1660
1661    public void testQueryProfileRawContactRequiresReadPermission() {
1662        long profileRawContactId = createBasicProfileContact(new ContentValues());
1663
1664        // Remove profile read permission and attempt to retrieve the profile's raw contact data.
1665        mActor.removePermissions("android.permission.READ_PROFILE");
1666        Cursor c = null;
1667
1668        // Case 1: Retrieve the overall raw contact set for the profile.
1669        try {
1670            c = mResolver.query(Profile.CONTENT_RAW_CONTACTS_URI, null, null, null, null);
1671            fail("Querying for the raw contact profile without READ_PROFILE access should fail.");
1672        } catch (SecurityException expected) {
1673        } finally {
1674            if (c != null) {
1675                c.close();
1676            }
1677        }
1678
1679        // Case 2: Retrieve the raw contact profile data for the inserted raw contact ID.
1680        try {
1681            c = mResolver.query(ContentUris.withAppendedId(
1682                    Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
1683                    .appendPath("data").build(), null, null, null, null);
1684            fail("Querying for the raw profile data without READ_PROFILE access should fail.");
1685        } catch (SecurityException expected) {
1686        } finally {
1687            if (c != null) {
1688                c.close();
1689            }
1690        }
1691
1692        // Case 3: Retrieve the raw contact profile entity for the inserted raw contact ID.
1693        try {
1694            c = mResolver.query(ContentUris.withAppendedId(
1695                    Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
1696                    .appendPath("entity").build(), null, null, null, null);
1697            fail("Querying for the raw profile entities without READ_PROFILE access should fail.");
1698        } catch (SecurityException expected) {
1699        } finally {
1700            if (c != null) {
1701                c.close();
1702            }
1703        }
1704    }
1705
1706    public void testQueryProfileDataByDataIdRequiresReadPermission() {
1707        createBasicProfileContact(new ContentValues());
1708        Cursor c = mResolver.query(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
1709                new String[]{Data._ID, Data.MIMETYPE}, null, null, null);
1710        assertEquals(4, c.getCount());  // Photo, phone, email, name.
1711        c.moveToFirst();
1712        long profileDataId = c.getLong(0);
1713        c.close();
1714
1715        // Remove profile read permission and attempt to retrieve the data
1716        mActor.removePermissions("android.permission.READ_PROFILE");
1717        try {
1718            c = mResolver.query(ContentUris.withAppendedId(Data.CONTENT_URI, profileDataId),
1719                    null, null, null, null);
1720            fail("Querying for the data in the profile without READ_PROFILE access should fail.");
1721        } catch (SecurityException expected) {
1722        } finally {
1723            if (c != null) {
1724                c.close();
1725            }
1726        }
1727    }
1728
1729    public void testQueryProfileDataRequiresReadPermission() {
1730        createBasicProfileContact(new ContentValues());
1731
1732        // Remove profile read permission and attempt to retrieve all profile data.
1733        mActor.removePermissions("android.permission.READ_PROFILE");
1734        Cursor c = null;
1735        try {
1736            c = mResolver.query(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
1737                    null, null, null, null);
1738            fail("Querying for the data in the profile without READ_PROFILE access should fail.");
1739        } catch (SecurityException expected) {
1740        } finally {
1741            if (c != null) {
1742                c.close();
1743            }
1744        }
1745    }
1746
1747    public void testInsertProfileRequiresWritePermission() {
1748        mActor.removePermissions("android.permission.WRITE_PROFILE");
1749
1750        // Creating a non-profile contact should be fine.
1751        createBasicNonProfileContact(new ContentValues());
1752
1753        // Creating a profile contact should throw an exception.
1754        try {
1755            createBasicProfileContact(new ContentValues());
1756            fail("Creating a profile contact should fail without WRITE_PROFILE access.");
1757        } catch (SecurityException expected) {
1758        }
1759    }
1760
1761    public void testInsertProfileDataRequiresWritePermission() {
1762        long profileRawContactId = createBasicProfileContact(new ContentValues());
1763
1764        mActor.removePermissions("android.permission.WRITE_PROFILE");
1765        try {
1766            insertEmail(profileRawContactId, "foo@bar.net", false);
1767            fail("Inserting data into a profile contact should fail without WRITE_PROFILE access.");
1768        } catch (SecurityException expected) {
1769        }
1770    }
1771
1772    public void testUpdateDataDoesNotRequireProfilePermission() {
1773        mActor.removePermissions("android.permission.READ_PROFILE");
1774        mActor.removePermissions("android.permission.WRITE_PROFILE");
1775
1776        // Create a non-profile contact.
1777        long rawContactId = createRawContactWithName("Domo", "Arigato");
1778        long dataId = getStoredLongValue(Data.CONTENT_URI,
1779                Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
1780                new String[]{String.valueOf(rawContactId), StructuredName.CONTENT_ITEM_TYPE},
1781                Data._ID);
1782
1783        // Updates its name using a selection.
1784        ContentValues values = new ContentValues();
1785        values.put(StructuredName.GIVEN_NAME, "Bob");
1786        values.put(StructuredName.FAMILY_NAME, "Blob");
1787        mResolver.update(Data.CONTENT_URI, values, Data._ID + "=?",
1788                new String[]{String.valueOf(dataId)});
1789
1790        // Check that the update went through.
1791        assertStoredValues(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), values);
1792    }
1793
1794    public void testQueryContactIncludeProfile() {
1795        ContentValues profileValues = new ContentValues();
1796        long profileRawContactId = createBasicProfileContact(profileValues);
1797        long profileContactId = queryContactId(profileRawContactId);
1798
1799        ContentValues nonProfileValues = new ContentValues();
1800        long nonProfileRawContactId = createBasicNonProfileContact(nonProfileValues);
1801        long nonProfileContactId = queryContactId(nonProfileRawContactId);
1802
1803        Uri contactWithProfilesUri = Contacts.CONTENT_URI.buildUpon()
1804                .appendQueryParameter(ContactsContract.ALLOW_PROFILE, "1").build();
1805        assertStoredValuesOrderly(contactWithProfilesUri,
1806                new ContentValues[]{profileValues, nonProfileValues});
1807        assertSelection(contactWithProfilesUri, profileValues, Contacts._ID, profileContactId);
1808        assertSelection(Contacts.CONTENT_URI, nonProfileValues, Contacts._ID, nonProfileContactId);
1809    }
1810
1811    public void testQueryContactExcludeProfile() {
1812        // Create a profile contact (it should not be returned by the general contact URI).
1813        createBasicProfileContact(new ContentValues());
1814
1815        // Create a non-profile contact - this should be returned.
1816        ContentValues nonProfileValues = new ContentValues();
1817        createBasicNonProfileContact(nonProfileValues);
1818
1819        assertStoredValues(Contacts.CONTENT_URI, new ContentValues[] {nonProfileValues});
1820    }
1821
1822    public void testQueryProfile() {
1823        ContentValues profileValues = new ContentValues();
1824        createBasicProfileContact(profileValues);
1825
1826        assertStoredValues(Profile.CONTENT_URI, profileValues);
1827    }
1828
1829    private ContentValues[] getExpectedProfileDataValues() {
1830        // Expected photo data values (only field is the photo BLOB, which we can't check).
1831        ContentValues photoRow = new ContentValues();
1832        photoRow.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
1833
1834        // Expected phone data values.
1835        ContentValues phoneRow = new ContentValues();
1836        phoneRow.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
1837        phoneRow.put(Phone.NUMBER, "18005554411");
1838
1839        // Expected email data values.
1840        ContentValues emailRow = new ContentValues();
1841        emailRow.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
1842        emailRow.put(Email.ADDRESS, "mia.prophyl@acme.com");
1843
1844        // Expected name data values.
1845        ContentValues nameRow = new ContentValues();
1846        nameRow.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
1847        nameRow.put(StructuredName.DISPLAY_NAME, "Mia Prophyl");
1848        nameRow.put(StructuredName.GIVEN_NAME, "Mia");
1849        nameRow.put(StructuredName.FAMILY_NAME, "Prophyl");
1850
1851        return new ContentValues[]{photoRow, phoneRow, emailRow, nameRow};
1852    }
1853
1854    public void testQueryProfileData() {
1855        createBasicProfileContact(new ContentValues());
1856
1857        assertStoredValues(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
1858                getExpectedProfileDataValues());
1859    }
1860
1861    public void testQueryProfileEntities() {
1862        createBasicProfileContact(new ContentValues());
1863
1864        assertStoredValues(Profile.CONTENT_URI.buildUpon().appendPath("entities").build(),
1865                getExpectedProfileDataValues());
1866    }
1867
1868    public void testQueryRawProfile() {
1869        ContentValues profileValues = new ContentValues();
1870        createBasicProfileContact(profileValues);
1871
1872        // The raw contact view doesn't include the photo ID.
1873        profileValues.remove(Contacts.PHOTO_ID);
1874        assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, profileValues);
1875    }
1876
1877    public void testQueryRawProfileById() {
1878        ContentValues profileValues = new ContentValues();
1879        long profileRawContactId = createBasicProfileContact(profileValues);
1880
1881        // The raw contact view doesn't include the photo ID.
1882        profileValues.remove(Contacts.PHOTO_ID);
1883        assertStoredValues(ContentUris.withAppendedId(
1884                Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId), profileValues);
1885    }
1886
1887    public void testQueryRawProfileData() {
1888        long profileRawContactId = createBasicProfileContact(new ContentValues());
1889
1890        assertStoredValues(ContentUris.withAppendedId(
1891                Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
1892                .appendPath("data").build(), getExpectedProfileDataValues());
1893    }
1894
1895    public void testQueryRawProfileEntity() {
1896        long profileRawContactId = createBasicProfileContact(new ContentValues());
1897
1898        assertStoredValues(ContentUris.withAppendedId(
1899                Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon()
1900                .appendPath("entity").build(), getExpectedProfileDataValues());
1901    }
1902
1903    public void testQueryDataForProfile() {
1904        createBasicProfileContact(new ContentValues());
1905
1906        assertStoredValues(Profile.CONTENT_URI.buildUpon().appendPath("data").build(),
1907                getExpectedProfileDataValues());
1908    }
1909
1910    public void testPhonesWithStatusUpdate() {
1911
1912        ContentValues values = new ContentValues();
1913        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
1914        long rawContactId = ContentUris.parseId(rawContactUri);
1915        insertStructuredName(rawContactId, "John", "Doe");
1916        Uri photoUri = insertPhoto(rawContactId);
1917        long photoId = ContentUris.parseId(photoUri);
1918        insertPhoneNumber(rawContactId, "18004664411");
1919        insertPhoneNumber(rawContactId, "18004664412");
1920        insertEmail(rawContactId, "goog411@acme.com");
1921        insertEmail(rawContactId, "goog412@acme.com");
1922
1923        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com",
1924                StatusUpdates.INVISIBLE, "Bad",
1925                StatusUpdates.CAPABILITY_HAS_CAMERA);
1926        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog412@acme.com",
1927                StatusUpdates.AVAILABLE, "Good",
1928                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VOICE);
1929        long contactId = queryContactId(rawContactId);
1930
1931        Uri uri = Data.CONTENT_URI;
1932
1933        Cursor c = mResolver.query(uri, null, RawContacts.CONTACT_ID + "=" + contactId + " AND "
1934                + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'", null, Phone.NUMBER);
1935        assertEquals(2, c.getCount());
1936
1937        c.moveToFirst();
1938
1939        values.clear();
1940        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE);
1941        values.put(Contacts.CONTACT_STATUS, "Bad");
1942        values.put(Contacts.DISPLAY_NAME, "John Doe");
1943        values.put(Phone.NUMBER, "18004664411");
1944        values.putNull(Phone.LABEL);
1945        values.put(RawContacts.CONTACT_ID, contactId);
1946        assertCursorValues(c, values);
1947
1948        c.moveToNext();
1949
1950        values.clear();
1951        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE);
1952        values.put(Contacts.CONTACT_STATUS, "Bad");
1953        values.put(Contacts.DISPLAY_NAME, "John Doe");
1954        values.put(Phone.NUMBER, "18004664412");
1955        values.putNull(Phone.LABEL);
1956        values.put(RawContacts.CONTACT_ID, contactId);
1957        assertCursorValues(c, values);
1958
1959        c.close();
1960    }
1961
1962    public void testGroupQuery() {
1963        Account account1 = new Account("a", "b");
1964        Account account2 = new Account("c", "d");
1965        long groupId1 = createGroup(account1, "e", "f");
1966        long groupId2 = createGroup(account2, "g", "h");
1967        Uri uri1 = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account1);
1968        Uri uri2 = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account2);
1969        assertEquals(1, getCount(uri1, null, null));
1970        assertEquals(1, getCount(uri2, null, null));
1971        assertStoredValue(uri1, Groups._ID + "=" + groupId1, null, Groups._ID, groupId1) ;
1972        assertStoredValue(uri2, Groups._ID + "=" + groupId2, null, Groups._ID, groupId2) ;
1973    }
1974
1975    public void testGroupInsert() {
1976        ContentValues values = new ContentValues();
1977
1978        values.put(Groups.ACCOUNT_NAME, "a");
1979        values.put(Groups.ACCOUNT_TYPE, "b");
1980        values.put(Groups.SOURCE_ID, "c");
1981        values.put(Groups.VERSION, 42);
1982        values.put(Groups.GROUP_VISIBLE, 1);
1983        values.put(Groups.TITLE, "d");
1984        values.put(Groups.TITLE_RES, 1234);
1985        values.put(Groups.NOTES, "e");
1986        values.put(Groups.RES_PACKAGE, "f");
1987        values.put(Groups.SYSTEM_ID, "g");
1988        values.put(Groups.DELETED, 1);
1989        values.put(Groups.SYNC1, "h");
1990        values.put(Groups.SYNC2, "i");
1991        values.put(Groups.SYNC3, "j");
1992        values.put(Groups.SYNC4, "k");
1993
1994        Uri rowUri = mResolver.insert(Groups.CONTENT_URI, values);
1995
1996        values.put(Groups.DIRTY, 1);
1997        assertStoredValues(rowUri, values);
1998    }
1999
2000    public void testSettingsQuery() {
2001        Account account1 = new Account("a", "b");
2002        Account account2 = new Account("c", "d");
2003        createSettings(account1, "0", "0");
2004        createSettings(account2, "1", "1");
2005        Uri uri1 = maybeAddAccountQueryParameters(Settings.CONTENT_URI, account1);
2006        Uri uri2 = maybeAddAccountQueryParameters(Settings.CONTENT_URI, account2);
2007        assertEquals(1, getCount(uri1, null, null));
2008        assertEquals(1, getCount(uri2, null, null));
2009        assertStoredValue(uri1, Settings.SHOULD_SYNC, "0") ;
2010        assertStoredValue(uri1, Settings.UNGROUPED_VISIBLE, "0") ;
2011        assertStoredValue(uri2, Settings.SHOULD_SYNC, "1") ;
2012        assertStoredValue(uri2, Settings.UNGROUPED_VISIBLE, "1") ;
2013    }
2014
2015    public void testDisplayNameParsingWhenPartsUnspecified() {
2016        long rawContactId = createRawContact();
2017        ContentValues values = new ContentValues();
2018        values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr.");
2019        insertStructuredName(rawContactId, values);
2020
2021        assertStructuredName(rawContactId, "Mr.", "John", "Kevin", "von Smith", "Jr.");
2022    }
2023
2024    public void testDisplayNameParsingWhenPartsAreNull() {
2025        long rawContactId = createRawContact();
2026        ContentValues values = new ContentValues();
2027        values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr.");
2028        values.putNull(StructuredName.GIVEN_NAME);
2029        values.putNull(StructuredName.FAMILY_NAME);
2030        insertStructuredName(rawContactId, values);
2031        assertStructuredName(rawContactId, "Mr.", "John", "Kevin", "von Smith", "Jr.");
2032    }
2033
2034    public void testDisplayNameParsingWhenPartsSpecified() {
2035        long rawContactId = createRawContact();
2036        ContentValues values = new ContentValues();
2037        values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr.");
2038        values.put(StructuredName.FAMILY_NAME, "Johnson");
2039        insertStructuredName(rawContactId, values);
2040
2041        assertStructuredName(rawContactId, null, null, null, "Johnson", null);
2042    }
2043
2044    public void testContactWithoutPhoneticName() {
2045        final long rawContactId = createRawContact(null);
2046
2047        ContentValues values = new ContentValues();
2048        values.put(StructuredName.PREFIX, "Mr");
2049        values.put(StructuredName.GIVEN_NAME, "John");
2050        values.put(StructuredName.MIDDLE_NAME, "K.");
2051        values.put(StructuredName.FAMILY_NAME, "Doe");
2052        values.put(StructuredName.SUFFIX, "Jr.");
2053        Uri dataUri = insertStructuredName(rawContactId, values);
2054
2055        values.clear();
2056        values.put(RawContacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
2057        values.put(RawContacts.DISPLAY_NAME_PRIMARY, "Mr John K. Doe, Jr.");
2058        values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "Mr Doe, John K., Jr.");
2059        values.putNull(RawContacts.PHONETIC_NAME);
2060        values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
2061        values.put(RawContacts.SORT_KEY_PRIMARY, "John K. Doe, Jr.");
2062        values.put(RawContacts.SORT_KEY_ALTERNATIVE, "Doe, John K., Jr.");
2063
2064        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
2065        assertStoredValues(rawContactUri, values);
2066
2067        values.clear();
2068        values.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
2069        values.put(Contacts.DISPLAY_NAME_PRIMARY, "Mr John K. Doe, Jr.");
2070        values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "Mr Doe, John K., Jr.");
2071        values.putNull(Contacts.PHONETIC_NAME);
2072        values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
2073        values.put(Contacts.SORT_KEY_PRIMARY, "John K. Doe, Jr.");
2074        values.put(Contacts.SORT_KEY_ALTERNATIVE, "Doe, John K., Jr.");
2075
2076        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
2077                queryContactId(rawContactId));
2078        assertStoredValues(contactUri, values);
2079
2080        // The same values should be available through a join with Data
2081        assertStoredValues(dataUri, values);
2082    }
2083
2084    public void testContactWithChineseName() {
2085
2086        // Only run this test when Chinese collation is supported
2087        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.CHINA)) {
2088            return;
2089        }
2090
2091        long rawContactId = createRawContact(null);
2092
2093        ContentValues values = new ContentValues();
2094        values.put(StructuredName.DISPLAY_NAME, "\u6BB5\u5C0F\u6D9B");
2095        Uri dataUri = insertStructuredName(rawContactId, values);
2096
2097        values.clear();
2098        values.put(RawContacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
2099        values.put(RawContacts.DISPLAY_NAME_PRIMARY, "\u6BB5\u5C0F\u6D9B");
2100        values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B");
2101        values.putNull(RawContacts.PHONETIC_NAME);
2102        values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
2103        values.put(RawContacts.SORT_KEY_PRIMARY, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
2104        values.put(RawContacts.SORT_KEY_ALTERNATIVE, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
2105
2106        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
2107        assertStoredValues(rawContactUri, values);
2108
2109        values.clear();
2110        values.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
2111        values.put(Contacts.DISPLAY_NAME_PRIMARY, "\u6BB5\u5C0F\u6D9B");
2112        values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B");
2113        values.putNull(Contacts.PHONETIC_NAME);
2114        values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
2115        values.put(Contacts.SORT_KEY_PRIMARY, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
2116        values.put(Contacts.SORT_KEY_ALTERNATIVE, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
2117
2118        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
2119                queryContactId(rawContactId));
2120        assertStoredValues(contactUri, values);
2121
2122        // The same values should be available through a join with Data
2123        assertStoredValues(dataUri, values);
2124    }
2125
2126    public void testContactWithJapaneseName() {
2127        long rawContactId = createRawContact(null);
2128
2129        ContentValues values = new ContentValues();
2130        values.put(StructuredName.GIVEN_NAME, "\u7A7A\u6D77");
2131        values.put(StructuredName.PHONETIC_GIVEN_NAME, "\u304B\u3044\u304F\u3046");
2132        Uri dataUri = insertStructuredName(rawContactId, values);
2133
2134        values.clear();
2135        values.put(RawContacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
2136        values.put(RawContacts.DISPLAY_NAME_PRIMARY, "\u7A7A\u6D77");
2137        values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "\u7A7A\u6D77");
2138        values.put(RawContacts.PHONETIC_NAME, "\u304B\u3044\u304F\u3046");
2139        values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE);
2140        values.put(RawContacts.SORT_KEY_PRIMARY, "\u304B\u3044\u304F\u3046");
2141        values.put(RawContacts.SORT_KEY_ALTERNATIVE, "\u304B\u3044\u304F\u3046");
2142
2143        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
2144        assertStoredValues(rawContactUri, values);
2145
2146        values.clear();
2147        values.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
2148        values.put(Contacts.DISPLAY_NAME_PRIMARY, "\u7A7A\u6D77");
2149        values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "\u7A7A\u6D77");
2150        values.put(Contacts.PHONETIC_NAME, "\u304B\u3044\u304F\u3046");
2151        values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE);
2152        values.put(Contacts.SORT_KEY_PRIMARY, "\u304B\u3044\u304F\u3046");
2153        values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u304B\u3044\u304F\u3046");
2154
2155        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
2156                queryContactId(rawContactId));
2157        assertStoredValues(contactUri, values);
2158
2159        // The same values should be available through a join with Data
2160        assertStoredValues(dataUri, values);
2161    }
2162
2163    public void testDisplayNameUpdate() {
2164        long rawContactId1 = createRawContact();
2165        insertEmail(rawContactId1, "potato@acme.com", true);
2166
2167        long rawContactId2 = createRawContact();
2168        insertPhoneNumber(rawContactId2, "123456789", true);
2169
2170        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
2171                rawContactId1, rawContactId2);
2172
2173        assertAggregated(rawContactId1, rawContactId2, "123456789");
2174
2175        insertStructuredName(rawContactId2, "Potato", "Head");
2176
2177        assertAggregated(rawContactId1, rawContactId2, "Potato Head");
2178        assertNetworkNotified(true);
2179    }
2180
2181    public void testDisplayNameFromData() {
2182        long rawContactId = createRawContact();
2183        long contactId = queryContactId(rawContactId);
2184        ContentValues values = new ContentValues();
2185
2186        Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2187
2188        assertStoredValue(uri, Contacts.DISPLAY_NAME, null);
2189        insertEmail(rawContactId, "mike@monstersinc.com");
2190        assertStoredValue(uri, Contacts.DISPLAY_NAME, "mike@monstersinc.com");
2191
2192        insertEmail(rawContactId, "james@monstersinc.com", true);
2193        assertStoredValue(uri, Contacts.DISPLAY_NAME, "james@monstersinc.com");
2194
2195        insertPhoneNumber(rawContactId, "1-800-466-4411");
2196        assertStoredValue(uri, Contacts.DISPLAY_NAME, "1-800-466-4411");
2197
2198        // If there are title and company, the company is display name.
2199        values.clear();
2200        values.put(Organization.COMPANY, "Monsters Inc");
2201        Uri organizationUri = insertOrganization(rawContactId, values);
2202        assertStoredValue(uri, Contacts.DISPLAY_NAME, "Monsters Inc");
2203
2204        // If there is nickname, that is display name.
2205        insertNickname(rawContactId, "Sully");
2206        assertStoredValue(uri, Contacts.DISPLAY_NAME, "Sully");
2207
2208        // If there is structured name, that is display name.
2209        values.clear();
2210        values.put(StructuredName.GIVEN_NAME, "James");
2211        values.put(StructuredName.MIDDLE_NAME, "P.");
2212        values.put(StructuredName.FAMILY_NAME, "Sullivan");
2213        insertStructuredName(rawContactId, values);
2214        assertStoredValue(uri, Contacts.DISPLAY_NAME, "James P. Sullivan");
2215    }
2216
2217    public void testDisplayNameFromOrganizationWithoutPhoneticName() {
2218        long rawContactId = createRawContact();
2219        long contactId = queryContactId(rawContactId);
2220        ContentValues values = new ContentValues();
2221
2222        Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2223
2224        // If there is title without company, the title is display name.
2225        values.clear();
2226        values.put(Organization.TITLE, "Protagonist");
2227        Uri organizationUri = insertOrganization(rawContactId, values);
2228        assertStoredValue(uri, Contacts.DISPLAY_NAME, "Protagonist");
2229
2230        // If there are title and company, the company is display name.
2231        values.clear();
2232        values.put(Organization.COMPANY, "Monsters Inc");
2233        mResolver.update(organizationUri, values, null, null);
2234
2235        values.clear();
2236        values.put(Contacts.DISPLAY_NAME, "Monsters Inc");
2237        values.putNull(Contacts.PHONETIC_NAME);
2238        values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
2239        values.put(Contacts.SORT_KEY_PRIMARY, "Monsters Inc");
2240        values.put(Contacts.SORT_KEY_ALTERNATIVE, "Monsters Inc");
2241        assertStoredValues(uri, values);
2242    }
2243
2244    public void testDisplayNameFromOrganizationWithJapanesePhoneticName() {
2245        long rawContactId = createRawContact();
2246        long contactId = queryContactId(rawContactId);
2247        ContentValues values = new ContentValues();
2248
2249        Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2250
2251        // If there is title without company, the title is display name.
2252        values.clear();
2253        values.put(Organization.COMPANY, "DoCoMo");
2254        values.put(Organization.PHONETIC_NAME, "\u30C9\u30B3\u30E2");
2255        Uri organizationUri = insertOrganization(rawContactId, values);
2256
2257        values.clear();
2258        values.put(Contacts.DISPLAY_NAME, "DoCoMo");
2259        values.put(Contacts.PHONETIC_NAME, "\u30C9\u30B3\u30E2");
2260        values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE);
2261        values.put(Contacts.SORT_KEY_PRIMARY, "\u30C9\u30B3\u30E2");
2262        values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u30C9\u30B3\u30E2");
2263        assertStoredValues(uri, values);
2264    }
2265
2266    public void testDisplayNameFromOrganizationWithChineseName() {
2267        boolean hasChineseCollator = false;
2268        final Locale locale[] = Collator.getAvailableLocales();
2269        for (int i = 0; i < locale.length; i++) {
2270            if (locale[i].equals(Locale.CHINA)) {
2271                hasChineseCollator = true;
2272                break;
2273            }
2274        }
2275
2276        if (!hasChineseCollator) {
2277            return;
2278        }
2279
2280        long rawContactId = createRawContact();
2281        long contactId = queryContactId(rawContactId);
2282        ContentValues values = new ContentValues();
2283
2284        Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2285
2286        // If there is title without company, the title is display name.
2287        values.clear();
2288        values.put(Organization.COMPANY, "\u4E2D\u56FD\u7535\u4FE1");
2289        Uri organizationUri = insertOrganization(rawContactId, values);
2290
2291        values.clear();
2292        values.put(Contacts.DISPLAY_NAME, "\u4E2D\u56FD\u7535\u4FE1");
2293        values.putNull(Contacts.PHONETIC_NAME);
2294        values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
2295        values.put(Contacts.SORT_KEY_PRIMARY, "ZHONG \u4E2D GUO \u56FD DIAN \u7535 XIN \u4FE1");
2296        values.put(Contacts.SORT_KEY_ALTERNATIVE, "ZHONG \u4E2D GUO \u56FD DIAN \u7535 XIN \u4FE1");
2297        assertStoredValues(uri, values);
2298    }
2299
2300    public void testLookupByOrganization() {
2301        long rawContactId = createRawContact();
2302        long contactId = queryContactId(rawContactId);
2303        ContentValues values = new ContentValues();
2304
2305        values.clear();
2306        values.put(Organization.COMPANY, "acmecorp");
2307        values.put(Organization.TITLE, "president");
2308        Uri organizationUri = insertOrganization(rawContactId, values);
2309
2310        assertContactFilter(contactId, "acmecorp");
2311        assertContactFilter(contactId, "president");
2312
2313        values.clear();
2314        values.put(Organization.DEPARTMENT, "software");
2315        mResolver.update(organizationUri, values, null, null);
2316
2317        assertContactFilter(contactId, "acmecorp");
2318        assertContactFilter(contactId, "president");
2319
2320        values.clear();
2321        values.put(Organization.COMPANY, "incredibles");
2322        mResolver.update(organizationUri, values, null, null);
2323
2324        assertContactFilter(contactId, "incredibles");
2325        assertContactFilter(contactId, "president");
2326
2327        values.clear();
2328        values.put(Organization.TITLE, "director");
2329        mResolver.update(organizationUri, values, null, null);
2330
2331        assertContactFilter(contactId, "incredibles");
2332        assertContactFilter(contactId, "director");
2333
2334        values.clear();
2335        values.put(Organization.COMPANY, "monsters");
2336        values.put(Organization.TITLE, "scarer");
2337        mResolver.update(organizationUri, values, null, null);
2338
2339        assertContactFilter(contactId, "monsters");
2340        assertContactFilter(contactId, "scarer");
2341    }
2342
2343    private void assertContactFilter(long contactId, String filter) {
2344        Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(filter));
2345        assertStoredValue(filterUri, Contacts._ID, contactId);
2346    }
2347
2348    private void assertContactFilterNoResult(String filter) {
2349        Uri filterUri4 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, filter);
2350        assertEquals(0, getCount(filterUri4, null, null));
2351    }
2352
2353    public void testSearchSnippetOrganization() throws Exception {
2354        long rawContactId = createRawContactWithName();
2355        long contactId = queryContactId(rawContactId);
2356
2357        // Some random data element
2358        insertEmail(rawContactId, "inc@corp.com");
2359
2360        ContentValues values = new ContentValues();
2361        values.clear();
2362        values.put(Organization.COMPANY, "acmecorp");
2363        values.put(Organization.TITLE, "engineer");
2364        Uri organizationUri = insertOrganization(rawContactId, values);
2365
2366        // Add another matching organization
2367        values.put(Organization.COMPANY, "acmeinc");
2368        insertOrganization(rawContactId, values);
2369
2370        // Add another non-matching organization
2371        values.put(Organization.COMPANY, "corpacme");
2372        insertOrganization(rawContactId, values);
2373
2374        // And another data element
2375        insertEmail(rawContactId, "emca@corp.com", true, Email.TYPE_CUSTOM, "Custom");
2376
2377        Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("acme"));
2378
2379        values.clear();
2380        values.put(Contacts._ID, contactId);
2381        values.put(SearchSnippetColumns.SNIPPET, "engineer, [acmecorp]");
2382        assertStoredValues(filterUri, values);
2383    }
2384
2385    public void testSearchSnippetEmail() throws Exception {
2386        long rawContactId = createRawContact();
2387        long contactId = queryContactId(rawContactId);
2388        ContentValues values = new ContentValues();
2389
2390        insertStructuredName(rawContactId, "John", "Doe");
2391        Uri dataUri = insertEmail(rawContactId, "acme@corp.com", true, Email.TYPE_CUSTOM, "Custom");
2392
2393        Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("acme"));
2394
2395        values.clear();
2396        values.put(Contacts._ID, contactId);
2397        values.put(SearchSnippetColumns.SNIPPET, "[acme@corp.com]");
2398        assertStoredValues(filterUri, values);
2399    }
2400
2401    public void testSearchSnippetPhone() throws Exception {
2402        long rawContactId = createRawContact();
2403        long contactId = queryContactId(rawContactId);
2404        ContentValues values = new ContentValues();
2405
2406        insertStructuredName(rawContactId, "Cave", "Johnson");
2407        insertPhoneNumber(rawContactId, "(860) 555-1234");
2408
2409        values.clear();
2410        values.put(Contacts._ID, contactId);
2411        values.put(SearchSnippetColumns.SNIPPET, "[(860) 555-1234]");
2412
2413        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2414                Uri.encode("86 (0) 5-55-12-34")), values);
2415        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2416                Uri.encode("860 555-1234")), values);
2417        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2418                Uri.encode("860")), values);
2419        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2420                Uri.encode("8605551234")), values);
2421        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2422                Uri.encode("860555")), values);
2423        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2424                Uri.encode("860 555")), values);
2425        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2426                Uri.encode("860-555")), values);
2427    }
2428
2429    public void testSearchSnippetNickname() throws Exception {
2430        long rawContactId = createRawContactWithName();
2431        long contactId = queryContactId(rawContactId);
2432        ContentValues values = new ContentValues();
2433
2434        Uri dataUri = insertNickname(rawContactId, "Incredible");
2435
2436        Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("inc"));
2437
2438        values.clear();
2439        values.put(Contacts._ID, contactId);
2440        values.put(SearchSnippetColumns.SNIPPET, "[Incredible]");
2441        assertStoredValues(filterUri, values);
2442    }
2443
2444    public void testSearchSnippetEmptyForNameInDisplayName() throws Exception {
2445        long rawContactId = createRawContact();
2446        long contactId = queryContactId(rawContactId);
2447        insertStructuredName(rawContactId, "Cave", "Johnson");
2448        insertEmail(rawContactId, "cave@aperturescience.com", true);
2449
2450        ContentValues emptySnippet = new ContentValues();
2451        emptySnippet.clear();
2452        emptySnippet.put(Contacts._ID, contactId);
2453        emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null);
2454
2455        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("cave")),
2456                emptySnippet);
2457        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("john")),
2458                emptySnippet);
2459    }
2460
2461    public void testSearchSnippetEmptyForNicknameInDisplayName() throws Exception {
2462        long rawContactId = createRawContact();
2463        long contactId = queryContactId(rawContactId);
2464        insertNickname(rawContactId, "Caveman");
2465        insertEmail(rawContactId, "cave@aperturescience.com", true);
2466
2467        ContentValues emptySnippet = new ContentValues();
2468        emptySnippet.clear();
2469        emptySnippet.put(Contacts._ID, contactId);
2470        emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null);
2471
2472        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("cave")),
2473                emptySnippet);
2474    }
2475
2476    public void testSearchSnippetEmptyForCompanyInDisplayName() throws Exception {
2477        long rawContactId = createRawContact();
2478        long contactId = queryContactId(rawContactId);
2479        ContentValues company = new ContentValues();
2480        company.clear();
2481        company.put(Organization.COMPANY, "Aperture Science");
2482        company.put(Organization.TITLE, "President");
2483        insertOrganization(rawContactId, company);
2484        insertEmail(rawContactId, "aperturepresident@aperturescience.com", true);
2485
2486        ContentValues emptySnippet = new ContentValues();
2487        emptySnippet.clear();
2488        emptySnippet.put(Contacts._ID, contactId);
2489        emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null);
2490
2491        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
2492                Uri.encode("aperture")), emptySnippet);
2493    }
2494
2495    public void testSearchSnippetEmptyForPhoneInDisplayName() throws Exception {
2496        long rawContactId = createRawContact();
2497        long contactId = queryContactId(rawContactId);
2498        insertPhoneNumber(rawContactId, "860-555-1234");
2499        insertEmail(rawContactId, "860@aperturescience.com", true);
2500
2501        ContentValues emptySnippet = new ContentValues();
2502        emptySnippet.clear();
2503        emptySnippet.put(Contacts._ID, contactId);
2504        emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null);
2505
2506        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("860")),
2507                emptySnippet);
2508    }
2509
2510    public void testSearchSnippetEmptyForEmailInDisplayName() throws Exception {
2511        long rawContactId = createRawContact();
2512        long contactId = queryContactId(rawContactId);
2513        insertEmail(rawContactId, "cave@aperturescience.com", true);
2514        insertNote(rawContactId, "Cave Johnson is president of Aperture Science");
2515
2516        ContentValues emptySnippet = new ContentValues();
2517        emptySnippet.clear();
2518        emptySnippet.put(Contacts._ID, contactId);
2519        emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null);
2520
2521        assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("cave")),
2522                emptySnippet);
2523    }
2524
2525    public void testDisplayNameUpdateFromStructuredNameUpdate() {
2526        long rawContactId = createRawContact();
2527        Uri nameUri = insertStructuredName(rawContactId, "Slinky", "Dog");
2528
2529        long contactId = queryContactId(rawContactId);
2530
2531        Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2532        assertStoredValue(uri, Contacts.DISPLAY_NAME, "Slinky Dog");
2533
2534        ContentValues values = new ContentValues();
2535        values.putNull(StructuredName.FAMILY_NAME);
2536
2537        mResolver.update(nameUri, values, null, null);
2538        assertStoredValue(uri, Contacts.DISPLAY_NAME, "Slinky");
2539
2540        values.putNull(StructuredName.GIVEN_NAME);
2541
2542        mResolver.update(nameUri, values, null, null);
2543        assertStoredValue(uri, Contacts.DISPLAY_NAME, null);
2544
2545        values.put(StructuredName.FAMILY_NAME, "Dog");
2546        mResolver.update(nameUri, values, null, null);
2547
2548        assertStoredValue(uri, Contacts.DISPLAY_NAME, "Dog");
2549    }
2550
2551    public void testInsertDataWithContentProviderOperations() throws Exception {
2552        ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
2553                .withValues(new ContentValues())
2554                .build();
2555        ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
2556                .withValueBackReference(Data.RAW_CONTACT_ID, 0)
2557                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
2558                .withValue(StructuredName.GIVEN_NAME, "John")
2559                .withValue(StructuredName.FAMILY_NAME, "Doe")
2560                .build();
2561        ContentProviderResult[] results =
2562                mResolver.applyBatch(ContactsContract.AUTHORITY, Lists.newArrayList(cpo1, cpo2));
2563        long contactId = queryContactId(ContentUris.parseId(results[0].uri));
2564        Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2565        assertStoredValue(uri, Contacts.DISPLAY_NAME, "John Doe");
2566    }
2567
2568    public void testSendToVoicemailDefault() {
2569        long rawContactId = createRawContactWithName();
2570        long contactId = queryContactId(rawContactId);
2571
2572        Cursor c = queryContact(contactId);
2573        assertTrue(c.moveToNext());
2574        int sendToVoicemail = c.getInt(c.getColumnIndex(Contacts.SEND_TO_VOICEMAIL));
2575        assertEquals(0, sendToVoicemail);
2576        c.close();
2577    }
2578
2579    public void testSetSendToVoicemailAndRingtone() {
2580        long rawContactId = createRawContactWithName();
2581        long contactId = queryContactId(rawContactId);
2582
2583        updateSendToVoicemailAndRingtone(contactId, true, "foo");
2584        assertSendToVoicemailAndRingtone(contactId, true, "foo");
2585        assertNetworkNotified(false);
2586
2587        updateSendToVoicemailAndRingtoneWithSelection(contactId, false, "bar");
2588        assertSendToVoicemailAndRingtone(contactId, false, "bar");
2589        assertNetworkNotified(false);
2590    }
2591
2592    public void testSendToVoicemailAndRingtoneAfterAggregation() {
2593        long rawContactId1 = createRawContactWithName("a", "b");
2594        long contactId1 = queryContactId(rawContactId1);
2595        updateSendToVoicemailAndRingtone(contactId1, true, "foo");
2596
2597        long rawContactId2 = createRawContactWithName("c", "d");
2598        long contactId2 = queryContactId(rawContactId2);
2599        updateSendToVoicemailAndRingtone(contactId2, true, "bar");
2600
2601        // Aggregate them
2602        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
2603                rawContactId1, rawContactId2);
2604
2605        // Both contacts had "send to VM", the contact now has the same value
2606        assertSendToVoicemailAndRingtone(contactId1, true, "foo,bar"); // Either foo or bar
2607    }
2608
2609    public void testDoNotSendToVoicemailAfterAggregation() {
2610        long rawContactId1 = createRawContactWithName("e", "f");
2611        long contactId1 = queryContactId(rawContactId1);
2612        updateSendToVoicemailAndRingtone(contactId1, true, null);
2613
2614        long rawContactId2 = createRawContactWithName("g", "h");
2615        long contactId2 = queryContactId(rawContactId2);
2616        updateSendToVoicemailAndRingtone(contactId2, false, null);
2617
2618        // Aggregate them
2619        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
2620                rawContactId1, rawContactId2);
2621
2622        // Since one of the contacts had "don't send to VM" that setting wins for the aggregate
2623        assertSendToVoicemailAndRingtone(queryContactId(rawContactId1), false, null);
2624    }
2625
2626    public void testSetSendToVoicemailAndRingtonePreservedAfterJoinAndSplit() {
2627        long rawContactId1 = createRawContactWithName("i", "j");
2628        long contactId1 = queryContactId(rawContactId1);
2629        updateSendToVoicemailAndRingtone(contactId1, true, "foo");
2630
2631        long rawContactId2 = createRawContactWithName("k", "l");
2632        long contactId2 = queryContactId(rawContactId2);
2633        updateSendToVoicemailAndRingtone(contactId2, false, "bar");
2634
2635        // Aggregate them
2636        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
2637                rawContactId1, rawContactId2);
2638
2639        // Split them
2640        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
2641                rawContactId1, rawContactId2);
2642
2643        assertSendToVoicemailAndRingtone(queryContactId(rawContactId1), true, "foo");
2644        assertSendToVoicemailAndRingtone(queryContactId(rawContactId2), false, "bar");
2645    }
2646
2647    public void testStatusUpdateInsert() {
2648        long rawContactId = createRawContact();
2649        Uri imUri = insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
2650        long dataId = ContentUris.parseId(imUri);
2651
2652        ContentValues values = new ContentValues();
2653        values.put(StatusUpdates.DATA_ID, dataId);
2654        values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_AIM);
2655        values.putNull(StatusUpdates.CUSTOM_PROTOCOL);
2656        values.put(StatusUpdates.IM_HANDLE, "aim");
2657        values.put(StatusUpdates.PRESENCE, StatusUpdates.INVISIBLE);
2658        values.put(StatusUpdates.STATUS, "Hiding");
2659        values.put(StatusUpdates.STATUS_TIMESTAMP, 100);
2660        values.put(StatusUpdates.STATUS_RES_PACKAGE, "a.b.c");
2661        values.put(StatusUpdates.STATUS_ICON, 1234);
2662        values.put(StatusUpdates.STATUS_LABEL, 2345);
2663
2664        Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values);
2665
2666        assertStoredValues(resultUri, values);
2667
2668        long contactId = queryContactId(rawContactId);
2669        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2670
2671        values.clear();
2672        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
2673        values.put(Contacts.CONTACT_STATUS, "Hiding");
2674        values.put(Contacts.CONTACT_STATUS_TIMESTAMP, 100);
2675        values.put(Contacts.CONTACT_STATUS_RES_PACKAGE, "a.b.c");
2676        values.put(Contacts.CONTACT_STATUS_ICON, 1234);
2677        values.put(Contacts.CONTACT_STATUS_LABEL, 2345);
2678
2679        assertStoredValues(contactUri, values);
2680
2681        values.clear();
2682        values.put(StatusUpdates.DATA_ID, dataId);
2683        values.put(StatusUpdates.STATUS, "Cloaked");
2684        values.put(StatusUpdates.STATUS_TIMESTAMP, 200);
2685        values.put(StatusUpdates.STATUS_RES_PACKAGE, "d.e.f");
2686        values.put(StatusUpdates.STATUS_ICON, 4321);
2687        values.put(StatusUpdates.STATUS_LABEL, 5432);
2688        mResolver.insert(StatusUpdates.CONTENT_URI, values);
2689
2690        values.clear();
2691        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
2692        values.put(Contacts.CONTACT_STATUS, "Cloaked");
2693        values.put(Contacts.CONTACT_STATUS_TIMESTAMP, 200);
2694        values.put(Contacts.CONTACT_STATUS_RES_PACKAGE, "d.e.f");
2695        values.put(Contacts.CONTACT_STATUS_ICON, 4321);
2696        values.put(Contacts.CONTACT_STATUS_LABEL, 5432);
2697        assertStoredValues(contactUri, values);
2698    }
2699
2700    public void testStatusUpdateInferAttribution() {
2701        long rawContactId = createRawContact();
2702        Uri imUri = insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
2703        long dataId = ContentUris.parseId(imUri);
2704
2705        ContentValues values = new ContentValues();
2706        values.put(StatusUpdates.DATA_ID, dataId);
2707        values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_AIM);
2708        values.put(StatusUpdates.IM_HANDLE, "aim");
2709        values.put(StatusUpdates.STATUS, "Hiding");
2710
2711        Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values);
2712
2713        values.clear();
2714        values.put(StatusUpdates.DATA_ID, dataId);
2715        values.put(StatusUpdates.STATUS_LABEL, com.android.internal.R.string.imProtocolAim);
2716        values.put(StatusUpdates.STATUS, "Hiding");
2717
2718        assertStoredValues(resultUri, values);
2719    }
2720
2721    public void testStatusUpdateMatchingImOrEmail() {
2722        long rawContactId = createRawContact();
2723        insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
2724        insertImHandle(rawContactId, Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im");
2725        insertEmail(rawContactId, "m@acme.com");
2726
2727        // Match on IM (standard)
2728        insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AVAILABLE, "Available",
2729                StatusUpdates.CAPABILITY_HAS_CAMERA);
2730
2731        // Match on IM (custom)
2732        insertStatusUpdate(Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im", StatusUpdates.IDLE, "Idle",
2733                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
2734
2735        // Match on Email
2736        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "m@acme.com", StatusUpdates.AWAY, "Away",
2737                StatusUpdates.CAPABILITY_HAS_VOICE);
2738
2739        // No match
2740        insertStatusUpdate(Im.PROTOCOL_ICQ, null, "12345", StatusUpdates.DO_NOT_DISTURB, "Go away",
2741                StatusUpdates.CAPABILITY_HAS_CAMERA);
2742
2743        Cursor c = mResolver.query(StatusUpdates.CONTENT_URI, new String[] {
2744                StatusUpdates.DATA_ID, StatusUpdates.PROTOCOL, StatusUpdates.CUSTOM_PROTOCOL,
2745                StatusUpdates.PRESENCE, StatusUpdates.STATUS},
2746                PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null, StatusUpdates.DATA_ID);
2747        assertTrue(c.moveToNext());
2748        assertStatusUpdate(c, Im.PROTOCOL_AIM, null, StatusUpdates.AVAILABLE, "Available");
2749        assertTrue(c.moveToNext());
2750        assertStatusUpdate(c, Im.PROTOCOL_CUSTOM, "my_im_proto", StatusUpdates.IDLE, "Idle");
2751        assertTrue(c.moveToNext());
2752        assertStatusUpdate(c, Im.PROTOCOL_GOOGLE_TALK, null, StatusUpdates.AWAY, "Away");
2753        assertFalse(c.moveToNext());
2754        c.close();
2755
2756        long contactId = queryContactId(rawContactId);
2757        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2758
2759        ContentValues values = new ContentValues();
2760        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE);
2761        values.put(Contacts.CONTACT_STATUS, "Available");
2762        assertStoredValuesWithProjection(contactUri, values);
2763    }
2764
2765    public void testStatusUpdateUpdateAndDelete() {
2766        long rawContactId = createRawContact();
2767        insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
2768
2769        long contactId = queryContactId(rawContactId);
2770        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2771
2772        ContentValues values = new ContentValues();
2773        values.putNull(Contacts.CONTACT_PRESENCE);
2774        values.putNull(Contacts.CONTACT_STATUS);
2775        assertStoredValuesWithProjection(contactUri, values);
2776
2777        insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AWAY, "BUSY",
2778                StatusUpdates.CAPABILITY_HAS_CAMERA);
2779        insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.DO_NOT_DISTURB, "GO AWAY",
2780                StatusUpdates.CAPABILITY_HAS_CAMERA);
2781        Uri statusUri =
2782            insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AVAILABLE, "Available",
2783                    StatusUpdates.CAPABILITY_HAS_CAMERA);
2784        long statusId = ContentUris.parseId(statusUri);
2785
2786        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE);
2787        values.put(Contacts.CONTACT_STATUS, "Available");
2788        assertStoredValuesWithProjection(contactUri, values);
2789
2790        // update status_updates table to set new values for
2791        //     status_updates.status
2792        //     status_updates.status_ts
2793        //     presence
2794        long updatedTs = 200;
2795        String testUpdate = "test_update";
2796        String selection = StatusUpdates.DATA_ID + "=" + statusId;
2797        values.clear();
2798        values.put(StatusUpdates.STATUS_TIMESTAMP, updatedTs);
2799        values.put(StatusUpdates.STATUS, testUpdate);
2800        values.put(StatusUpdates.PRESENCE, "presence_test");
2801        mResolver.update(StatusUpdates.CONTENT_URI, values,
2802                StatusUpdates.DATA_ID + "=" + statusId, null);
2803        assertStoredValuesWithProjection(StatusUpdates.CONTENT_URI, values);
2804
2805        // update status_updates table to set new values for columns in status_updates table ONLY
2806        // i.e., no rows in presence table are to be updated.
2807        updatedTs = 300;
2808        testUpdate = "test_update_new";
2809        selection = StatusUpdates.DATA_ID + "=" + statusId;
2810        values.clear();
2811        values.put(StatusUpdates.STATUS_TIMESTAMP, updatedTs);
2812        values.put(StatusUpdates.STATUS, testUpdate);
2813        mResolver.update(StatusUpdates.CONTENT_URI, values,
2814                StatusUpdates.DATA_ID + "=" + statusId, null);
2815        // make sure the presence column value is still the old value
2816        values.put(StatusUpdates.PRESENCE, "presence_test");
2817        assertStoredValuesWithProjection(StatusUpdates.CONTENT_URI, values);
2818
2819        // update status_updates table to set new values for columns in presence table ONLY
2820        // i.e., no rows in status_updates table are to be updated.
2821        selection = StatusUpdates.DATA_ID + "=" + statusId;
2822        values.clear();
2823        values.put(StatusUpdates.PRESENCE, "presence_test_new");
2824        mResolver.update(StatusUpdates.CONTENT_URI, values,
2825                StatusUpdates.DATA_ID + "=" + statusId, null);
2826        // make sure the status_updates table is not updated
2827        values.put(StatusUpdates.STATUS_TIMESTAMP, updatedTs);
2828        values.put(StatusUpdates.STATUS, testUpdate);
2829        assertStoredValuesWithProjection(StatusUpdates.CONTENT_URI, values);
2830
2831        // effect "delete status_updates" operation and expect the following
2832        //   data deleted from status_updates table
2833        //   presence set to null
2834        mResolver.delete(StatusUpdates.CONTENT_URI, StatusUpdates.DATA_ID + "=" + statusId, null);
2835        values.clear();
2836        values.putNull(Contacts.CONTACT_PRESENCE);
2837        assertStoredValuesWithProjection(contactUri, values);
2838    }
2839
2840    public void testStatusUpdateUpdateToNull() {
2841        long rawContactId = createRawContact();
2842        insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
2843
2844        long contactId = queryContactId(rawContactId);
2845        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2846
2847        ContentValues values = new ContentValues();
2848        Uri statusUri =
2849            insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AVAILABLE, "Available",
2850                    StatusUpdates.CAPABILITY_HAS_CAMERA);
2851        long statusId = ContentUris.parseId(statusUri);
2852
2853        values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE);
2854        values.put(Contacts.CONTACT_STATUS, "Available");
2855        assertStoredValuesWithProjection(contactUri, values);
2856
2857        values.clear();
2858        values.putNull(StatusUpdates.PRESENCE);
2859        mResolver.update(StatusUpdates.CONTENT_URI, values,
2860                StatusUpdates.DATA_ID + "=" + statusId, null);
2861
2862        values.clear();
2863        values.putNull(Contacts.CONTACT_PRESENCE);
2864        values.put(Contacts.CONTACT_STATUS, "Available");
2865        assertStoredValuesWithProjection(contactUri, values);
2866    }
2867
2868    public void testStatusUpdateWithTimestamp() {
2869        long rawContactId = createRawContact();
2870        insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
2871        insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk");
2872
2873        long contactId = queryContactId(rawContactId);
2874        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
2875        insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", 0, "Offline", 80,
2876                StatusUpdates.CAPABILITY_HAS_CAMERA);
2877        insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", 0, "Available", 100,
2878                StatusUpdates.CAPABILITY_HAS_CAMERA);
2879        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", 0, "Busy", 90,
2880                StatusUpdates.CAPABILITY_HAS_CAMERA);
2881
2882        // Should return the latest status
2883        ContentValues values = new ContentValues();
2884        values.put(Contacts.CONTACT_STATUS_TIMESTAMP, 100);
2885        values.put(Contacts.CONTACT_STATUS, "Available");
2886        assertStoredValuesWithProjection(contactUri, values);
2887    }
2888
2889    private void assertStatusUpdate(Cursor c, int protocol, String customProtocol, int presence,
2890            String status) {
2891        ContentValues values = new ContentValues();
2892        values.put(StatusUpdates.PROTOCOL, protocol);
2893        values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol);
2894        values.put(StatusUpdates.PRESENCE, presence);
2895        values.put(StatusUpdates.STATUS, status);
2896        assertCursorValues(c, values);
2897    }
2898
2899    // Stream item query test cases.
2900
2901    public void testQueryStreamItemsByRawContactId() {
2902        long rawContactId = createRawContact(mAccount);
2903        ContentValues values = buildGenericStreamItemValues();
2904        insertStreamItem(rawContactId, values, mAccount);
2905        assertStoredValues(
2906                Uri.withAppendedPath(
2907                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
2908                        RawContacts.StreamItems.CONTENT_DIRECTORY),
2909                values);
2910    }
2911
2912    public void testQueryStreamItemsByContactId() {
2913        long rawContactId = createRawContact();
2914        long contactId = queryContactId(rawContactId);
2915        ContentValues values = buildGenericStreamItemValues();
2916        insertStreamItem(rawContactId, values, null);
2917        assertStoredValues(
2918                Uri.withAppendedPath(
2919                        ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
2920                        Contacts.StreamItems.CONTENT_DIRECTORY),
2921                values);
2922    }
2923
2924    public void testQueryStreamItemsByLookupKey() {
2925        long rawContactId = createRawContact();
2926        long contactId = queryContactId(rawContactId);
2927        String lookupKey = queryLookupKey(contactId);
2928        ContentValues values = buildGenericStreamItemValues();
2929        insertStreamItem(rawContactId, values, null);
2930        assertStoredValues(
2931                Uri.withAppendedPath(
2932                        Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
2933                        Contacts.StreamItems.CONTENT_DIRECTORY),
2934                values);
2935    }
2936
2937    public void testQueryStreamItemsByLookupKeyAndContactId() {
2938        long rawContactId = createRawContact();
2939        long contactId = queryContactId(rawContactId);
2940        String lookupKey = queryLookupKey(contactId);
2941        ContentValues values = buildGenericStreamItemValues();
2942        insertStreamItem(rawContactId, values, null);
2943        assertStoredValues(
2944                Uri.withAppendedPath(
2945                        ContentUris.withAppendedId(
2946                                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey),
2947                                contactId),
2948                        Contacts.StreamItems.CONTENT_DIRECTORY),
2949                values);
2950    }
2951
2952    public void testQueryStreamItems() {
2953        long rawContactId = createRawContact();
2954        ContentValues values = buildGenericStreamItemValues();
2955        insertStreamItem(rawContactId, values, null);
2956        assertStoredValues(StreamItems.CONTENT_URI, values);
2957    }
2958
2959    public void testQueryStreamItemsWithSelection() {
2960        long rawContactId = createRawContact();
2961        ContentValues firstValues = buildGenericStreamItemValues();
2962        insertStreamItem(rawContactId, firstValues, null);
2963
2964        ContentValues secondValues = buildGenericStreamItemValues();
2965        secondValues.put(StreamItems.TEXT, "Goodbye world");
2966        insertStreamItem(rawContactId, secondValues, null);
2967
2968        // Select only the first stream item.
2969        assertStoredValues(StreamItems.CONTENT_URI, StreamItems.TEXT + "=?",
2970                new String[]{"Hello world"}, firstValues);
2971
2972        // Select only the second stream item.
2973        assertStoredValues(StreamItems.CONTENT_URI, StreamItems.TEXT + "=?",
2974                new String[]{"Goodbye world"}, secondValues);
2975    }
2976
2977    public void testQueryStreamItemById() {
2978        long rawContactId = createRawContact();
2979        ContentValues firstValues = buildGenericStreamItemValues();
2980        Uri resultUri = insertStreamItem(rawContactId, firstValues, null);
2981        long firstStreamItemId = ContentUris.parseId(resultUri);
2982
2983        ContentValues secondValues = buildGenericStreamItemValues();
2984        secondValues.put(StreamItems.TEXT, "Goodbye world");
2985        resultUri = insertStreamItem(rawContactId, secondValues, null);
2986        long secondStreamItemId = ContentUris.parseId(resultUri);
2987
2988        // Select only the first stream item.
2989        assertStoredValues(ContentUris.withAppendedId(StreamItems.CONTENT_URI, firstStreamItemId),
2990                firstValues);
2991
2992        // Select only the second stream item.
2993        assertStoredValues(ContentUris.withAppendedId(StreamItems.CONTENT_URI, secondStreamItemId),
2994                secondValues);
2995    }
2996
2997    // Stream item photo insertion + query test cases.
2998
2999    public void testQueryStreamItemPhotoWithSelection() {
3000        long rawContactId = createRawContact();
3001        ContentValues values = buildGenericStreamItemValues();
3002        Uri resultUri = insertStreamItem(rawContactId, values, null);
3003        long streamItemId = ContentUris.parseId(resultUri);
3004
3005        ContentValues photo1Values = buildGenericStreamItemPhotoValues(1);
3006        insertStreamItemPhoto(streamItemId, photo1Values, null);
3007        photo1Values.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3008        ContentValues photo2Values = buildGenericStreamItemPhotoValues(2);
3009        insertStreamItemPhoto(streamItemId, photo2Values, null);
3010
3011        // Select only the first photo.
3012        assertStoredValues(StreamItems.CONTENT_PHOTO_URI, StreamItemPhotos.SORT_INDEX + "=?",
3013                new String[]{"1"}, photo1Values);
3014    }
3015
3016    public void testQueryStreamItemPhotoByStreamItemId() {
3017        long rawContactId = createRawContact();
3018
3019        // Insert a first stream item.
3020        ContentValues firstValues = buildGenericStreamItemValues();
3021        Uri resultUri = insertStreamItem(rawContactId, firstValues, null);
3022        long firstStreamItemId = ContentUris.parseId(resultUri);
3023
3024        // Insert a second stream item.
3025        ContentValues secondValues = buildGenericStreamItemValues();
3026        resultUri = insertStreamItem(rawContactId, secondValues, null);
3027        long secondStreamItemId = ContentUris.parseId(resultUri);
3028
3029        // Add a photo to the first stream item.
3030        ContentValues photo1Values = buildGenericStreamItemPhotoValues(1);
3031        insertStreamItemPhoto(firstStreamItemId, photo1Values, null);
3032        photo1Values.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3033
3034        // Add a photo to the second stream item.
3035        ContentValues photo2Values = buildGenericStreamItemPhotoValues(1);
3036        photo2Values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(
3037                R.drawable.nebula, PhotoSize.ORIGINAL));
3038        insertStreamItemPhoto(secondStreamItemId, photo2Values, null);
3039        photo2Values.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3040
3041        // Select only the photos from the second stream item.
3042        assertStoredValues(Uri.withAppendedPath(
3043                ContentUris.withAppendedId(StreamItems.CONTENT_URI, secondStreamItemId),
3044                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), photo2Values);
3045    }
3046
3047    public void testQueryStreamItemPhotoByStreamItemPhotoId() {
3048        long rawContactId = createRawContact();
3049
3050        // Insert a first stream item.
3051        ContentValues firstValues = buildGenericStreamItemValues();
3052        Uri resultUri = insertStreamItem(rawContactId, firstValues, null);
3053        long firstStreamItemId = ContentUris.parseId(resultUri);
3054
3055        // Insert a second stream item.
3056        ContentValues secondValues = buildGenericStreamItemValues();
3057        resultUri = insertStreamItem(rawContactId, secondValues, null);
3058        long secondStreamItemId = ContentUris.parseId(resultUri);
3059
3060        // Add a photo to the first stream item.
3061        ContentValues photo1Values = buildGenericStreamItemPhotoValues(1);
3062        resultUri = insertStreamItemPhoto(firstStreamItemId, photo1Values, null);
3063        long firstPhotoId = ContentUris.parseId(resultUri);
3064        photo1Values.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3065
3066        // Add a photo to the second stream item.
3067        ContentValues photo2Values = buildGenericStreamItemPhotoValues(1);
3068        photo2Values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(
3069                R.drawable.galaxy, PhotoSize.ORIGINAL));
3070        resultUri = insertStreamItemPhoto(secondStreamItemId, photo2Values, null);
3071        long secondPhotoId = ContentUris.parseId(resultUri);
3072        photo2Values.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3073
3074        // Select the first photo.
3075        assertStoredValues(ContentUris.withAppendedId(
3076                Uri.withAppendedPath(
3077                        ContentUris.withAppendedId(StreamItems.CONTENT_URI, firstStreamItemId),
3078                        StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3079                firstPhotoId),
3080                photo1Values);
3081
3082        // Select the second photo.
3083        assertStoredValues(ContentUris.withAppendedId(
3084                Uri.withAppendedPath(
3085                        ContentUris.withAppendedId(StreamItems.CONTENT_URI, secondStreamItemId),
3086                        StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3087                secondPhotoId),
3088                photo2Values);
3089    }
3090
3091    // Stream item insertion test cases.
3092
3093    public void testInsertStreamItemIntoOtherAccount() {
3094        long rawContactId = createRawContact(mAccount);
3095        ContentValues values = buildGenericStreamItemValues();
3096        try {
3097            insertStreamItem(rawContactId, values, mAccountTwo);
3098            fail("Stream insertion was allowed in another account's raw contact.");
3099        } catch (SecurityException expected) {
3100            // Trying to insert stream items into account one's raw contact is forbidden.
3101        }
3102    }
3103
3104    public void testInsertStreamItemInProfileRequiresWriteProfileAccess() {
3105        long profileRawContactId = createBasicProfileContact(new ContentValues());
3106
3107        // With our (default) write profile permission, we should be able to insert a stream item.
3108        ContentValues values = buildGenericStreamItemValues();
3109        insertStreamItem(profileRawContactId, values, null);
3110
3111        // Now take away write profile permission.
3112        mActor.removePermissions("android.permission.WRITE_PROFILE");
3113
3114        // Try inserting another stream item.
3115        try {
3116            insertStreamItem(profileRawContactId, values, null);
3117            fail("Should require WRITE_PROFILE access to insert a stream item in the profile.");
3118        } catch (SecurityException expected) {
3119            // Trying to insert a stream item in the profile without WRITE_PROFILE permission
3120            // should fail.
3121        }
3122    }
3123
3124    public void testInsertStreamItemWithContentValues() {
3125        long rawContactId = createRawContact();
3126        ContentValues values = buildGenericStreamItemValues();
3127        values.put(StreamItems.RAW_CONTACT_ID, rawContactId);
3128        mResolver.insert(StreamItems.CONTENT_URI, values);
3129        assertStoredValues(Uri.withAppendedPath(
3130                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3131                RawContacts.StreamItems.CONTENT_DIRECTORY), values);
3132    }
3133
3134    public void testInsertStreamItemOverLimit() {
3135        long rawContactId = createRawContact();
3136        ContentValues values = buildGenericStreamItemValues();
3137        values.put(StreamItems.RAW_CONTACT_ID, rawContactId);
3138
3139        List<Long> streamItemIds = Lists.newArrayList();
3140
3141        // Insert MAX + 1 stream items.
3142        long baseTime = System.currentTimeMillis();
3143        for (int i = 0; i < 6; i++) {
3144            values.put(StreamItems.TIMESTAMP, baseTime + i);
3145            Uri resultUri = mResolver.insert(StreamItems.CONTENT_URI, values);
3146            streamItemIds.add(ContentUris.parseId(resultUri));
3147        }
3148        Long doomedStreamItemId = streamItemIds.get(0);
3149
3150        // There should only be MAX items.  The oldest one should have been cleaned up.
3151        Cursor c = mResolver.query(
3152                Uri.withAppendedPath(
3153                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3154                        RawContacts.StreamItems.CONTENT_DIRECTORY),
3155                new String[]{StreamItems._ID}, null, null, null);
3156        try {
3157            while(c.moveToNext()) {
3158                long streamItemId = c.getLong(0);
3159                streamItemIds.remove(streamItemId);
3160            }
3161        } finally {
3162            c.close();
3163        }
3164
3165        assertEquals(1, streamItemIds.size());
3166        assertEquals(doomedStreamItemId, streamItemIds.get(0));
3167    }
3168
3169    public void testInsertStreamItemOlderThanOldestInLimit() {
3170        long rawContactId = createRawContact();
3171        ContentValues values = buildGenericStreamItemValues();
3172        values.put(StreamItems.RAW_CONTACT_ID, rawContactId);
3173
3174        // Insert MAX stream items.
3175        long baseTime = System.currentTimeMillis();
3176        for (int i = 0; i < 5; i++) {
3177            values.put(StreamItems.TIMESTAMP, baseTime + i);
3178            Uri resultUri = mResolver.insert(StreamItems.CONTENT_URI, values);
3179            assertNotSame("Expected non-0 stream item ID to be inserted",
3180                    0L, ContentUris.parseId(resultUri));
3181        }
3182
3183        // Now try to insert a stream item that's older.  It should be deleted immediately
3184        // and return an ID of 0.
3185        values.put(StreamItems.TIMESTAMP, baseTime - 1);
3186        Uri resultUri = mResolver.insert(StreamItems.CONTENT_URI, values);
3187        assertEquals(0L, ContentUris.parseId(resultUri));
3188    }
3189
3190    // Stream item photo insertion test cases.
3191
3192    public void testInsertStreamItemsAndPhotosInBatch() throws Exception {
3193        long rawContactId = createRawContact();
3194        ContentValues streamItemValues = buildGenericStreamItemValues();
3195        ContentValues streamItemPhotoValues = buildGenericStreamItemPhotoValues(0);
3196
3197        ArrayList<ContentProviderOperation> ops = Lists.newArrayList();
3198        ops.add(ContentProviderOperation.newInsert(
3199                Uri.withAppendedPath(
3200                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3201                        RawContacts.StreamItems.CONTENT_DIRECTORY))
3202                .withValues(streamItemValues).build());
3203        for (int i = 0; i < 5; i++) {
3204            streamItemPhotoValues.put(StreamItemPhotos.SORT_INDEX, i);
3205            ops.add(ContentProviderOperation.newInsert(StreamItems.CONTENT_PHOTO_URI)
3206                    .withValues(streamItemPhotoValues)
3207                    .withValueBackReference(StreamItemPhotos.STREAM_ITEM_ID, 0)
3208                    .build());
3209        }
3210        mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
3211
3212        // Check that all five photos were inserted under the raw contact.
3213        Cursor c = mResolver.query(StreamItems.CONTENT_URI, new String[]{StreamItems._ID},
3214                StreamItems.RAW_CONTACT_ID + "=?", new String[]{String.valueOf(rawContactId)},
3215                null);
3216        long streamItemId = 0;
3217        try {
3218            assertEquals(1, c.getCount());
3219            c.moveToFirst();
3220            streamItemId = c.getLong(0);
3221        } finally {
3222            c.close();
3223        }
3224
3225        c = mResolver.query(Uri.withAppendedPath(
3226                ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3227                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3228                new String[]{StreamItemPhotos._ID, StreamItemPhotos.PHOTO_URI},
3229                null, null, null);
3230        try {
3231            assertEquals(5, c.getCount());
3232            byte[] expectedPhotoBytes = loadPhotoFromResource(
3233                    R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO);
3234            while (c.moveToNext()) {
3235                String photoUri = c.getString(1);
3236                assertInputStreamContent(expectedPhotoBytes,
3237                        mResolver.openInputStream(Uri.parse(photoUri)));
3238            }
3239        } finally {
3240            c.close();
3241        }
3242    }
3243
3244    // Stream item update test cases.
3245
3246    public void testUpdateStreamItemById() {
3247        long rawContactId = createRawContact();
3248        ContentValues values = buildGenericStreamItemValues();
3249        Uri resultUri = insertStreamItem(rawContactId, values, null);
3250        long streamItemId = ContentUris.parseId(resultUri);
3251        values.put(StreamItems.TEXT, "Goodbye world");
3252        mResolver.update(ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), values,
3253                null, null);
3254        assertStoredValues(Uri.withAppendedPath(
3255                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3256                RawContacts.StreamItems.CONTENT_DIRECTORY), values);
3257    }
3258
3259    public void testUpdateStreamItemWithContentValues() {
3260        long rawContactId = createRawContact();
3261        ContentValues values = buildGenericStreamItemValues();
3262        Uri resultUri = insertStreamItem(rawContactId, values, null);
3263        long streamItemId = ContentUris.parseId(resultUri);
3264        values.put(StreamItems._ID, streamItemId);
3265        values.put(StreamItems.TEXT, "Goodbye world");
3266        mResolver.update(StreamItems.CONTENT_URI, values, null, null);
3267        assertStoredValues(Uri.withAppendedPath(
3268                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3269                RawContacts.StreamItems.CONTENT_DIRECTORY), values);
3270    }
3271
3272    public void testUpdateStreamItemFromOtherAccount() {
3273        long rawContactId = createRawContact(mAccount);
3274        ContentValues values = buildGenericStreamItemValues();
3275        Uri resultUri = insertStreamItem(rawContactId, values, mAccount);
3276        long streamItemId = ContentUris.parseId(resultUri);
3277        values.put(StreamItems._ID, streamItemId);
3278        values.put(StreamItems.TEXT, "Goodbye world");
3279        try {
3280            mResolver.update(maybeAddAccountQueryParameters(StreamItems.CONTENT_URI, mAccountTwo),
3281                    values, null, null);
3282            fail("Should not be able to update stream items inserted by another account");
3283        } catch (SecurityException expected) {
3284            // Can't update the stream items from another account.
3285        }
3286    }
3287
3288    // Stream item photo update test cases.
3289
3290    public void testUpdateStreamItemPhotoById() throws IOException {
3291        long rawContactId = createRawContact();
3292        ContentValues values = buildGenericStreamItemValues();
3293        Uri resultUri = insertStreamItem(rawContactId, values, null);
3294        long streamItemId = ContentUris.parseId(resultUri);
3295        ContentValues photoValues = buildGenericStreamItemPhotoValues(1);
3296        resultUri = insertStreamItemPhoto(streamItemId, photoValues, null);
3297        long streamItemPhotoId = ContentUris.parseId(resultUri);
3298
3299        photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(
3300                R.drawable.nebula, PhotoSize.ORIGINAL));
3301        Uri photoUri =
3302                ContentUris.withAppendedId(
3303                        Uri.withAppendedPath(
3304                                ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3305                                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3306                        streamItemPhotoId);
3307        mResolver.update(photoUri, photoValues, null, null);
3308        photoValues.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3309        assertStoredValues(photoUri, photoValues);
3310
3311        // Check that the photo stored is the expected one.
3312        String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI);
3313        assertInputStreamContent(loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO),
3314                mResolver.openInputStream(Uri.parse(displayPhotoUri)));
3315    }
3316
3317    public void testUpdateStreamItemPhotoWithContentValues() throws IOException {
3318        long rawContactId = createRawContact();
3319        ContentValues values = buildGenericStreamItemValues();
3320        Uri resultUri = insertStreamItem(rawContactId, values, null);
3321        long streamItemId = ContentUris.parseId(resultUri);
3322        ContentValues photoValues = buildGenericStreamItemPhotoValues(1);
3323        resultUri = insertStreamItemPhoto(streamItemId, photoValues, null);
3324        long streamItemPhotoId = ContentUris.parseId(resultUri);
3325
3326        photoValues.put(StreamItemPhotos._ID, streamItemPhotoId);
3327        photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(
3328                R.drawable.nebula, PhotoSize.ORIGINAL));
3329        Uri photoUri =
3330                Uri.withAppendedPath(
3331                        ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3332                        StreamItems.StreamItemPhotos.CONTENT_DIRECTORY);
3333        mResolver.update(photoUri, photoValues, null, null);
3334        photoValues.remove(StreamItemPhotos.PHOTO);  // Removed during processing.
3335        assertStoredValues(photoUri, photoValues);
3336
3337        // Check that the photo stored is the expected one.
3338        String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI);
3339        assertInputStreamContent(loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO),
3340                mResolver.openInputStream(Uri.parse(displayPhotoUri)));
3341    }
3342
3343    public void testUpdateStreamItemPhotoFromOtherAccount() {
3344        long rawContactId = createRawContact(mAccount);
3345        ContentValues values = buildGenericStreamItemValues();
3346        Uri resultUri = insertStreamItem(rawContactId, values, mAccount);
3347        long streamItemId = ContentUris.parseId(resultUri);
3348        ContentValues photoValues = buildGenericStreamItemPhotoValues(1);
3349        resultUri = insertStreamItemPhoto(streamItemId, photoValues, mAccount);
3350        long streamItemPhotoId = ContentUris.parseId(resultUri);
3351
3352        photoValues.put(StreamItemPhotos._ID, streamItemPhotoId);
3353        photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(
3354                R.drawable.galaxy, PhotoSize.ORIGINAL));
3355        Uri photoUri =
3356                maybeAddAccountQueryParameters(
3357                        Uri.withAppendedPath(
3358                                ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3359                                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3360                        mAccountTwo);
3361        try {
3362            mResolver.update(photoUri, photoValues, null, null);
3363            fail("Should not be able to update stream item photos inserted by another account");
3364        } catch (SecurityException expected) {
3365            // Can't update a stream item photo inserted by another account.
3366        }
3367    }
3368
3369    // Stream item deletion test cases.
3370
3371    public void testDeleteStreamItemById() {
3372        long rawContactId = createRawContact();
3373        ContentValues firstValues = buildGenericStreamItemValues();
3374        Uri resultUri = insertStreamItem(rawContactId, firstValues, null);
3375        long firstStreamItemId = ContentUris.parseId(resultUri);
3376
3377        ContentValues secondValues = buildGenericStreamItemValues();
3378        secondValues.put(StreamItems.TEXT, "Goodbye world");
3379        insertStreamItem(rawContactId, secondValues, null);
3380
3381        // Delete the first stream item.
3382        mResolver.delete(ContentUris.withAppendedId(StreamItems.CONTENT_URI, firstStreamItemId),
3383                null, null);
3384
3385        // Check that only the second item remains.
3386        assertStoredValues(Uri.withAppendedPath(
3387                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3388                RawContacts.StreamItems.CONTENT_DIRECTORY), secondValues);
3389    }
3390
3391    public void testDeleteStreamItemWithSelection() {
3392        long rawContactId = createRawContact();
3393        ContentValues firstValues = buildGenericStreamItemValues();
3394        insertStreamItem(rawContactId, firstValues, null);
3395
3396        ContentValues secondValues = buildGenericStreamItemValues();
3397        secondValues.put(StreamItems.TEXT, "Goodbye world");
3398        insertStreamItem(rawContactId, secondValues, null);
3399
3400        // Delete the first stream item with a custom selection.
3401        mResolver.delete(StreamItems.CONTENT_URI, StreamItems.TEXT + "=?",
3402                new String[]{"Hello world"});
3403
3404        // Check that only the second item remains.
3405        assertStoredValues(Uri.withAppendedPath(
3406                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
3407                RawContacts.StreamItems.CONTENT_DIRECTORY), secondValues);
3408    }
3409
3410    public void testDeleteStreamItemFromOtherAccount() {
3411        long rawContactId = createRawContact(mAccount);
3412        long streamItemId = ContentUris.parseId(
3413                insertStreamItem(rawContactId, buildGenericStreamItemValues(), mAccount));
3414        try {
3415            mResolver.delete(
3416                    maybeAddAccountQueryParameters(
3417                            ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3418                            mAccountTwo), null, null);
3419            fail("Should not be able to delete stream item inserted by another account");
3420        } catch (SecurityException expected) {
3421            // Can't delete a stream item from another account.
3422        }
3423    }
3424
3425    // Stream item photo deletion test cases.
3426
3427    public void testDeleteStreamItemPhotoById() {
3428        long rawContactId = createRawContact();
3429        long streamItemId = ContentUris.parseId(
3430                insertStreamItem(rawContactId, buildGenericStreamItemValues(), null));
3431        long streamItemPhotoId = ContentUris.parseId(
3432                insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), null));
3433        mResolver.delete(
3434                ContentUris.withAppendedId(
3435                        Uri.withAppendedPath(
3436                                ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3437                                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3438                        streamItemPhotoId), null, null);
3439
3440        Cursor c = mResolver.query(StreamItems.CONTENT_PHOTO_URI,
3441                new String[]{StreamItemPhotos._ID},
3442                StreamItemPhotos.STREAM_ITEM_ID + "=?", new String[]{String.valueOf(streamItemId)},
3443                null);
3444        try {
3445            assertEquals("Expected photo to be deleted.", 0, c.getCount());
3446        } finally {
3447            c.close();
3448        }
3449    }
3450
3451    public void testDeleteStreamItemPhotoWithSelection() {
3452        long rawContactId = createRawContact();
3453        long streamItemId = ContentUris.parseId(
3454                insertStreamItem(rawContactId, buildGenericStreamItemValues(), null));
3455        ContentValues firstPhotoValues = buildGenericStreamItemPhotoValues(0);
3456        ContentValues secondPhotoValues = buildGenericStreamItemPhotoValues(1);
3457        insertStreamItemPhoto(streamItemId, firstPhotoValues, null);
3458        firstPhotoValues.remove(StreamItemPhotos.PHOTO);  // Removed while processing.
3459        insertStreamItemPhoto(streamItemId, secondPhotoValues, null);
3460        Uri photoUri = Uri.withAppendedPath(
3461                ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3462                StreamItems.StreamItemPhotos.CONTENT_DIRECTORY);
3463        mResolver.delete(photoUri, StreamItemPhotos.SORT_INDEX + "=1", null);
3464
3465        assertStoredValues(photoUri, firstPhotoValues);
3466    }
3467
3468    public void testDeleteStreamItemPhotoFromOtherAccount() {
3469        long rawContactId = createRawContact(mAccount);
3470        long streamItemId = ContentUris.parseId(
3471                insertStreamItem(rawContactId, buildGenericStreamItemValues(), mAccount));
3472        insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), mAccount);
3473        try {
3474            mResolver.delete(maybeAddAccountQueryParameters(
3475                    Uri.withAppendedPath(
3476                            ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId),
3477                            StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
3478                    mAccountTwo), null, null);
3479            fail("Should not be able to delete stream item photo inserted by another account");
3480        } catch (SecurityException expected) {
3481            // Can't delete a stream item photo from another account.
3482        }
3483    }
3484
3485    public void testQueryStreamItemLimit() {
3486        ContentValues values = new ContentValues();
3487        values.put(StreamItems.MAX_ITEMS, 5);
3488        assertStoredValues(StreamItems.CONTENT_LIMIT_URI, values);
3489    }
3490
3491    // Tests for inserting or updating stream items as a side-effect of making status updates
3492    // (forward-compatibility of status updates into the new social stream API).
3493
3494    public void testStreamItemInsertedOnStatusUpdate() {
3495
3496        // This method of creating a raw contact automatically inserts a status update with
3497        // the status message "hacking".
3498        ContentValues values = new ContentValues();
3499        long rawContactId = createRawContact(values, "18004664411",
3500                "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
3501                StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO |
3502                        StatusUpdates.CAPABILITY_HAS_VOICE);
3503
3504        ContentValues expectedValues = new ContentValues();
3505        expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId);
3506        expectedValues.put(StreamItems.TEXT, "hacking");
3507        assertStoredValues(RawContacts.CONTENT_URI.buildUpon()
3508                .appendPath(String.valueOf(rawContactId))
3509                .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(),
3510                expectedValues);
3511    }
3512
3513    public void testStreamItemUpdatedOnSecondStatusUpdate() {
3514
3515        // This method of creating a raw contact automatically inserts a status update with
3516        // the status message "hacking".
3517        ContentValues values = new ContentValues();
3518        int chatMode = StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO |
3519                StatusUpdates.CAPABILITY_HAS_VOICE;
3520        long rawContactId = createRawContact(values, "18004664411",
3521                "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, chatMode);
3522
3523        // Insert a new status update for the raw contact.
3524        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com",
3525                StatusUpdates.INVISIBLE, "finished hacking", chatMode);
3526
3527        ContentValues expectedValues = new ContentValues();
3528        expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId);
3529        expectedValues.put(StreamItems.TEXT, "finished hacking");
3530        assertStoredValues(RawContacts.CONTENT_URI.buildUpon()
3531                .appendPath(String.valueOf(rawContactId))
3532                .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(),
3533                expectedValues);
3534    }
3535
3536    private ContentValues buildGenericStreamItemValues() {
3537        ContentValues values = new ContentValues();
3538        values.put(StreamItems.TEXT, "Hello world");
3539        values.put(StreamItems.TIMESTAMP, System.currentTimeMillis());
3540        values.put(StreamItems.COMMENTS, "Reshared by 123 others");
3541        return values;
3542    }
3543
3544    private ContentValues buildGenericStreamItemPhotoValues(int sortIndex) {
3545        ContentValues values = new ContentValues();
3546        values.put(StreamItemPhotos.SORT_INDEX, sortIndex);
3547        values.put(StreamItemPhotos.PHOTO,
3548                loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.ORIGINAL));
3549        return values;
3550    }
3551
3552    public void testSingleStatusUpdateRowPerContact() {
3553        int protocol1 = Im.PROTOCOL_GOOGLE_TALK;
3554        String handle1 = "test@gmail.com";
3555
3556        long rawContactId1 = createRawContact();
3557        insertImHandle(rawContactId1, protocol1, null, handle1);
3558
3559        insertStatusUpdate(protocol1, null, handle1, StatusUpdates.AVAILABLE, "Green",
3560                StatusUpdates.CAPABILITY_HAS_CAMERA);
3561        insertStatusUpdate(protocol1, null, handle1, StatusUpdates.AWAY, "Yellow",
3562                StatusUpdates.CAPABILITY_HAS_CAMERA);
3563        insertStatusUpdate(protocol1, null, handle1, StatusUpdates.INVISIBLE, "Red",
3564                StatusUpdates.CAPABILITY_HAS_CAMERA);
3565
3566        Cursor c = queryContact(queryContactId(rawContactId1),
3567                new String[] {Contacts.CONTACT_PRESENCE, Contacts.CONTACT_STATUS});
3568        assertEquals(1, c.getCount());
3569
3570        c.moveToFirst();
3571        assertEquals(StatusUpdates.INVISIBLE, c.getInt(0));
3572        assertEquals("Red", c.getString(1));
3573        c.close();
3574    }
3575
3576    private void updateSendToVoicemailAndRingtone(long contactId, boolean sendToVoicemail,
3577            String ringtone) {
3578        ContentValues values = new ContentValues();
3579        values.put(Contacts.SEND_TO_VOICEMAIL, sendToVoicemail);
3580        if (ringtone != null) {
3581            values.put(Contacts.CUSTOM_RINGTONE, ringtone);
3582        }
3583
3584        final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
3585        int count = mResolver.update(uri, values, null, null);
3586        assertEquals(1, count);
3587    }
3588
3589    private void updateSendToVoicemailAndRingtoneWithSelection(long contactId,
3590            boolean sendToVoicemail, String ringtone) {
3591        ContentValues values = new ContentValues();
3592        values.put(Contacts.SEND_TO_VOICEMAIL, sendToVoicemail);
3593        if (ringtone != null) {
3594            values.put(Contacts.CUSTOM_RINGTONE, ringtone);
3595        }
3596
3597        int count = mResolver.update(Contacts.CONTENT_URI, values, Contacts._ID + "=" + contactId,
3598                null);
3599        assertEquals(1, count);
3600    }
3601
3602    private void assertSendToVoicemailAndRingtone(long contactId, boolean expectedSendToVoicemail,
3603            String expectedRingtone) {
3604        Cursor c = queryContact(contactId);
3605        assertTrue(c.moveToNext());
3606        int sendToVoicemail = c.getInt(c.getColumnIndex(Contacts.SEND_TO_VOICEMAIL));
3607        assertEquals(expectedSendToVoicemail ? 1 : 0, sendToVoicemail);
3608        String ringtone = c.getString(c.getColumnIndex(Contacts.CUSTOM_RINGTONE));
3609        if (expectedRingtone == null) {
3610            assertNull(ringtone);
3611        } else {
3612            assertTrue(ArrayUtils.contains(expectedRingtone.split(","), ringtone));
3613        }
3614        c.close();
3615    }
3616
3617    public void testGroupCreationAfterMembershipInsert() {
3618        long rawContactId1 = createRawContact(mAccount);
3619        Uri groupMembershipUri = insertGroupMembership(rawContactId1, "gsid1");
3620
3621        long groupId = assertSingleGroup(NO_LONG, mAccount, "gsid1", null);
3622        assertSingleGroupMembership(ContentUris.parseId(groupMembershipUri),
3623                rawContactId1, groupId, "gsid1");
3624    }
3625
3626    public void testGroupReuseAfterMembershipInsert() {
3627        long rawContactId1 = createRawContact(mAccount);
3628        long groupId1 = createGroup(mAccount, "gsid1", "title1");
3629        Uri groupMembershipUri = insertGroupMembership(rawContactId1, "gsid1");
3630
3631        assertSingleGroup(groupId1, mAccount, "gsid1", "title1");
3632        assertSingleGroupMembership(ContentUris.parseId(groupMembershipUri),
3633                rawContactId1, groupId1, "gsid1");
3634    }
3635
3636    public void testGroupInsertFailureOnGroupIdConflict() {
3637        long rawContactId1 = createRawContact(mAccount);
3638        long groupId1 = createGroup(mAccount, "gsid1", "title1");
3639
3640        ContentValues values = new ContentValues();
3641        values.put(GroupMembership.RAW_CONTACT_ID, rawContactId1);
3642        values.put(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
3643        values.put(GroupMembership.GROUP_SOURCE_ID, "gsid1");
3644        values.put(GroupMembership.GROUP_ROW_ID, groupId1);
3645        try {
3646            mResolver.insert(Data.CONTENT_URI, values);
3647            fail("the insert was expected to fail, but it succeeded");
3648        } catch (IllegalArgumentException e) {
3649            // this was expected
3650        }
3651    }
3652
3653    public void testContactVisibilityUpdateOnMembershipChange() {
3654        long rawContactId = createRawContact(mAccount);
3655        assertVisibility(rawContactId, "0");
3656
3657        long visibleGroupId = createGroup(mAccount, "123", "Visible", 1);
3658        long invisibleGroupId = createGroup(mAccount, "567", "Invisible", 0);
3659
3660        Uri membership1 = insertGroupMembership(rawContactId, visibleGroupId);
3661        assertVisibility(rawContactId, "1");
3662
3663        Uri membership2 = insertGroupMembership(rawContactId, invisibleGroupId);
3664        assertVisibility(rawContactId, "1");
3665
3666        mResolver.delete(membership1, null, null);
3667        assertVisibility(rawContactId, "0");
3668
3669        ContentValues values = new ContentValues();
3670        values.put(GroupMembership.GROUP_ROW_ID, visibleGroupId);
3671
3672        mResolver.update(membership2, values, null, null);
3673        assertVisibility(rawContactId, "1");
3674    }
3675
3676    private void assertVisibility(long rawContactId, String expectedValue) {
3677        assertStoredValue(Contacts.CONTENT_URI, Contacts._ID + "=" + queryContactId(rawContactId),
3678                null, Contacts.IN_VISIBLE_GROUP, expectedValue);
3679    }
3680
3681    public void testSupplyingBothValuesAndParameters() throws Exception {
3682        Account account = new Account("account 1", "type%/:1");
3683        Uri uri = ContactsContract.Groups.CONTENT_URI.buildUpon()
3684                .appendQueryParameter(ContactsContract.Groups.ACCOUNT_NAME, account.name)
3685                .appendQueryParameter(ContactsContract.Groups.ACCOUNT_TYPE, account.type)
3686                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
3687                .build();
3688
3689        ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri);
3690        builder.withValue(ContactsContract.Groups.ACCOUNT_TYPE, account.type);
3691        builder.withValue(ContactsContract.Groups.ACCOUNT_NAME, account.name);
3692        builder.withValue(ContactsContract.Groups.SYSTEM_ID, "some id");
3693        builder.withValue(ContactsContract.Groups.TITLE, "some name");
3694        builder.withValue(ContactsContract.Groups.GROUP_VISIBLE, 1);
3695
3696        mResolver.applyBatch(ContactsContract.AUTHORITY, Lists.newArrayList(builder.build()));
3697
3698        builder = ContentProviderOperation.newInsert(uri);
3699        builder.withValue(ContactsContract.Groups.ACCOUNT_TYPE, account.type + "diff");
3700        builder.withValue(ContactsContract.Groups.ACCOUNT_NAME, account.name);
3701        builder.withValue(ContactsContract.Groups.SYSTEM_ID, "some other id");
3702        builder.withValue(ContactsContract.Groups.TITLE, "some other name");
3703        builder.withValue(ContactsContract.Groups.GROUP_VISIBLE, 1);
3704
3705        try {
3706            mResolver.applyBatch(ContactsContract.AUTHORITY, Lists.newArrayList(builder.build()));
3707            fail("Expected IllegalArgumentException");
3708        } catch (IllegalArgumentException ex) {
3709            // Expected
3710        }
3711    }
3712
3713    public void testContentEntityIterator() {
3714        // create multiple contacts and check that the selected ones are returned
3715        long id;
3716
3717        long groupId1 = createGroup(mAccount, "gsid1", "title1");
3718        long groupId2 = createGroup(mAccount, "gsid2", "title2");
3719
3720        id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c0");
3721        insertGroupMembership(id, "gsid1");
3722        insertEmail(id, "c0@email.com");
3723        insertPhoneNumber(id, "5551212c0");
3724
3725        long c1 = id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c1");
3726        Uri id_1_0 = insertGroupMembership(id, "gsid1");
3727        Uri id_1_1 = insertGroupMembership(id, "gsid2");
3728        Uri id_1_2 = insertEmail(id, "c1@email.com");
3729        Uri id_1_3 = insertPhoneNumber(id, "5551212c1");
3730
3731        long c2 = id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c2");
3732        Uri id_2_0 = insertGroupMembership(id, "gsid1");
3733        Uri id_2_1 = insertEmail(id, "c2@email.com");
3734        Uri id_2_2 = insertPhoneNumber(id, "5551212c2");
3735
3736        long c3 = id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c3");
3737        Uri id_3_0 = insertGroupMembership(id, groupId2);
3738        Uri id_3_1 = insertEmail(id, "c3@email.com");
3739        Uri id_3_2 = insertPhoneNumber(id, "5551212c3");
3740
3741        EntityIterator iterator = RawContacts.newEntityIterator(mResolver.query(
3742                maybeAddAccountQueryParameters(RawContactsEntity.CONTENT_URI, mAccount), null,
3743                RawContacts.SOURCE_ID + " in ('c1', 'c2', 'c3')", null, null));
3744        Entity entity;
3745        ContentValues[] subValues;
3746        entity = iterator.next();
3747        assertEquals(c1, (long) entity.getEntityValues().getAsLong(RawContacts._ID));
3748        subValues = asSortedContentValuesArray(entity.getSubValues());
3749        assertEquals(4, subValues.length);
3750        assertDataRow(subValues[0], GroupMembership.CONTENT_ITEM_TYPE,
3751                Data._ID, id_1_0,
3752                GroupMembership.GROUP_ROW_ID, groupId1,
3753                GroupMembership.GROUP_SOURCE_ID, "gsid1");
3754        assertDataRow(subValues[1], GroupMembership.CONTENT_ITEM_TYPE,
3755                Data._ID, id_1_1,
3756                GroupMembership.GROUP_ROW_ID, groupId2,
3757                GroupMembership.GROUP_SOURCE_ID, "gsid2");
3758        assertDataRow(subValues[2], Email.CONTENT_ITEM_TYPE,
3759                Data._ID, id_1_2,
3760                Email.DATA, "c1@email.com");
3761        assertDataRow(subValues[3], Phone.CONTENT_ITEM_TYPE,
3762                Data._ID, id_1_3,
3763                Email.DATA, "5551212c1");
3764
3765        entity = iterator.next();
3766        assertEquals(c2, (long) entity.getEntityValues().getAsLong(RawContacts._ID));
3767        subValues = asSortedContentValuesArray(entity.getSubValues());
3768        assertEquals(3, subValues.length);
3769        assertDataRow(subValues[0], GroupMembership.CONTENT_ITEM_TYPE,
3770                Data._ID, id_2_0,
3771                GroupMembership.GROUP_ROW_ID, groupId1,
3772                GroupMembership.GROUP_SOURCE_ID, "gsid1");
3773        assertDataRow(subValues[1], Email.CONTENT_ITEM_TYPE,
3774                Data._ID, id_2_1,
3775                Email.DATA, "c2@email.com");
3776        assertDataRow(subValues[2], Phone.CONTENT_ITEM_TYPE,
3777                Data._ID, id_2_2,
3778                Email.DATA, "5551212c2");
3779
3780        entity = iterator.next();
3781        assertEquals(c3, (long) entity.getEntityValues().getAsLong(RawContacts._ID));
3782        subValues = asSortedContentValuesArray(entity.getSubValues());
3783        assertEquals(3, subValues.length);
3784        assertDataRow(subValues[0], GroupMembership.CONTENT_ITEM_TYPE,
3785                Data._ID, id_3_0,
3786                GroupMembership.GROUP_ROW_ID, groupId2,
3787                GroupMembership.GROUP_SOURCE_ID, "gsid2");
3788        assertDataRow(subValues[1], Email.CONTENT_ITEM_TYPE,
3789                Data._ID, id_3_1,
3790                Email.DATA, "c3@email.com");
3791        assertDataRow(subValues[2], Phone.CONTENT_ITEM_TYPE,
3792                Data._ID, id_3_2,
3793                Email.DATA, "5551212c3");
3794
3795        assertFalse(iterator.hasNext());
3796        iterator.close();
3797    }
3798
3799    public void testDataCreateUpdateDeleteByMimeType() throws Exception {
3800        long rawContactId = createRawContact();
3801
3802        ContentValues values = new ContentValues();
3803        values.put(Data.RAW_CONTACT_ID, rawContactId);
3804        values.put(Data.MIMETYPE, "testmimetype");
3805        values.put(Data.RES_PACKAGE, "oldpackage");
3806        values.put(Data.IS_PRIMARY, 1);
3807        values.put(Data.IS_SUPER_PRIMARY, 1);
3808        values.put(Data.DATA1, "old1");
3809        values.put(Data.DATA2, "old2");
3810        values.put(Data.DATA3, "old3");
3811        values.put(Data.DATA4, "old4");
3812        values.put(Data.DATA5, "old5");
3813        values.put(Data.DATA6, "old6");
3814        values.put(Data.DATA7, "old7");
3815        values.put(Data.DATA8, "old8");
3816        values.put(Data.DATA9, "old9");
3817        values.put(Data.DATA10, "old10");
3818        values.put(Data.DATA11, "old11");
3819        values.put(Data.DATA12, "old12");
3820        values.put(Data.DATA13, "old13");
3821        values.put(Data.DATA14, "old14");
3822        values.put(Data.DATA15, "old15");
3823        Uri uri = mResolver.insert(Data.CONTENT_URI, values);
3824        assertStoredValues(uri, values);
3825        assertNetworkNotified(true);
3826
3827        values.clear();
3828        values.put(Data.RES_PACKAGE, "newpackage");
3829        values.put(Data.IS_PRIMARY, 0);
3830        values.put(Data.IS_SUPER_PRIMARY, 0);
3831        values.put(Data.DATA1, "new1");
3832        values.put(Data.DATA2, "new2");
3833        values.put(Data.DATA3, "new3");
3834        values.put(Data.DATA4, "new4");
3835        values.put(Data.DATA5, "new5");
3836        values.put(Data.DATA6, "new6");
3837        values.put(Data.DATA7, "new7");
3838        values.put(Data.DATA8, "new8");
3839        values.put(Data.DATA9, "new9");
3840        values.put(Data.DATA10, "new10");
3841        values.put(Data.DATA11, "new11");
3842        values.put(Data.DATA12, "new12");
3843        values.put(Data.DATA13, "new13");
3844        values.put(Data.DATA14, "new14");
3845        values.put(Data.DATA15, "new15");
3846        mResolver.update(Data.CONTENT_URI, values, Data.RAW_CONTACT_ID + "=" + rawContactId +
3847                " AND " + Data.MIMETYPE + "='testmimetype'", null);
3848        assertNetworkNotified(true);
3849
3850        assertStoredValues(uri, values);
3851
3852        int count = mResolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=" + rawContactId
3853                + " AND " + Data.MIMETYPE + "='testmimetype'", null);
3854        assertEquals(1, count);
3855        assertEquals(0, getCount(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=" + rawContactId
3856                        + " AND " + Data.MIMETYPE + "='testmimetype'", null));
3857        assertNetworkNotified(true);
3858    }
3859
3860    public void testRawContactQuery() {
3861        Account account1 = new Account("a", "b");
3862        Account account2 = new Account("c", "d");
3863        long rawContactId1 = createRawContact(account1);
3864        long rawContactId2 = createRawContact(account2);
3865
3866        Uri uri1 = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account1);
3867        Uri uri2 = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account2);
3868        assertEquals(1, getCount(uri1, null, null));
3869        assertEquals(1, getCount(uri2, null, null));
3870        assertStoredValue(uri1, RawContacts._ID, rawContactId1) ;
3871        assertStoredValue(uri2, RawContacts._ID, rawContactId2) ;
3872
3873        Uri rowUri1 = ContentUris.withAppendedId(uri1, rawContactId1);
3874        Uri rowUri2 = ContentUris.withAppendedId(uri2, rawContactId2);
3875        assertStoredValue(rowUri1, RawContacts._ID, rawContactId1) ;
3876        assertStoredValue(rowUri2, RawContacts._ID, rawContactId2) ;
3877    }
3878
3879    public void testRawContactDeletion() {
3880        long rawContactId = createRawContact(mAccount);
3881        Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
3882
3883        insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com");
3884        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com",
3885                StatusUpdates.AVAILABLE, null,
3886                StatusUpdates.CAPABILITY_HAS_CAMERA);
3887        long contactId = queryContactId(rawContactId);
3888
3889        assertEquals(1, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY),
3890                null, null));
3891        assertEquals(1, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "="
3892                + rawContactId, null));
3893
3894        mResolver.delete(uri, null, null);
3895
3896        assertStoredValue(uri, RawContacts.DELETED, "1");
3897        assertNetworkNotified(true);
3898
3899        Uri permanentDeletionUri = setCallerIsSyncAdapter(uri, mAccount);
3900        mResolver.delete(permanentDeletionUri, null, null);
3901        assertEquals(0, getCount(uri, null, null));
3902        assertEquals(0, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY),
3903                null, null));
3904        assertEquals(0, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "="
3905                + rawContactId, null));
3906        assertEquals(0, getCount(Contacts.CONTENT_URI, Contacts._ID + "=" + contactId, null));
3907        assertNetworkNotified(false);
3908    }
3909
3910    public void testRawContactDeletionKeepingAggregateContact() {
3911        long rawContactId1 = createRawContactWithName(mAccount);
3912        long rawContactId2 = createRawContactWithName(mAccount);
3913        setAggregationException(
3914                AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
3915
3916        long contactId = queryContactId(rawContactId1);
3917
3918        Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
3919        Uri permanentDeletionUri = setCallerIsSyncAdapter(uri, mAccount);
3920        mResolver.delete(permanentDeletionUri, null, null);
3921        assertEquals(0, getCount(uri, null, null));
3922        assertEquals(1, getCount(Contacts.CONTENT_URI, Contacts._ID + "=" + contactId, null));
3923    }
3924
3925    public void testRawContactDeletionWithAccounts() {
3926        long rawContactId = createRawContact(mAccount);
3927        Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
3928
3929        insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com");
3930        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com",
3931                StatusUpdates.AVAILABLE, null,
3932                StatusUpdates.CAPABILITY_HAS_CAMERA);
3933        assertEquals(1, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY),
3934                null, null));
3935        assertEquals(1, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "="
3936                + rawContactId, null));
3937
3938        // Do not delete if we are deleting with wrong account.
3939        Uri deleteWithWrongAccountUri =
3940            RawContacts.CONTENT_URI.buildUpon()
3941                .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, mAccountTwo.name)
3942                .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccountTwo.type)
3943                .build();
3944        mResolver.delete(deleteWithWrongAccountUri, null, null);
3945
3946        assertStoredValue(uri, RawContacts.DELETED, "0");
3947
3948        // Delete if we are deleting with correct account.
3949        Uri deleteWithCorrectAccountUri =
3950            RawContacts.CONTENT_URI.buildUpon()
3951                .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, mAccount.name)
3952                .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccount.type)
3953                .build();
3954        mResolver.delete(deleteWithCorrectAccountUri, null, null);
3955
3956        assertStoredValue(uri, RawContacts.DELETED, "1");
3957    }
3958
3959    public void testAccountsUpdated() {
3960        // This is to ensure we do not delete contacts with null, null (account name, type)
3961        // accidentally.
3962        long rawContactId3 = createRawContactWithName("James", "Sullivan");
3963        insertPhoneNumber(rawContactId3, "5234567890");
3964        Uri rawContact3 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId3);
3965        assertEquals(1, getCount(RawContacts.CONTENT_URI, null, null));
3966
3967        ContactsProvider2 cp = (ContactsProvider2) getProvider();
3968        mActor.setAccounts(new Account[]{mAccount, mAccountTwo});
3969        cp.onAccountsUpdated(new Account[]{mAccount, mAccountTwo});
3970        assertEquals(1, getCount(RawContacts.CONTENT_URI, null, null));
3971        assertStoredValue(rawContact3, RawContacts.ACCOUNT_NAME, null);
3972        assertStoredValue(rawContact3, RawContacts.ACCOUNT_TYPE, null);
3973
3974        long rawContactId1 = createRawContact(mAccount);
3975        insertEmail(rawContactId1, "account1@email.com");
3976        long rawContactId2 = createRawContact(mAccountTwo);
3977        insertEmail(rawContactId2, "account2@email.com");
3978        insertImHandle(rawContactId2, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com");
3979        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com",
3980                StatusUpdates.AVAILABLE, null,
3981                StatusUpdates.CAPABILITY_HAS_CAMERA);
3982
3983        mActor.setAccounts(new Account[]{mAccount});
3984        cp.onAccountsUpdated(new Account[]{mAccount});
3985        assertEquals(2, getCount(RawContacts.CONTENT_URI, null, null));
3986        assertEquals(0, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "="
3987                + rawContactId2, null));
3988    }
3989
3990    public void testAccountDeletion() {
3991        Account readOnlyAccount = new Account("act", READ_ONLY_ACCOUNT_TYPE);
3992        ContactsProvider2 cp = (ContactsProvider2) getProvider();
3993        mActor.setAccounts(new Account[]{readOnlyAccount, mAccount});
3994        cp.onAccountsUpdated(new Account[]{readOnlyAccount, mAccount});
3995
3996        long rawContactId1 = createRawContactWithName("John", "Doe", readOnlyAccount);
3997        Uri photoUri1 = insertPhoto(rawContactId1);
3998        long rawContactId2 = createRawContactWithName("john", "doe", mAccount);
3999        Uri photoUri2 = insertPhoto(rawContactId2);
4000        storeValue(photoUri2, Photo.IS_SUPER_PRIMARY, "1");
4001
4002        assertAggregated(rawContactId1, rawContactId2);
4003
4004        long contactId = queryContactId(rawContactId1);
4005
4006        // The display name should come from the writable account
4007        assertStoredValue(Uri.withAppendedPath(
4008                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4009                Contacts.Data.CONTENT_DIRECTORY),
4010                Contacts.DISPLAY_NAME, "john doe");
4011
4012        // The photo should be the one we marked as super-primary
4013        assertStoredValue(Contacts.CONTENT_URI, contactId,
4014                Contacts.PHOTO_ID, ContentUris.parseId(photoUri2));
4015
4016        mActor.setAccounts(new Account[]{readOnlyAccount});
4017        // Remove the writable account
4018        cp.onAccountsUpdated(new Account[]{readOnlyAccount});
4019
4020        // The display name should come from the remaining account
4021        assertStoredValue(Uri.withAppendedPath(
4022                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4023                Contacts.Data.CONTENT_DIRECTORY),
4024                Contacts.DISPLAY_NAME, "John Doe");
4025
4026        // The photo should be the remaining one
4027        assertStoredValue(Contacts.CONTENT_URI, contactId,
4028                Contacts.PHOTO_ID, ContentUris.parseId(photoUri1));
4029    }
4030
4031    public void testContactDeletion() {
4032        long rawContactId1 = createRawContactWithName("John", "Doe", ACCOUNT_1);
4033        long rawContactId2 = createRawContactWithName("John", "Doe", ACCOUNT_2);
4034
4035        long contactId = queryContactId(rawContactId1);
4036
4037        mResolver.delete(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), null, null);
4038
4039        assertStoredValue(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1),
4040                RawContacts.DELETED, "1");
4041        assertStoredValue(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2),
4042                RawContacts.DELETED, "1");
4043    }
4044
4045    public void testMarkAsDirtyParameter() {
4046        long rawContactId = createRawContact(mAccount);
4047        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
4048
4049        Uri uri = insertStructuredName(rawContactId, "John", "Doe");
4050        clearDirty(rawContactUri);
4051        Uri updateUri = setCallerIsSyncAdapter(uri, mAccount);
4052
4053        ContentValues values = new ContentValues();
4054        values.put(StructuredName.FAMILY_NAME, "Dough");
4055        mResolver.update(updateUri, values, null, null);
4056        assertStoredValue(uri, StructuredName.FAMILY_NAME, "Dough");
4057        assertDirty(rawContactUri, false);
4058        assertNetworkNotified(false);
4059    }
4060
4061    public void testRawContactDirtyAndVersion() {
4062        final long rawContactId = createRawContact(mAccount);
4063        Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId);
4064        assertDirty(uri, false);
4065        long version = getVersion(uri);
4066
4067        ContentValues values = new ContentValues();
4068        values.put(ContactsContract.RawContacts.DIRTY, 0);
4069        values.put(ContactsContract.RawContacts.SEND_TO_VOICEMAIL, 1);
4070        values.put(ContactsContract.RawContacts.AGGREGATION_MODE,
4071                RawContacts.AGGREGATION_MODE_IMMEDIATE);
4072        values.put(ContactsContract.RawContacts.STARRED, 1);
4073        assertEquals(1, mResolver.update(uri, values, null, null));
4074        assertEquals(version, getVersion(uri));
4075
4076        assertDirty(uri, false);
4077        assertNetworkNotified(false);
4078
4079        Uri emailUri = insertEmail(rawContactId, "goo@woo.com");
4080        assertDirty(uri, true);
4081        assertNetworkNotified(true);
4082        ++version;
4083        assertEquals(version, getVersion(uri));
4084        clearDirty(uri);
4085
4086        values = new ContentValues();
4087        values.put(Email.DATA, "goo@hoo.com");
4088        mResolver.update(emailUri, values, null, null);
4089        assertDirty(uri, true);
4090        assertNetworkNotified(true);
4091        ++version;
4092        assertEquals(version, getVersion(uri));
4093        clearDirty(uri);
4094
4095        mResolver.delete(emailUri, null, null);
4096        assertDirty(uri, true);
4097        assertNetworkNotified(true);
4098        ++version;
4099        assertEquals(version, getVersion(uri));
4100    }
4101
4102    public void testRawContactClearDirty() {
4103        final long rawContactId = createRawContact(mAccount);
4104        Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI,
4105                rawContactId);
4106        long version = getVersion(uri);
4107        insertEmail(rawContactId, "goo@woo.com");
4108        assertDirty(uri, true);
4109        version++;
4110        assertEquals(version, getVersion(uri));
4111
4112        clearDirty(uri);
4113        assertDirty(uri, false);
4114        assertEquals(version, getVersion(uri));
4115    }
4116
4117    public void testRawContactDeletionSetsDirty() {
4118        final long rawContactId = createRawContact(mAccount);
4119        Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI,
4120                rawContactId);
4121        long version = getVersion(uri);
4122        clearDirty(uri);
4123        assertDirty(uri, false);
4124
4125        mResolver.delete(uri, null, null);
4126        assertStoredValue(uri, RawContacts.DELETED, "1");
4127        assertDirty(uri, true);
4128        assertNetworkNotified(true);
4129        version++;
4130        assertEquals(version, getVersion(uri));
4131    }
4132
4133    public void testDeleteContactWithoutName() {
4134        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, new ContentValues());
4135        long rawContactId = ContentUris.parseId(rawContactUri);
4136
4137        Uri phoneUri = insertPhoneNumber(rawContactId, "555-123-45678", true);
4138
4139        long contactId = queryContactId(rawContactId);
4140        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4141        Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri);
4142
4143        int numDeleted = mResolver.delete(lookupUri, null, null);
4144        assertEquals(1, numDeleted);
4145    }
4146
4147    public void testDeleteContactWithoutAnyData() {
4148        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, new ContentValues());
4149        long rawContactId = ContentUris.parseId(rawContactUri);
4150
4151        long contactId = queryContactId(rawContactId);
4152        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4153        Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri);
4154
4155        int numDeleted = mResolver.delete(lookupUri, null, null);
4156        assertEquals(1, numDeleted);
4157    }
4158
4159    public void testDeleteContactWithEscapedUri() {
4160        ContentValues values = new ContentValues();
4161        values.put(RawContacts.SOURCE_ID, "!@#$%^&*()_+=-/.,<>?;'\":[]}{\\|`~");
4162        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
4163        long rawContactId = ContentUris.parseId(rawContactUri);
4164
4165        long contactId = queryContactId(rawContactId);
4166        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4167        Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri);
4168        assertEquals(1, mResolver.delete(lookupUri, null, null));
4169    }
4170
4171    public void testQueryContactWithEscapedUri() {
4172        ContentValues values = new ContentValues();
4173        values.put(RawContacts.SOURCE_ID, "!@#$%^&*()_+=-/.,<>?;'\":[]}{\\|`~");
4174        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
4175        long rawContactId = ContentUris.parseId(rawContactUri);
4176
4177        long contactId = queryContactId(rawContactId);
4178        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4179        Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri);
4180        Cursor c = mResolver.query(lookupUri, null, null, null, "");
4181        assertEquals(1, c.getCount());
4182        c.close();
4183    }
4184
4185    public void testGetPhotoUri() {
4186        ContentValues values = new ContentValues();
4187        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
4188        long rawContactId = ContentUris.parseId(rawContactUri);
4189        insertStructuredName(rawContactId, "John", "Doe");
4190        long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal));
4191        long photoFileId = getStoredLongValue(Data.CONTENT_URI, Data._ID + "=?",
4192                new String[]{String.valueOf(dataId)}, Photo.PHOTO_FILE_ID);
4193        String photoUri = ContentUris.withAppendedId(DisplayPhoto.CONTENT_URI, photoFileId)
4194                .toString();
4195
4196        assertStoredValue(
4197                ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)),
4198                Contacts.PHOTO_URI, photoUri);
4199    }
4200
4201    public void testInputStreamForPhoto() throws Exception {
4202        long rawContactId = createRawContact();
4203        long contactId = queryContactId(rawContactId);
4204        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4205        insertPhoto(rawContactId);
4206        Uri photoUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_URI));
4207        Uri photoThumbnailUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI));
4208
4209        assertInputStreamContent(loadTestPhoto(PhotoSize.DISPLAY_PHOTO),
4210                mResolver.openInputStream(photoUri));
4211        assertInputStreamContent(loadTestPhoto(PhotoSize.THUMBNAIL),
4212                mResolver.openInputStream(photoThumbnailUri));
4213    }
4214
4215    private static void assertInputStreamContent(byte[] expected, InputStream is)
4216            throws IOException {
4217        try {
4218            byte[] observed = new byte[expected.length];
4219            int count = is.read(observed);
4220            assertEquals(expected.length, count);
4221            assertEquals(-1, is.read());
4222            MoreAsserts.assertEquals(expected, observed);
4223        } finally {
4224            is.close();
4225        }
4226    }
4227
4228    public void testSuperPrimaryPhoto() {
4229        long rawContactId1 = createRawContact(new Account("a", "a"));
4230        Uri photoUri1 = insertPhoto(rawContactId1, R.drawable.earth_normal);
4231        long photoId1 = ContentUris.parseId(photoUri1);
4232
4233        long rawContactId2 = createRawContact(new Account("b", "b"));
4234        Uri photoUri2 = insertPhoto(rawContactId2, R.drawable.earth_normal);
4235        long photoId2 = ContentUris.parseId(photoUri2);
4236
4237        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
4238                rawContactId1, rawContactId2);
4239
4240        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
4241                queryContactId(rawContactId1));
4242
4243        long photoFileId1 = getStoredLongValue(Data.CONTENT_URI, Data._ID + "=?",
4244                new String[]{String.valueOf(photoId1)}, Photo.PHOTO_FILE_ID);
4245        String photoUri = ContentUris.withAppendedId(DisplayPhoto.CONTENT_URI, photoFileId1)
4246                .toString();
4247        assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId1);
4248        assertStoredValue(contactUri, Contacts.PHOTO_URI, photoUri);
4249
4250        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
4251                rawContactId1, rawContactId2);
4252
4253        ContentValues values = new ContentValues();
4254        values.put(Data.IS_SUPER_PRIMARY, 1);
4255        mResolver.update(photoUri2, values, null, null);
4256
4257        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
4258                rawContactId1, rawContactId2);
4259        contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
4260                queryContactId(rawContactId1));
4261        assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId2);
4262
4263        mResolver.update(photoUri1, values, null, null);
4264        assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId1);
4265    }
4266
4267    public void testUpdatePhoto() {
4268        ContentValues values = new ContentValues();
4269        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
4270        long rawContactId = ContentUris.parseId(rawContactUri);
4271        insertStructuredName(rawContactId, "John", "Doe");
4272
4273        Uri twigUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
4274                queryContactId(rawContactId)), Contacts.Photo.CONTENT_DIRECTORY);
4275
4276        values.clear();
4277        values.put(Data.RAW_CONTACT_ID, rawContactId);
4278        values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
4279        values.putNull(Photo.PHOTO);
4280        Uri dataUri = mResolver.insert(Data.CONTENT_URI, values);
4281        long photoId = ContentUris.parseId(dataUri);
4282
4283        assertEquals(0, getCount(twigUri, null, null));
4284
4285        values.clear();
4286        values.put(Photo.PHOTO, loadTestPhoto());
4287        mResolver.update(dataUri, values, null, null);
4288        assertNetworkNotified(true);
4289
4290        long twigId = getStoredLongValue(twigUri, Data._ID);
4291        assertEquals(photoId, twigId);
4292    }
4293
4294    public void testUpdateRawContactDataPhoto() {
4295        // setup a contact with a null photo
4296        ContentValues values = new ContentValues();
4297        Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
4298        long rawContactId = ContentUris.parseId(rawContactUri);
4299
4300        // setup a photo
4301        values.put(Data.RAW_CONTACT_ID, rawContactId);
4302        values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
4303        values.putNull(Photo.PHOTO);
4304
4305        // try to do an update before insert should return count == 0
4306        Uri dataUri = Uri.withAppendedPath(
4307                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
4308                RawContacts.Data.CONTENT_DIRECTORY);
4309        assertEquals(0, mResolver.update(dataUri, values, Data.MIMETYPE + "=?",
4310                new String[] {Photo.CONTENT_ITEM_TYPE}));
4311
4312        mResolver.insert(Data.CONTENT_URI, values);
4313
4314        // save a photo to the db
4315        values.clear();
4316        values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
4317        values.put(Photo.PHOTO, loadTestPhoto());
4318        assertEquals(1, mResolver.update(dataUri, values, Data.MIMETYPE + "=?",
4319                new String[] {Photo.CONTENT_ITEM_TYPE}));
4320
4321        // verify the photo
4322        Cursor storedPhoto = mResolver.query(dataUri, new String[] {Photo.PHOTO},
4323                Data.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE}, null);
4324        storedPhoto.moveToFirst();
4325        MoreAsserts.assertEquals(loadTestPhoto(PhotoSize.THUMBNAIL), storedPhoto.getBlob(0));
4326        storedPhoto.close();
4327    }
4328
4329    public void testOpenDisplayPhotoForContactId() throws IOException {
4330        long rawContactId = createRawContactWithName();
4331        long contactId = queryContactId(rawContactId);
4332        insertPhoto(rawContactId, R.drawable.earth_normal);
4333        Uri photoUri = Contacts.CONTENT_URI.buildUpon()
4334                .appendPath(String.valueOf(contactId))
4335                .appendPath(Contacts.Photo.DISPLAY_PHOTO).build();
4336        assertInputStreamContent(
4337                loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
4338                mResolver.openInputStream(photoUri));
4339    }
4340
4341    public void testOpenDisplayPhotoForContactLookupKey() throws IOException {
4342        long rawContactId = createRawContactWithName();
4343        long contactId = queryContactId(rawContactId);
4344        String lookupKey = queryLookupKey(contactId);
4345        insertPhoto(rawContactId, R.drawable.earth_normal);
4346        Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon()
4347                .appendPath(lookupKey)
4348                .appendPath(Contacts.Photo.DISPLAY_PHOTO).build();
4349        assertInputStreamContent(
4350                loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
4351                mResolver.openInputStream(photoUri));
4352    }
4353
4354    public void testOpenDisplayPhotoForContactLookupKeyAndId() throws IOException {
4355        long rawContactId = createRawContactWithName();
4356        long contactId = queryContactId(rawContactId);
4357        String lookupKey = queryLookupKey(contactId);
4358        insertPhoto(rawContactId, R.drawable.earth_normal);
4359        Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon()
4360                .appendPath(lookupKey)
4361                .appendPath(String.valueOf(contactId))
4362                .appendPath(Contacts.Photo.DISPLAY_PHOTO).build();
4363        assertInputStreamContent(
4364                loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
4365                mResolver.openInputStream(photoUri));
4366    }
4367
4368    public void testOpenDisplayPhotoForRawContactId() throws IOException {
4369        long rawContactId = createRawContactWithName();
4370        insertPhoto(rawContactId, R.drawable.earth_normal);
4371        Uri photoUri = RawContacts.CONTENT_URI.buildUpon()
4372                .appendPath(String.valueOf(rawContactId))
4373                .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build();
4374        assertInputStreamContent(
4375                loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
4376                mResolver.openInputStream(photoUri));
4377    }
4378
4379    public void testOpenDisplayPhotoByPhotoUri() throws IOException {
4380        long rawContactId = createRawContactWithName();
4381        long contactId = queryContactId(rawContactId);
4382        insertPhoto(rawContactId, R.drawable.earth_normal);
4383
4384        // Get the photo URI out and check the content.
4385        String photoUri = getStoredValue(
4386                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4387                Contacts.PHOTO_URI);
4388        assertInputStreamContent(
4389                loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO),
4390                mResolver.openInputStream(Uri.parse(photoUri)));
4391    }
4392
4393    public void testPhotoUriForDisplayPhoto() {
4394        long rawContactId = createRawContactWithName();
4395        long contactId = queryContactId(rawContactId);
4396
4397        // Photo being inserted is larger than a thumbnail, so it will be stored as a file.
4398        long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal));
4399        String photoFileId = getStoredValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
4400                Photo.PHOTO_FILE_ID);
4401        String photoUri = getStoredValue(
4402                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4403                Contacts.PHOTO_URI);
4404
4405        // Check that the photo URI differs from the thumbnail.
4406        String thumbnailUri = getStoredValue(
4407                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4408                Contacts.PHOTO_THUMBNAIL_URI);
4409        assertFalse(photoUri.equals(thumbnailUri));
4410
4411        // URI should be of the form display_photo/ID
4412        assertEquals(Uri.withAppendedPath(DisplayPhoto.CONTENT_URI, photoFileId).toString(),
4413                photoUri);
4414    }
4415
4416    public void testPhotoUriForThumbnailPhoto() throws IOException {
4417        long rawContactId = createRawContactWithName();
4418        long contactId = queryContactId(rawContactId);
4419
4420        // Photo being inserted is a thumbnail, so it will only be stored in a BLOB.  The photo URI
4421        // will fall back to the thumbnail URI.
4422        insertPhoto(rawContactId, R.drawable.earth_small);
4423        String photoUri = getStoredValue(
4424                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4425                Contacts.PHOTO_URI);
4426
4427        // Check that the photo URI is equal to the thumbnail URI.
4428        String thumbnailUri = getStoredValue(
4429                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4430                Contacts.PHOTO_THUMBNAIL_URI);
4431        assertEquals(photoUri, thumbnailUri);
4432
4433        // URI should be of the form contacts/ID/photo
4434        assertEquals(Uri.withAppendedPath(
4435                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4436                Contacts.Photo.CONTENT_DIRECTORY).toString(),
4437                photoUri);
4438
4439        // Loading the photo URI content should get the thumbnail.
4440        assertInputStreamContent(
4441                loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL),
4442                mResolver.openInputStream(Uri.parse(photoUri)));
4443    }
4444
4445    public void testWriteNewPhotoToAssetFile() throws IOException {
4446        long rawContactId = createRawContactWithName();
4447        long contactId = queryContactId(rawContactId);
4448
4449        // Load in a huge photo.
4450        byte[] originalPhoto = loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL);
4451
4452        // Write it out.
4453        Uri writeablePhotoUri = RawContacts.CONTENT_URI.buildUpon()
4454                .appendPath(String.valueOf(rawContactId))
4455                .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build();
4456        OutputStream os = mResolver.openOutputStream(writeablePhotoUri, "rw");
4457        try {
4458            os.write(originalPhoto);
4459        } finally {
4460            os.close();
4461        }
4462
4463        // Check that the display photo and thumbnail have been set.
4464        String photoUri = getStoredValue(
4465                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI);
4466        assertFalse(TextUtils.isEmpty(photoUri));
4467        String thumbnailUri = getStoredValue(
4468                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4469                Contacts.PHOTO_THUMBNAIL_URI);
4470        assertFalse(TextUtils.isEmpty(thumbnailUri));
4471        assertFalse(photoUri.equals(thumbnailUri));
4472
4473        // Check the content of the display photo and thumbnail.
4474        assertInputStreamContent(
4475                loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO),
4476                mResolver.openInputStream(Uri.parse(photoUri)));
4477        assertInputStreamContent(
4478                loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL),
4479                mResolver.openInputStream(Uri.parse(thumbnailUri)));
4480    }
4481
4482    public void testWriteUpdatedPhotoToAssetFile() throws IOException {
4483        long rawContactId = createRawContactWithName();
4484        long contactId = queryContactId(rawContactId);
4485
4486        // Insert a large photo first.
4487        insertPhoto(rawContactId, R.drawable.earth_large);
4488        String largeEarthPhotoUri = getStoredValue(
4489                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI);
4490
4491        // Load in a huge photo.
4492        byte[] originalPhoto = loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL);
4493
4494        // Write it out.
4495        Uri writeablePhotoUri = RawContacts.CONTENT_URI.buildUpon()
4496                .appendPath(String.valueOf(rawContactId))
4497                .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build();
4498        OutputStream os = mResolver.openOutputStream(writeablePhotoUri, "rw");
4499        try {
4500            os.write(originalPhoto);
4501        } finally {
4502            os.close();
4503        }
4504
4505        // Check that the display photo URI has been modified.
4506        String hugeEarthPhotoUri = getStoredValue(
4507                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI);
4508        assertFalse(hugeEarthPhotoUri.equals(largeEarthPhotoUri));
4509
4510        // Check the content of the display photo and thumbnail.
4511        String hugeEarthThumbnailUri = getStoredValue(
4512                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
4513                Contacts.PHOTO_THUMBNAIL_URI);
4514        assertInputStreamContent(
4515                loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO),
4516                mResolver.openInputStream(Uri.parse(hugeEarthPhotoUri)));
4517        assertInputStreamContent(
4518                loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL),
4519                mResolver.openInputStream(Uri.parse(hugeEarthThumbnailUri)));
4520
4521    }
4522
4523    public void testPhotoDimensionLimits() {
4524        ContentValues values = new ContentValues();
4525        values.put(DisplayPhoto.DISPLAY_MAX_DIM, 256);
4526        values.put(DisplayPhoto.THUMBNAIL_MAX_DIM, 96);
4527        assertStoredValues(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, values);
4528    }
4529
4530    public void testPhotoStoreCleanup() throws IOException {
4531        SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider;
4532
4533        // Trigger an initial cleanup so another one won't happen while we're running this test.
4534        provider.cleanupPhotoStore();
4535
4536        // Insert a couple of contacts with photos.
4537        long rawContactId1 = createRawContactWithName();
4538        long contactId1 = queryContactId(rawContactId1);
4539        long dataId1 = ContentUris.parseId(insertPhoto(rawContactId1, R.drawable.earth_normal));
4540        long photoFileId1 =
4541                getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId1),
4542                        Photo.PHOTO_FILE_ID);
4543
4544        long rawContactId2 = createRawContactWithName();
4545        long contactId2 = queryContactId(rawContactId2);
4546        long dataId2 = ContentUris.parseId(insertPhoto(rawContactId2, R.drawable.earth_normal));
4547        long photoFileId2 =
4548                getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId2),
4549                        Photo.PHOTO_FILE_ID);
4550
4551        // Update the second raw contact with a different photo.
4552        ContentValues values = new ContentValues();
4553        values.put(Data.RAW_CONTACT_ID, rawContactId2);
4554        values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
4555        values.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL));
4556        assertEquals(1, mResolver.update(Data.CONTENT_URI, values, Data._ID + "=?",
4557                new String[]{String.valueOf(dataId2)}));
4558        long replacementPhotoFileId =
4559                getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId2),
4560                        Photo.PHOTO_FILE_ID);
4561
4562        // Insert a third raw contact that has a bogus photo file ID.
4563        long bogusFileId = 1234567;
4564        long rawContactId3 = createRawContactWithName();
4565        long contactId3 = queryContactId(rawContactId3);
4566        values.clear();
4567        values.put(Data.RAW_CONTACT_ID, rawContactId3);
4568        values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
4569        values.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_normal,
4570                PhotoSize.THUMBNAIL));
4571        values.put(Photo.PHOTO_FILE_ID, bogusFileId);
4572        values.put(DataRowHandlerForPhoto.SKIP_PROCESSING_KEY, true);
4573        mResolver.insert(Data.CONTENT_URI, values);
4574
4575        // Also insert a bogus photo that nobody is using.
4576        PhotoStore photoStore = provider.getPhotoStore();
4577        long bogusPhotoId = photoStore.insert(new PhotoProcessor(loadPhotoFromResource(
4578                R.drawable.earth_huge, PhotoSize.ORIGINAL), 256, 96));
4579
4580        // Manually trigger another cleanup in the provider.
4581        provider.cleanupPhotoStore();
4582
4583        // The following things should have happened.
4584
4585        // 1. Raw contact 1 and its photo remain unaffected.
4586        assertEquals(photoFileId1, (long) getStoredLongValue(
4587                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1),
4588                Contacts.PHOTO_FILE_ID));
4589
4590        // 2. Raw contact 2 retains its new photo.  The old one is deleted from the photo store.
4591        assertEquals(replacementPhotoFileId, (long) getStoredLongValue(
4592                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2),
4593                Contacts.PHOTO_FILE_ID));
4594        assertNull(photoStore.get(photoFileId2));
4595
4596        // 3. Raw contact 3 should have its photo file reference cleared.
4597        assertNull(getStoredValue(
4598                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId3),
4599                Contacts.PHOTO_FILE_ID));
4600
4601        // 4. The bogus photo that nobody was using should be cleared from the photo store.
4602        assertNull(photoStore.get(bogusPhotoId));
4603    }
4604
4605    public void testOverwritePhotoWithThumbnail() throws IOException {
4606        long rawContactId = createRawContactWithName();
4607        long contactId = queryContactId(rawContactId);
4608        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4609
4610        // Write a regular-size photo.
4611        long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal));
4612        Long photoFileId = getStoredLongValue(contactUri, Contacts.PHOTO_FILE_ID);
4613        assertTrue(photoFileId != null && photoFileId > 0);
4614
4615        // Now overwrite the photo with a thumbnail-sized photo.
4616        ContentValues update = new ContentValues();
4617        update.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_small, PhotoSize.ORIGINAL));
4618        mResolver.update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), update, null, null);
4619
4620        // Photo file ID should have been nulled out, and the photo URI should be the same as the
4621        // thumbnail URI.
4622        assertNull(getStoredValue(contactUri, Contacts.PHOTO_FILE_ID));
4623        String photoUri = getStoredValue(contactUri, Contacts.PHOTO_URI);
4624        String thumbnailUri = getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI);
4625        assertEquals(photoUri, thumbnailUri);
4626
4627        // Retrieving the photo URI should get the thumbnail content.
4628        assertInputStreamContent(loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL),
4629                mResolver.openInputStream(Uri.parse(photoUri)));
4630    }
4631
4632    public void testUpdateRawContactSetStarred() {
4633        long rawContactId1 = createRawContactWithName();
4634        Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
4635        long rawContactId2 = createRawContactWithName();
4636        Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2);
4637        setAggregationException(
4638                AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
4639
4640        long contactId = queryContactId(rawContactId1);
4641        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
4642        assertStoredValue(contactUri, Contacts.STARRED, "0");
4643
4644        ContentValues values = new ContentValues();
4645        values.put(RawContacts.STARRED, "1");
4646
4647        mResolver.update(rawContactUri1, values, null, null);
4648
4649        assertStoredValue(rawContactUri1, RawContacts.STARRED, "1");
4650        assertStoredValue(rawContactUri2, RawContacts.STARRED, "0");
4651        assertStoredValue(contactUri, Contacts.STARRED, "1");
4652
4653        values.put(RawContacts.STARRED, "0");
4654        mResolver.update(rawContactUri1, values, null, null);
4655
4656        assertStoredValue(rawContactUri1, RawContacts.STARRED, "0");
4657        assertStoredValue(rawContactUri2, RawContacts.STARRED, "0");
4658        assertStoredValue(contactUri, Contacts.STARRED, "0");
4659
4660        values.put(Contacts.STARRED, "1");
4661        mResolver.update(contactUri, values, null, null);
4662
4663        assertStoredValue(rawContactUri1, RawContacts.STARRED, "1");
4664        assertStoredValue(rawContactUri2, RawContacts.STARRED, "1");
4665        assertStoredValue(contactUri, Contacts.STARRED, "1");
4666    }
4667
4668    public void testSetAndClearSuperPrimaryEmail() {
4669        long rawContactId1 = createRawContact(new Account("a", "a"));
4670        Uri mailUri11 = insertEmail(rawContactId1, "test1@domain1.com");
4671        Uri mailUri12 = insertEmail(rawContactId1, "test2@domain1.com");
4672
4673        long rawContactId2 = createRawContact(new Account("b", "b"));
4674        Uri mailUri21 = insertEmail(rawContactId2, "test1@domain2.com");
4675        Uri mailUri22 = insertEmail(rawContactId2, "test2@domain2.com");
4676
4677        assertStoredValue(mailUri11, Data.IS_PRIMARY, 0);
4678        assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 0);
4679        assertStoredValue(mailUri12, Data.IS_PRIMARY, 0);
4680        assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0);
4681        assertStoredValue(mailUri21, Data.IS_PRIMARY, 0);
4682        assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0);
4683        assertStoredValue(mailUri22, Data.IS_PRIMARY, 0);
4684        assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 0);
4685
4686        // Set super primary on the first pair, primary on the second
4687        {
4688            ContentValues values = new ContentValues();
4689            values.put(Data.IS_SUPER_PRIMARY, 1);
4690            mResolver.update(mailUri11, values, null, null);
4691        }
4692        {
4693            ContentValues values = new ContentValues();
4694            values.put(Data.IS_SUPER_PRIMARY, 1);
4695            mResolver.update(mailUri22, values, null, null);
4696        }
4697
4698        assertStoredValue(mailUri11, Data.IS_PRIMARY, 1);
4699        assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 1);
4700        assertStoredValue(mailUri12, Data.IS_PRIMARY, 0);
4701        assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0);
4702        assertStoredValue(mailUri21, Data.IS_PRIMARY, 0);
4703        assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0);
4704        assertStoredValue(mailUri22, Data.IS_PRIMARY, 1);
4705        assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1);
4706
4707        // Clear primary on the first pair, make sure second is not affected and super_primary is
4708        // also cleared
4709        {
4710            ContentValues values = new ContentValues();
4711            values.put(Data.IS_PRIMARY, 0);
4712            mResolver.update(mailUri11, values, null, null);
4713        }
4714
4715        assertStoredValue(mailUri11, Data.IS_PRIMARY, 0);
4716        assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 0);
4717        assertStoredValue(mailUri12, Data.IS_PRIMARY, 0);
4718        assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0);
4719        assertStoredValue(mailUri21, Data.IS_PRIMARY, 0);
4720        assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0);
4721        assertStoredValue(mailUri22, Data.IS_PRIMARY, 1);
4722        assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1);
4723
4724        // Ensure that we can only clear super_primary, if we specify the correct data row
4725        {
4726            ContentValues values = new ContentValues();
4727            values.put(Data.IS_SUPER_PRIMARY, 0);
4728            mResolver.update(mailUri21, values, null, null);
4729        }
4730
4731        assertStoredValue(mailUri21, Data.IS_PRIMARY, 0);
4732        assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0);
4733        assertStoredValue(mailUri22, Data.IS_PRIMARY, 1);
4734        assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1);
4735
4736        // Ensure that we can only clear primary, if we specify the correct data row
4737        {
4738            ContentValues values = new ContentValues();
4739            values.put(Data.IS_PRIMARY, 0);
4740            mResolver.update(mailUri21, values, null, null);
4741        }
4742
4743        assertStoredValue(mailUri21, Data.IS_PRIMARY, 0);
4744        assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0);
4745        assertStoredValue(mailUri22, Data.IS_PRIMARY, 1);
4746        assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1);
4747
4748        // Now clear super-primary for real
4749        {
4750            ContentValues values = new ContentValues();
4751            values.put(Data.IS_SUPER_PRIMARY, 0);
4752            mResolver.update(mailUri22, values, null, null);
4753        }
4754
4755        assertStoredValue(mailUri11, Data.IS_PRIMARY, 0);
4756        assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 0);
4757        assertStoredValue(mailUri12, Data.IS_PRIMARY, 0);
4758        assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0);
4759        assertStoredValue(mailUri21, Data.IS_PRIMARY, 0);
4760        assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0);
4761        assertStoredValue(mailUri22, Data.IS_PRIMARY, 1);
4762        assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 0);
4763    }
4764
4765    /**
4766     * Common function for the testNewPrimaryIn* functions. Its four configurations
4767     * are each called from its own test
4768     */
4769    public void testChangingPrimary(boolean inUpdate, boolean withSuperPrimary) {
4770        long rawContactId = createRawContact(new Account("a", "a"));
4771        Uri mailUri1 = insertEmail(rawContactId, "test1@domain1.com", true);
4772
4773        if (withSuperPrimary) {
4774            final ContentValues values = new ContentValues();
4775            values.put(Data.IS_SUPER_PRIMARY, 1);
4776            mResolver.update(mailUri1, values, null, null);
4777        }
4778
4779        assertStoredValue(mailUri1, Data.IS_PRIMARY, 1);
4780        assertStoredValue(mailUri1, Data.IS_SUPER_PRIMARY, withSuperPrimary ? 1 : 0);
4781
4782        // Insert another item
4783        final Uri mailUri2;
4784        if (inUpdate) {
4785            mailUri2 = insertEmail(rawContactId, "test2@domain1.com");
4786
4787            assertStoredValue(mailUri1, Data.IS_PRIMARY, 1);
4788            assertStoredValue(mailUri1, Data.IS_SUPER_PRIMARY, withSuperPrimary ? 1 : 0);
4789            assertStoredValue(mailUri2, Data.IS_PRIMARY, 0);
4790            assertStoredValue(mailUri2, Data.IS_SUPER_PRIMARY, 0);
4791
4792            final ContentValues values = new ContentValues();
4793            values.put(Data.IS_PRIMARY, 1);
4794            mResolver.update(mailUri2, values, null, null);
4795        } else {
4796            // directly add as default
4797            mailUri2 = insertEmail(rawContactId, "test2@domain1.com", true);
4798        }
4799
4800        // Ensure that primary has been unset on the first
4801        // If withSuperPrimary is set, also ensure that is has been moved to the new item
4802        assertStoredValue(mailUri1, Data.IS_PRIMARY, 0);
4803        assertStoredValue(mailUri1, Data.IS_SUPER_PRIMARY, 0);
4804        assertStoredValue(mailUri2, Data.IS_PRIMARY, 1);
4805        assertStoredValue(mailUri2, Data.IS_SUPER_PRIMARY, withSuperPrimary ? 1 : 0);
4806    }
4807
4808    public void testNewPrimaryInInsert() {
4809        testChangingPrimary(false, false);
4810    }
4811
4812    public void testNewPrimaryInInsertWithSuperPrimary() {
4813        testChangingPrimary(false, true);
4814    }
4815
4816    public void testNewPrimaryInUpdate() {
4817        testChangingPrimary(true, false);
4818    }
4819
4820    public void testNewPrimaryInUpdateWithSuperPrimary() {
4821        testChangingPrimary(true, true);
4822    }
4823
4824    public void testLiveFolders() {
4825        long rawContactId1 = createRawContactWithName("James", "Sullivan");
4826        insertPhoneNumber(rawContactId1, "5234567890");
4827        long contactId1 = queryContactId(rawContactId1);
4828
4829        long rawContactId2 = createRawContactWithName("Mike", "Wazowski");
4830        long contactId2 = queryContactId(rawContactId2);
4831        storeValue(Contacts.CONTENT_URI, contactId2, Contacts.STARRED, "1");
4832
4833        long rawContactId3 = createRawContactWithName("Randall", "Boggs");
4834        long contactId3 = queryContactId(rawContactId3);
4835        long groupId = createGroup(NO_ACCOUNT, "src1", "VIP");
4836        insertGroupMembership(rawContactId3, groupId);
4837
4838        assertLiveFolderContents(
4839                Uri.withAppendedPath(ContactsContract.AUTHORITY_URI,
4840                        "live_folders/contacts"),
4841                contactId1, "James Sullivan",
4842                contactId2, "Mike Wazowski",
4843                contactId3, "Randall Boggs");
4844
4845        assertLiveFolderContents(
4846                Uri.withAppendedPath(ContactsContract.AUTHORITY_URI,
4847                        "live_folders/contacts_with_phones"),
4848                contactId1, "James Sullivan");
4849
4850        assertLiveFolderContents(
4851                Uri.withAppendedPath(ContactsContract.AUTHORITY_URI,
4852                        "live_folders/favorites"),
4853                contactId2, "Mike Wazowski");
4854
4855        assertLiveFolderContents(
4856                Uri.withAppendedPath(Uri.withAppendedPath(ContactsContract.AUTHORITY_URI,
4857                        "live_folders/contacts"), Uri.encode("VIP")),
4858                contactId3, "Randall Boggs");
4859    }
4860
4861    private void assertLiveFolderContents(Uri uri, Object... expected) {
4862        Cursor c = mResolver.query(uri, new String[]{LiveFolders._ID, LiveFolders.NAME},
4863                null, null, LiveFolders._ID);
4864        assertEquals(expected.length/2, c.getCount());
4865        for (int i = 0; i < expected.length/2; i++) {
4866            assertTrue(c.moveToNext());
4867            assertEquals(((Long)expected[i * 2]).longValue(), c.getLong(0));
4868            assertEquals(expected[i * 2 + 1], c.getString(1));
4869        }
4870        c.close();
4871    }
4872
4873    public void testContactCounts() {
4874        Uri uri = Contacts.CONTENT_URI.buildUpon()
4875                .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
4876
4877        createRawContact();
4878        createRawContactWithName("James", "Sullivan");
4879        createRawContactWithName("The Abominable", "Snowman");
4880        createRawContactWithName("Mike", "Wazowski");
4881        createRawContactWithName("randall", "boggs");
4882        createRawContactWithName("Boo", null);
4883        createRawContactWithName("Mary", null);
4884        createRawContactWithName("Roz", null);
4885
4886        Cursor cursor = mResolver.query(uri,
4887                new String[]{Contacts.DISPLAY_NAME},
4888                null, null, Contacts.SORT_KEY_PRIMARY + " COLLATE LOCALIZED");
4889
4890        assertFirstLetterValues(cursor, null, "B", "J", "M", "R", "T");
4891        assertFirstLetterCounts(cursor,    1,   1,   1,   2,   2,   1);
4892        cursor.close();
4893
4894        cursor = mResolver.query(uri,
4895                new String[]{Contacts.DISPLAY_NAME},
4896                null, null, Contacts.SORT_KEY_ALTERNATIVE + " COLLATE LOCALIZED DESC");
4897
4898        assertFirstLetterValues(cursor, "W", "S", "R", "M", "B", null);
4899        assertFirstLetterCounts(cursor,   1,   2,   1,   1,   2,    1);
4900        cursor.close();
4901    }
4902
4903    private void assertFirstLetterValues(Cursor cursor, String... expected) {
4904        String[] actual = cursor.getExtras()
4905                .getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
4906        MoreAsserts.assertEquals(expected, actual);
4907    }
4908
4909    private void assertFirstLetterCounts(Cursor cursor, int... expected) {
4910        int[] actual = cursor.getExtras()
4911                .getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
4912        MoreAsserts.assertEquals(expected, actual);
4913    }
4914
4915    public void testReadBooleanQueryParameter() {
4916        assertBooleanUriParameter("foo:bar", "bool", true, true);
4917        assertBooleanUriParameter("foo:bar", "bool", false, false);
4918        assertBooleanUriParameter("foo:bar?bool=0", "bool", true, false);
4919        assertBooleanUriParameter("foo:bar?bool=1", "bool", false, true);
4920        assertBooleanUriParameter("foo:bar?bool=false", "bool", true, false);
4921        assertBooleanUriParameter("foo:bar?bool=true", "bool", false, true);
4922        assertBooleanUriParameter("foo:bar?bool=FaLsE", "bool", true, false);
4923        assertBooleanUriParameter("foo:bar?bool=false&some=some", "bool", true, false);
4924        assertBooleanUriParameter("foo:bar?bool=1&some=some", "bool", false, true);
4925        assertBooleanUriParameter("foo:bar?some=bool", "bool", true, true);
4926        assertBooleanUriParameter("foo:bar?bool", "bool", true, true);
4927    }
4928
4929    private void assertBooleanUriParameter(String uriString, String parameter,
4930            boolean defaultValue, boolean expectedValue) {
4931        assertEquals(expectedValue, ContactsProvider2.readBooleanQueryParameter(
4932                Uri.parse(uriString), parameter, defaultValue));
4933    }
4934
4935    public void testGetQueryParameter() {
4936        assertQueryParameter("foo:bar", "param", null);
4937        assertQueryParameter("foo:bar?param", "param", null);
4938        assertQueryParameter("foo:bar?param=", "param", "");
4939        assertQueryParameter("foo:bar?param=val", "param", "val");
4940        assertQueryParameter("foo:bar?param=val&some=some", "param", "val");
4941        assertQueryParameter("foo:bar?some=some&param=val", "param", "val");
4942        assertQueryParameter("foo:bar?some=some&param=val&else=else", "param", "val");
4943        assertQueryParameter("foo:bar?param=john%40doe.com", "param", "john@doe.com");
4944        assertQueryParameter("foo:bar?some_param=val", "param", null);
4945        assertQueryParameter("foo:bar?some_param=val1&param=val2", "param", "val2");
4946        assertQueryParameter("foo:bar?some_param=val1&param=", "param", "");
4947        assertQueryParameter("foo:bar?some_param=val1&param", "param", null);
4948        assertQueryParameter("foo:bar?some_param=val1&another_param=val2&param=val3",
4949                "param", "val3");
4950        assertQueryParameter("foo:bar?some_param=val1&param=val2&some_param=val3",
4951                "param", "val2");
4952        assertQueryParameter("foo:bar?param=val1&some_param=val2", "param", "val1");
4953        assertQueryParameter("foo:bar?p=val1&pp=val2", "p", "val1");
4954        assertQueryParameter("foo:bar?pp=val1&p=val2", "p", "val2");
4955        assertQueryParameter("foo:bar?ppp=val1&pp=val2&p=val3", "p", "val3");
4956        assertQueryParameter("foo:bar?ppp=val&", "p", null);
4957    }
4958
4959    public void testMissingAccountTypeParameter() {
4960        // Try querying for RawContacts only using ACCOUNT_NAME
4961        final Uri queryUri = RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(
4962                RawContacts.ACCOUNT_NAME, "lolwut").build();
4963        try {
4964            final Cursor cursor = mResolver.query(queryUri, null, null, null, null);
4965            fail("Able to query with incomplete account query parameters");
4966        } catch (IllegalArgumentException e) {
4967            // Expected behavior.
4968        }
4969    }
4970
4971    public void testInsertInconsistentAccountType() {
4972        // Try inserting RawContact with inconsistent Accounts
4973        final Account red = new Account("red", "red");
4974        final Account blue = new Account("blue", "blue");
4975
4976        final ContentValues values = new ContentValues();
4977        values.put(RawContacts.ACCOUNT_NAME, red.name);
4978        values.put(RawContacts.ACCOUNT_TYPE, red.type);
4979
4980        final Uri insertUri = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, blue);
4981        try {
4982            mResolver.insert(insertUri, values);
4983            fail("Able to insert RawContact with inconsistent account details");
4984        } catch (IllegalArgumentException e) {
4985            // Expected behavior.
4986        }
4987    }
4988
4989    public void testProviderStatusNoContactsNoAccounts() throws Exception {
4990        assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS);
4991    }
4992
4993    public void testProviderStatusOnlyLocalContacts() throws Exception {
4994        long rawContactId = createRawContact();
4995        assertProviderStatus(ProviderStatus.STATUS_NORMAL);
4996        mResolver.delete(
4997                ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), null, null);
4998        assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS);
4999    }
5000
5001    public void testProviderStatusWithAccounts() throws Exception {
5002        assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS);
5003        mActor.setAccounts(new Account[]{ACCOUNT_1});
5004        ((ContactsProvider2)getProvider()).onAccountsUpdated(new Account[]{ACCOUNT_1});
5005        assertProviderStatus(ProviderStatus.STATUS_NORMAL);
5006        mActor.setAccounts(new Account[0]);
5007        ((ContactsProvider2)getProvider()).onAccountsUpdated(new Account[0]);
5008        assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS);
5009    }
5010
5011    private void assertProviderStatus(int expectedProviderStatus) {
5012        Cursor cursor = mResolver.query(ProviderStatus.CONTENT_URI,
5013                new String[]{ProviderStatus.DATA1, ProviderStatus.STATUS}, null, null, null);
5014        assertTrue(cursor.moveToFirst());
5015        assertEquals(0, cursor.getLong(0));
5016        assertEquals(expectedProviderStatus, cursor.getInt(1));
5017        cursor.close();
5018    }
5019
5020    public void testProperties() throws Exception {
5021        ContactsProvider2 provider = (ContactsProvider2)getProvider();
5022        ContactsDatabaseHelper helper = (ContactsDatabaseHelper)provider.getDatabaseHelper();
5023        assertNull(helper.getProperty("non-existent", null));
5024        assertEquals("default", helper.getProperty("non-existent", "default"));
5025
5026        helper.setProperty("existent1", "string1");
5027        helper.setProperty("existent2", "string2");
5028        assertEquals("string1", helper.getProperty("existent1", "default"));
5029        assertEquals("string2", helper.getProperty("existent2", "default"));
5030        helper.setProperty("existent1", null);
5031        assertEquals("default", helper.getProperty("existent1", "default"));
5032    }
5033
5034    private class VCardTestUriCreator {
5035        private String mLookup1;
5036        private String mLookup2;
5037
5038        public VCardTestUriCreator(String lookup1, String lookup2) {
5039            super();
5040            mLookup1 = lookup1;
5041            mLookup2 = lookup2;
5042        }
5043
5044        public Uri getUri1() {
5045            return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup1);
5046        }
5047
5048        public Uri getUri2() {
5049            return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup2);
5050        }
5051
5052        public Uri getCombinedUri() {
5053            return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI,
5054                    Uri.encode(mLookup1 + ":" + mLookup2));
5055        }
5056    }
5057
5058    private VCardTestUriCreator createVCardTestContacts() {
5059        final long rawContactId1 = createRawContact(mAccount, RawContacts.SOURCE_ID, "4:12");
5060        insertStructuredName(rawContactId1, "John", "Doe");
5061
5062        final long rawContactId2 = createRawContact(mAccount, RawContacts.SOURCE_ID, "3:4%121");
5063        insertStructuredName(rawContactId2, "Jane", "Doh");
5064
5065        final long contactId1 = queryContactId(rawContactId1);
5066        final long contactId2 = queryContactId(rawContactId2);
5067        final Uri contact1Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1);
5068        final Uri contact2Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2);
5069        final String lookup1 =
5070            Uri.encode(Contacts.getLookupUri(mResolver, contact1Uri).getPathSegments().get(2));
5071        final String lookup2 =
5072            Uri.encode(Contacts.getLookupUri(mResolver, contact2Uri).getPathSegments().get(2));
5073        return new VCardTestUriCreator(lookup1, lookup2);
5074    }
5075
5076    public void testQueryMultiVCard() {
5077        // No need to create any contacts here, because the query for multiple vcards
5078        // does not go into the database at all
5079        Uri uri = Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI, Uri.encode("123:456"));
5080        Cursor cursor = mResolver.query(uri, null, null, null, null);
5081        assertEquals(1, cursor.getCount());
5082        assertTrue(cursor.moveToFirst());
5083        assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE)));
5084        String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
5085
5086        // The resulting name contains date and time. Ensure that before and after are correct
5087        assertTrue(filename.startsWith("vcards_"));
5088        assertTrue(filename.endsWith(".vcf"));
5089        cursor.close();
5090    }
5091
5092    public void testQueryFileSingleVCard() {
5093        final VCardTestUriCreator contacts = createVCardTestContacts();
5094
5095        {
5096            Cursor cursor = mResolver.query(contacts.getUri1(), null, null, null, null);
5097            assertEquals(1, cursor.getCount());
5098            assertTrue(cursor.moveToFirst());
5099            assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE)));
5100            String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
5101            assertEquals("John Doe.vcf", filename);
5102            cursor.close();
5103        }
5104
5105        {
5106            Cursor cursor = mResolver.query(contacts.getUri2(), null, null, null, null);
5107            assertEquals(1, cursor.getCount());
5108            assertTrue(cursor.moveToFirst());
5109            assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE)));
5110            String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
5111            assertEquals("Jane Doh.vcf", filename);
5112            cursor.close();
5113        }
5114    }
5115
5116    public void testQueryFileProfileVCard() {
5117        createBasicProfileContact(new ContentValues());
5118        Cursor cursor = mResolver.query(Profile.CONTENT_VCARD_URI, null, null, null, null);
5119        assertEquals(1, cursor.getCount());
5120        assertTrue(cursor.moveToFirst());
5121        assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE)));
5122        String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
5123        assertEquals("Mia Prophyl.vcf", filename);
5124        cursor.close();
5125    }
5126
5127    public void testOpenAssetFileMultiVCard() throws IOException {
5128        final VCardTestUriCreator contacts = createVCardTestContacts();
5129
5130        final AssetFileDescriptor descriptor =
5131            mResolver.openAssetFileDescriptor(contacts.getCombinedUri(), "r");
5132        final FileInputStream inputStream = descriptor.createInputStream();
5133        String data = readToEnd(inputStream);
5134        inputStream.close();
5135        descriptor.close();
5136
5137        // Ensure that the resulting VCard has both contacts
5138        assertTrue(data.contains("N:Doe;John;;;"));
5139        assertTrue(data.contains("N:Doh;Jane;;;"));
5140    }
5141
5142    public void testOpenAssetFileSingleVCard() throws IOException {
5143        final VCardTestUriCreator contacts = createVCardTestContacts();
5144
5145        // Ensure that the right VCard is being created in each case
5146        {
5147            final AssetFileDescriptor descriptor =
5148                mResolver.openAssetFileDescriptor(contacts.getUri1(), "r");
5149            final FileInputStream inputStream = descriptor.createInputStream();
5150            final String data = readToEnd(inputStream);
5151            inputStream.close();
5152            descriptor.close();
5153
5154            assertTrue(data.contains("N:Doe;John;;;"));
5155            assertFalse(data.contains("N:Doh;Jane;;;"));
5156        }
5157
5158        {
5159            final AssetFileDescriptor descriptor =
5160                mResolver.openAssetFileDescriptor(contacts.getUri2(), "r");
5161            final FileInputStream inputStream = descriptor.createInputStream();
5162            final String data = readToEnd(inputStream);
5163            inputStream.close();
5164            descriptor.close();
5165
5166            assertFalse(data.contains("N:Doe;John;;;"));
5167            assertTrue(data.contains("N:Doh;Jane;;;"));
5168        }
5169    }
5170
5171    public void testAutoGroupMembership() {
5172        long g1 = createGroup(mAccount, "g1", "t1", 0, true /* autoAdd */, false /* favorite */);
5173        long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */);
5174        long g3 = createGroup(mAccountTwo, "g3", "t3", 0, true /* autoAdd */, false /* favorite */);
5175        long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, false/* favorite */);
5176        long r1 = createRawContact(mAccount);
5177        long r2 = createRawContact(mAccountTwo);
5178        long r3 = createRawContact(null);
5179
5180        Cursor c = queryGroupMemberships(mAccount);
5181        try {
5182            assertTrue(c.moveToNext());
5183            assertEquals(g1, c.getLong(0));
5184            assertEquals(r1, c.getLong(1));
5185            assertFalse(c.moveToNext());
5186        } finally {
5187            c.close();
5188        }
5189
5190        c = queryGroupMemberships(mAccountTwo);
5191        try {
5192            assertTrue(c.moveToNext());
5193            assertEquals(g3, c.getLong(0));
5194            assertEquals(r2, c.getLong(1));
5195            assertFalse(c.moveToNext());
5196        } finally {
5197            c.close();
5198        }
5199    }
5200
5201    public void testNoAutoAddMembershipAfterGroupCreation() {
5202        long r1 = createRawContact(mAccount);
5203        long r2 = createRawContact(mAccount);
5204        long r3 = createRawContact(mAccount);
5205        long r4 = createRawContact(mAccountTwo);
5206        long r5 = createRawContact(mAccountTwo);
5207        long r6 = createRawContact(null);
5208
5209        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5210        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5211
5212        long g1 = createGroup(mAccount, "g1", "t1", 0, true /* autoAdd */, false /* favorite */);
5213        long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */);
5214        long g3 = createGroup(mAccountTwo, "g3", "t3", 0, true /* autoAdd */, false/* favorite */);
5215
5216        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5217        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5218    }
5219
5220    // create some starred and non-starred contacts, some associated with account, some not
5221    // favorites group created
5222    // the starred contacts should be added to group
5223    // favorites group removed
5224    // no change to starred status
5225    public void testFavoritesMembershipAfterGroupCreation() {
5226        long r1 = createRawContact(mAccount, RawContacts.STARRED, "1");
5227        long r2 = createRawContact(mAccount);
5228        long r3 = createRawContact(mAccount, RawContacts.STARRED, "1");
5229        long r4 = createRawContact(mAccountTwo, RawContacts.STARRED, "1");
5230        long r5 = createRawContact(mAccountTwo);
5231        long r6 = createRawContact(null, RawContacts.STARRED, "1");
5232        long r7 = createRawContact(null);
5233
5234        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5235        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5236
5237        long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */);
5238        long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */);
5239        long g3 = createGroup(mAccountTwo, "g3", "t3", 0, false /* autoAdd */, false/* favorite */);
5240
5241        assertTrue(queryRawContactIsStarred(r1));
5242        assertFalse(queryRawContactIsStarred(r2));
5243        assertTrue(queryRawContactIsStarred(r3));
5244        assertTrue(queryRawContactIsStarred(r4));
5245        assertFalse(queryRawContactIsStarred(r5));
5246        assertTrue(queryRawContactIsStarred(r6));
5247        assertFalse(queryRawContactIsStarred(r7));
5248
5249        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5250        Cursor c = queryGroupMemberships(mAccount);
5251        try {
5252            assertTrue(c.moveToNext());
5253            assertEquals(g1, c.getLong(0));
5254            assertEquals(r1, c.getLong(1));
5255            assertTrue(c.moveToNext());
5256            assertEquals(g1, c.getLong(0));
5257            assertEquals(r3, c.getLong(1));
5258            assertFalse(c.moveToNext());
5259        } finally {
5260            c.close();
5261        }
5262
5263        updateItem(RawContacts.CONTENT_URI, r6,
5264                RawContacts.ACCOUNT_NAME, mAccount.name,
5265                RawContacts.ACCOUNT_TYPE, mAccount.type);
5266        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5267        c = queryGroupMemberships(mAccount);
5268        try {
5269            assertTrue(c.moveToNext());
5270            assertEquals(g1, c.getLong(0));
5271            assertEquals(r1, c.getLong(1));
5272            assertTrue(c.moveToNext());
5273            assertEquals(g1, c.getLong(0));
5274            assertEquals(r3, c.getLong(1));
5275            assertTrue(c.moveToNext());
5276            assertEquals(g1, c.getLong(0));
5277            assertEquals(r6, c.getLong(1));
5278            assertFalse(c.moveToNext());
5279        } finally {
5280            c.close();
5281        }
5282
5283        mResolver.delete(ContentUris.withAppendedId(Groups.CONTENT_URI, g1), null, null);
5284
5285        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5286        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5287
5288        assertTrue(queryRawContactIsStarred(r1));
5289        assertFalse(queryRawContactIsStarred(r2));
5290        assertTrue(queryRawContactIsStarred(r3));
5291        assertTrue(queryRawContactIsStarred(r4));
5292        assertFalse(queryRawContactIsStarred(r5));
5293        assertTrue(queryRawContactIsStarred(r6));
5294        assertFalse(queryRawContactIsStarred(r7));
5295    }
5296
5297    public void testFavoritesGroupMembershipChangeAfterStarChange() {
5298        long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */);
5299        long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false/* favorite */);
5300        long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, true /* favorite */);
5301        long g5 = createGroup(mAccountTwo, "g5", "t5", 0, false /* autoAdd */, false/* favorite */);
5302        long r1 = createRawContact(mAccount, RawContacts.STARRED, "1");
5303        long r2 = createRawContact(mAccount);
5304        long r3 = createRawContact(mAccountTwo);
5305
5306        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5307        Cursor c = queryGroupMemberships(mAccount);
5308        try {
5309            assertTrue(c.moveToNext());
5310            assertEquals(g1, c.getLong(0));
5311            assertEquals(r1, c.getLong(1));
5312            assertFalse(c.moveToNext());
5313        } finally {
5314            c.close();
5315        }
5316
5317        // remove the star from r1
5318        assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "0"));
5319
5320        // Since no raw contacts are starred, there should be no group memberships.
5321        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5322        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5323
5324        // mark r1 as starred
5325        assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "1"));
5326        // Now that r1 is starred it should have a membership in the one groups from mAccount
5327        // that is marked as a favorite.
5328        // There should be no memberships in mAccountTwo since it has no starred raw contacts.
5329        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5330        c = queryGroupMemberships(mAccount);
5331        try {
5332            assertTrue(c.moveToNext());
5333            assertEquals(g1, c.getLong(0));
5334            assertEquals(r1, c.getLong(1));
5335            assertFalse(c.moveToNext());
5336        } finally {
5337            c.close();
5338        }
5339
5340        // remove the star from r1
5341        assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "0"));
5342        // Since no raw contacts are starred, there should be no group memberships.
5343        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5344        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5345
5346        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(r1));
5347        assertNotNull(contactUri);
5348
5349        // mark r1 as starred via its contact lookup uri
5350        assertEquals(1, updateItem(contactUri, Contacts.STARRED, "1"));
5351        // Now that r1 is starred it should have a membership in the one groups from mAccount
5352        // that is marked as a favorite.
5353        // There should be no memberships in mAccountTwo since it has no starred raw contacts.
5354        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5355        c = queryGroupMemberships(mAccount);
5356        try {
5357            assertTrue(c.moveToNext());
5358            assertEquals(g1, c.getLong(0));
5359            assertEquals(r1, c.getLong(1));
5360            assertFalse(c.moveToNext());
5361        } finally {
5362            c.close();
5363        }
5364
5365        // remove the star from r1
5366        updateItem(contactUri, Contacts.STARRED, "0");
5367        // Since no raw contacts are starred, there should be no group memberships.
5368        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5369        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5370    }
5371
5372    public void testStarChangedAfterGroupMembershipChange() {
5373        long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */);
5374        long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false/* favorite */);
5375        long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, true /* favorite */);
5376        long g5 = createGroup(mAccountTwo, "g5", "t5", 0, false /* autoAdd */, false/* favorite */);
5377        long r1 = createRawContact(mAccount);
5378        long r2 = createRawContact(mAccount);
5379        long r3 = createRawContact(mAccountTwo);
5380
5381        assertFalse(queryRawContactIsStarred(r1));
5382        assertFalse(queryRawContactIsStarred(r2));
5383        assertFalse(queryRawContactIsStarred(r3));
5384
5385        Cursor c;
5386
5387        // add r1 to one favorites group
5388        // r1's star should automatically be set
5389        // r1 should automatically be added to the other favorites group
5390        Uri urir1g1 = insertGroupMembership(r1, g1);
5391        assertTrue(queryRawContactIsStarred(r1));
5392        assertFalse(queryRawContactIsStarred(r2));
5393        assertFalse(queryRawContactIsStarred(r3));
5394        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5395        c = queryGroupMemberships(mAccount);
5396        try {
5397            assertTrue(c.moveToNext());
5398            assertEquals(g1, c.getLong(0));
5399            assertEquals(r1, c.getLong(1));
5400            assertFalse(c.moveToNext());
5401        } finally {
5402            c.close();
5403        }
5404
5405        // remove r1 from one favorites group
5406        mResolver.delete(urir1g1, null, null);
5407        // r1's star should no longer be set
5408        assertFalse(queryRawContactIsStarred(r1));
5409        assertFalse(queryRawContactIsStarred(r2));
5410        assertFalse(queryRawContactIsStarred(r3));
5411        // there should be no membership rows
5412        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5413        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5414
5415        // add r3 to the one favorites group for that account
5416        // r3's star should automatically be set
5417        Uri urir3g4 = insertGroupMembership(r3, g4);
5418        assertFalse(queryRawContactIsStarred(r1));
5419        assertFalse(queryRawContactIsStarred(r2));
5420        assertTrue(queryRawContactIsStarred(r3));
5421        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5422        c = queryGroupMemberships(mAccountTwo);
5423        try {
5424            assertTrue(c.moveToNext());
5425            assertEquals(g4, c.getLong(0));
5426            assertEquals(r3, c.getLong(1));
5427            assertFalse(c.moveToNext());
5428        } finally {
5429            c.close();
5430        }
5431
5432        // remove r3 from the favorites group
5433        mResolver.delete(urir3g4, null, null);
5434        // r3's star should automatically be cleared
5435        assertFalse(queryRawContactIsStarred(r1));
5436        assertFalse(queryRawContactIsStarred(r2));
5437        assertFalse(queryRawContactIsStarred(r3));
5438        assertNoRowsAndClose(queryGroupMemberships(mAccount));
5439        assertNoRowsAndClose(queryGroupMemberships(mAccountTwo));
5440    }
5441
5442    public void testReadOnlyRawContact() {
5443        long rawContactId = createRawContact();
5444        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
5445        storeValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "first");
5446        storeValue(rawContactUri, RawContacts.RAW_CONTACT_IS_READ_ONLY, 1);
5447
5448        storeValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "second");
5449        assertStoredValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "first");
5450
5451        Uri syncAdapterUri = rawContactUri.buildUpon()
5452                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "1")
5453                .build();
5454        storeValue(syncAdapterUri, RawContacts.CUSTOM_RINGTONE, "third");
5455        assertStoredValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "third");
5456    }
5457
5458    public void testReadOnlyDataRow() {
5459        long rawContactId = createRawContact();
5460        Uri emailUri = insertEmail(rawContactId, "email");
5461        Uri phoneUri = insertPhoneNumber(rawContactId, "555-1111");
5462
5463        storeValue(emailUri, Data.IS_READ_ONLY, "1");
5464        storeValue(emailUri, Email.ADDRESS, "changed");
5465        storeValue(phoneUri, Phone.NUMBER, "555-2222");
5466        assertStoredValue(emailUri, Email.ADDRESS, "email");
5467        assertStoredValue(phoneUri, Phone.NUMBER, "555-2222");
5468
5469        Uri syncAdapterUri = emailUri.buildUpon()
5470                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "1")
5471                .build();
5472        storeValue(syncAdapterUri, Email.ADDRESS, "changed");
5473        assertStoredValue(emailUri, Email.ADDRESS, "changed");
5474    }
5475
5476    public void testContactWithReadOnlyRawContact() {
5477        long rawContactId1 = createRawContact();
5478        Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
5479        storeValue(rawContactUri1, RawContacts.CUSTOM_RINGTONE, "first");
5480
5481        long rawContactId2 = createRawContact();
5482        Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2);
5483        storeValue(rawContactUri2, RawContacts.CUSTOM_RINGTONE, "second");
5484        storeValue(rawContactUri2, RawContacts.RAW_CONTACT_IS_READ_ONLY, 1);
5485
5486        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
5487                rawContactId1, rawContactId2);
5488
5489        long contactId = queryContactId(rawContactId1);
5490
5491        Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
5492        storeValue(contactUri, Contacts.CUSTOM_RINGTONE, "rt");
5493        assertStoredValue(contactUri, Contacts.CUSTOM_RINGTONE, "rt");
5494        assertStoredValue(rawContactUri1, RawContacts.CUSTOM_RINGTONE, "rt");
5495        assertStoredValue(rawContactUri2, RawContacts.CUSTOM_RINGTONE, "second");
5496    }
5497
5498    public void testNameParsingQuery() {
5499        Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name")
5500                .appendQueryParameter(StructuredName.DISPLAY_NAME, "Mr. John Q. Doe Jr.").build();
5501        Cursor cursor = mResolver.query(uri, null, null, null, null);
5502        ContentValues values = new ContentValues();
5503        values.put(StructuredName.DISPLAY_NAME, "Mr. John Q. Doe Jr.");
5504        values.put(StructuredName.PREFIX, "Mr.");
5505        values.put(StructuredName.GIVEN_NAME, "John");
5506        values.put(StructuredName.MIDDLE_NAME, "Q.");
5507        values.put(StructuredName.FAMILY_NAME, "Doe");
5508        values.put(StructuredName.SUFFIX, "Jr.");
5509        values.put(StructuredName.FULL_NAME_STYLE, FullNameStyle.WESTERN);
5510        assertTrue(cursor.moveToFirst());
5511        assertCursorValues(cursor, values);
5512        cursor.close();
5513    }
5514
5515    public void testNameConcatenationQuery() {
5516        Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name")
5517                .appendQueryParameter(StructuredName.PREFIX, "Mr")
5518                .appendQueryParameter(StructuredName.GIVEN_NAME, "John")
5519                .appendQueryParameter(StructuredName.MIDDLE_NAME, "Q.")
5520                .appendQueryParameter(StructuredName.FAMILY_NAME, "Doe")
5521                .appendQueryParameter(StructuredName.SUFFIX, "Jr.")
5522                .build();
5523        Cursor cursor = mResolver.query(uri, null, null, null, null);
5524        ContentValues values = new ContentValues();
5525        values.put(StructuredName.DISPLAY_NAME, "Mr John Q. Doe, Jr.");
5526        values.put(StructuredName.PREFIX, "Mr");
5527        values.put(StructuredName.GIVEN_NAME, "John");
5528        values.put(StructuredName.MIDDLE_NAME, "Q.");
5529        values.put(StructuredName.FAMILY_NAME, "Doe");
5530        values.put(StructuredName.SUFFIX, "Jr.");
5531        values.put(StructuredName.FULL_NAME_STYLE, FullNameStyle.WESTERN);
5532        assertTrue(cursor.moveToFirst());
5533        assertCursorValues(cursor, values);
5534        cursor.close();
5535    }
5536
5537    private Cursor queryGroupMemberships(Account account) {
5538        Cursor c = mResolver.query(maybeAddAccountQueryParameters(Data.CONTENT_URI, account),
5539                new String[]{GroupMembership.GROUP_ROW_ID, GroupMembership.RAW_CONTACT_ID},
5540                Data.MIMETYPE + "=?", new String[]{GroupMembership.CONTENT_ITEM_TYPE},
5541                GroupMembership.GROUP_SOURCE_ID);
5542        return c;
5543    }
5544
5545    private String readToEnd(FileInputStream inputStream) {
5546        try {
5547            System.out.println("DECLARED INPUT STREAM LENGTH: " + inputStream.available());
5548            int ch;
5549            StringBuilder stringBuilder = new StringBuilder();
5550            int index = 0;
5551            while (true) {
5552                ch = inputStream.read();
5553                System.out.println("READ CHARACTER: " + index + " " + ch);
5554                if (ch == -1) {
5555                    break;
5556                }
5557                stringBuilder.append((char)ch);
5558                index++;
5559            }
5560            return stringBuilder.toString();
5561        } catch (IOException e) {
5562            return null;
5563        }
5564    }
5565
5566    private void assertQueryParameter(String uriString, String parameter, String expectedValue) {
5567        assertEquals(expectedValue, ContactsProvider2.getQueryParameter(
5568                Uri.parse(uriString), parameter));
5569    }
5570
5571    private long createContact(ContentValues values, String firstName, String givenName,
5572            String phoneNumber, String email, int presenceStatus, int timesContacted, int starred,
5573            long groupId, int chatMode) {
5574        return createContact(values, firstName, givenName, phoneNumber, email, presenceStatus,
5575                timesContacted, starred, groupId, chatMode, false);
5576    }
5577
5578    private long createContact(ContentValues values, String firstName, String givenName,
5579            String phoneNumber, String email, int presenceStatus, int timesContacted, int starred,
5580            long groupId, int chatMode, boolean isUserProfile) {
5581        return queryContactId(createRawContact(values, firstName, givenName, phoneNumber, email,
5582                presenceStatus, timesContacted, starred, groupId, chatMode, isUserProfile));
5583    }
5584
5585    private long createRawContact(ContentValues values, String firstName, String givenName,
5586            String phoneNumber, String email, int presenceStatus, int timesContacted, int starred,
5587            long groupId, int chatMode) {
5588        long rawContactId = createRawContact(values, phoneNumber, email, presenceStatus,
5589                timesContacted, starred, groupId, chatMode);
5590        insertStructuredName(rawContactId, firstName, givenName);
5591        return rawContactId;
5592    }
5593
5594    private long createRawContact(ContentValues values, String firstName, String givenName,
5595            String phoneNumber, String email, int presenceStatus, int timesContacted, int starred,
5596            long groupId, int chatMode, boolean isUserProfile) {
5597        long rawContactId = createRawContact(values, phoneNumber, email, presenceStatus,
5598                timesContacted, starred, groupId, chatMode, isUserProfile);
5599        insertStructuredName(rawContactId, firstName, givenName);
5600        return rawContactId;
5601    }
5602
5603    private long createRawContact(ContentValues values, String phoneNumber, String email,
5604            int presenceStatus, int timesContacted, int starred, long groupId, int chatMode) {
5605        return createRawContact(values, phoneNumber, email, presenceStatus, timesContacted, starred,
5606                groupId, chatMode, false);
5607    }
5608
5609    private long createRawContact(ContentValues values, String phoneNumber, String email,
5610            int presenceStatus, int timesContacted, int starred, long groupId, int chatMode,
5611            boolean isUserProfile) {
5612        values.put(RawContacts.STARRED, starred);
5613        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
5614        values.put(RawContacts.CUSTOM_RINGTONE, "beethoven5");
5615        values.put(RawContacts.TIMES_CONTACTED, timesContacted);
5616
5617        Uri insertionUri = isUserProfile
5618                ? Profile.CONTENT_RAW_CONTACTS_URI
5619                : RawContacts.CONTENT_URI;
5620        Uri rawContactUri = mResolver.insert(insertionUri, values);
5621        long rawContactId = ContentUris.parseId(rawContactUri);
5622        Uri photoUri = insertPhoto(rawContactId);
5623        long photoId = ContentUris.parseId(photoUri);
5624        values.put(Contacts.PHOTO_ID, photoId);
5625        insertPhoneNumber(rawContactId, phoneNumber);
5626        insertEmail(rawContactId, email);
5627
5628        insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, email, presenceStatus, "hacking",
5629                chatMode);
5630
5631        if (groupId != 0) {
5632            insertGroupMembership(rawContactId, groupId);
5633        }
5634
5635        return rawContactId;
5636    }
5637
5638    /**
5639     * Creates a raw contact with pre-set values under the user's profile.
5640     * @param profileValues Values to be used to create the entry (common values will be
5641     *     automatically populated in createRawContact()).
5642     * @return the raw contact ID that was created.
5643     */
5644    private long createBasicProfileContact(ContentValues profileValues) {
5645        long profileRawContactId = createRawContact(profileValues, "Mia", "Prophyl",
5646                "18005554411", "mia.prophyl@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
5647                StatusUpdates.CAPABILITY_HAS_CAMERA, true);
5648        profileValues.put(Contacts.DISPLAY_NAME, "Mia Prophyl");
5649        return profileRawContactId;
5650    }
5651
5652    /**
5653     * Creates a raw contact with pre-set values that is not under the user's profile.
5654     * @param nonProfileValues Values to be used to create the entry (common values will be
5655     *     automatically populated in createRawContact()).
5656     * @return the raw contact ID that was created.
5657     */
5658    private long createBasicNonProfileContact(ContentValues nonProfileValues) {
5659        long nonProfileRawContactId = createRawContact(nonProfileValues, "John", "Doe",
5660                "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0,
5661                StatusUpdates.CAPABILITY_HAS_CAMERA, false);
5662        nonProfileValues.put(Contacts.DISPLAY_NAME, "John Doe");
5663        return nonProfileRawContactId;
5664    }
5665
5666    private void putDataValues(ContentValues values, long rawContactId) {
5667        values.put(Data.RAW_CONTACT_ID, rawContactId);
5668        values.put(Data.MIMETYPE, "testmimetype");
5669        values.put(Data.RES_PACKAGE, "oldpackage");
5670        values.put(Data.IS_PRIMARY, 1);
5671        values.put(Data.IS_SUPER_PRIMARY, 1);
5672        values.put(Data.DATA1, "one");
5673        values.put(Data.DATA2, "two");
5674        values.put(Data.DATA3, "three");
5675        values.put(Data.DATA4, "four");
5676        values.put(Data.DATA5, "five");
5677        values.put(Data.DATA6, "six");
5678        values.put(Data.DATA7, "seven");
5679        values.put(Data.DATA8, "eight");
5680        values.put(Data.DATA9, "nine");
5681        values.put(Data.DATA10, "ten");
5682        values.put(Data.DATA11, "eleven");
5683        values.put(Data.DATA12, "twelve");
5684        values.put(Data.DATA13, "thirteen");
5685        values.put(Data.DATA14, "fourteen");
5686        values.put(Data.DATA15, "fifteen");
5687        values.put(Data.SYNC1, "sync1");
5688        values.put(Data.SYNC2, "sync2");
5689        values.put(Data.SYNC3, "sync3");
5690        values.put(Data.SYNC4, "sync4");
5691    }
5692
5693    /**
5694     * @param data1 email address or phone number
5695     * @param usageType One of {@link DataUsageFeedback#USAGE_TYPE}
5696     * @param values ContentValues for this feedback. Useful for incrementing
5697     * {Contacts#TIMES_CONTACTED} in the ContentValue. Can be null.
5698     */
5699    private void sendFeedback(String data1, String usageType, ContentValues values) {
5700        final long dataId = getStoredLongValue(Data.CONTENT_URI,
5701                Data.DATA1 + "=?", new String[] { data1 }, Data._ID);
5702        final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
5703                .appendPath(String.valueOf(dataId))
5704                .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
5705                .build();
5706        assertNotSame(0, mResolver.update(feedbackUri, new ContentValues(), null, null));
5707        if (values != null && values.containsKey(Contacts.TIMES_CONTACTED)) {
5708            values.put(Contacts.TIMES_CONTACTED, values.getAsInteger(Contacts.TIMES_CONTACTED) + 1);
5709        }
5710    }
5711}
5712
5713