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