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