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