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