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