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