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