/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.providers.contacts; import static com.android.providers.contacts.TestUtils.cv; import android.accounts.Account; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.Entity; import android.content.EntityIterator; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.provider.ContactsContract; import android.provider.ContactsContract.AggregationExceptions; import android.provider.ContactsContract.CommonDataKinds.Callable; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.ContactCounts; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.DataUsageFeedback; import android.provider.ContactsContract.Directory; import android.provider.ContactsContract.DisplayNameSources; import android.provider.ContactsContract.DisplayPhoto; import android.provider.ContactsContract.FullNameStyle; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneticNameStyle; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.ProviderStatus; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContactsEntity; import android.provider.ContactsContract.SearchSnippetColumns; import android.provider.ContactsContract.Settings; import android.provider.ContactsContract.StatusUpdates; import android.provider.ContactsContract.StreamItemPhotos; import android.provider.ContactsContract.StreamItems; import android.provider.OpenableColumns; import android.test.MoreAsserts; import android.test.suitebuilder.annotation.LargeTest; import android.text.TextUtils; import com.android.internal.util.ArrayUtils; import com.android.providers.contacts.ContactsDatabaseHelper; import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns; import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns; import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties; import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns; import com.android.providers.contacts.ContactsDatabaseHelper.Tables; import com.android.providers.contacts.tests.R; import com.google.android.collect.Lists; import com.google.android.collect.Sets; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Set; /** * Unit tests for {@link ContactsProvider2}. * * Run the test like this: * adb shell am instrument -e class com.android.providers.contacts.ContactsProvider2Test -w \ com.android.providers.contacts.tests/android.test.InstrumentationTestRunner * */ @LargeTest public class ContactsProvider2Test extends BaseContactsProvider2Test { private static final Account ACCOUNT_1 = new Account("account_name_1", "account_type_1"); private static final Account ACCOUNT_2 = new Account("account_name_2", "account_type_2"); public void testContactsProjection() { assertProjection(Contacts.CONTENT_URI, new String[]{ Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, Contacts.HAS_PHONE_NUMBER, Contacts.SEND_TO_VOICEMAIL, Contacts.IS_USER_PROFILE, Contacts.LOOKUP_KEY, Contacts.NAME_RAW_CONTACT_ID, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, }); } public void testContactsStrequentProjection() { assertProjection(Contacts.CONTENT_STREQUENT_URI, new String[]{ Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, Contacts.HAS_PHONE_NUMBER, Contacts.SEND_TO_VOICEMAIL, Contacts.IS_USER_PROFILE, Contacts.LOOKUP_KEY, Contacts.NAME_RAW_CONTACT_ID, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, DataUsageStatColumns.TIMES_USED, DataUsageStatColumns.LAST_TIME_USED, }); } public void testContactsStrequentPhoneOnlyProjection() { assertProjection(Contacts.CONTENT_STREQUENT_URI.buildUpon() .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build(), new String[] { Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, Contacts.HAS_PHONE_NUMBER, Contacts.SEND_TO_VOICEMAIL, Contacts.IS_USER_PROFILE, Contacts.LOOKUP_KEY, Contacts.NAME_RAW_CONTACT_ID, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, DataUsageStatColumns.TIMES_USED, DataUsageStatColumns.LAST_TIME_USED, Phone.NUMBER, Phone.TYPE, Phone.LABEL, }); } public void testContactsWithSnippetProjection() { assertProjection(Contacts.CONTENT_FILTER_URI.buildUpon().appendPath("nothing").build(), new String[]{ Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, Contacts.HAS_PHONE_NUMBER, Contacts.SEND_TO_VOICEMAIL, Contacts.IS_USER_PROFILE, Contacts.LOOKUP_KEY, Contacts.NAME_RAW_CONTACT_ID, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, SearchSnippetColumns.SNIPPET, }); } public void testRawContactsProjection() { assertProjection(RawContacts.CONTENT_URI, new String[]{ RawContacts._ID, RawContacts.CONTACT_ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET, RawContacts.ACCOUNT_TYPE_AND_DATA_SET, RawContacts.SOURCE_ID, RawContacts.VERSION, RawContacts.RAW_CONTACT_IS_USER_PROFILE, RawContacts.DIRTY, RawContacts.DELETED, RawContacts.DISPLAY_NAME_PRIMARY, RawContacts.DISPLAY_NAME_ALTERNATIVE, RawContacts.DISPLAY_NAME_SOURCE, RawContacts.PHONETIC_NAME, RawContacts.PHONETIC_NAME_STYLE, RawContacts.NAME_VERIFIED, RawContacts.SORT_KEY_PRIMARY, RawContacts.SORT_KEY_ALTERNATIVE, RawContacts.TIMES_CONTACTED, RawContacts.LAST_TIME_CONTACTED, RawContacts.CUSTOM_RINGTONE, RawContacts.SEND_TO_VOICEMAIL, RawContacts.STARRED, RawContacts.AGGREGATION_MODE, RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4, }); } public void testDataProjection() { assertProjection(Data.CONTENT_URI, new String[]{ Data._ID, Data.RAW_CONTACT_ID, Data.DATA_VERSION, Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY, Data.RES_PACKAGE, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4, Data.CONTACT_ID, Data.PRESENCE, Data.CHAT_CAPABILITY, Data.STATUS, Data.STATUS_TIMESTAMP, Data.STATUS_RES_PACKAGE, Data.STATUS_LABEL, Data.STATUS_ICON, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET, RawContacts.ACCOUNT_TYPE_AND_DATA_SET, RawContacts.SOURCE_ID, RawContacts.VERSION, RawContacts.DIRTY, RawContacts.NAME_VERIFIED, RawContacts.RAW_CONTACT_IS_USER_PROFILE, Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, Contacts.SEND_TO_VOICEMAIL, Contacts.LOOKUP_KEY, Contacts.NAME_RAW_CONTACT_ID, Contacts.HAS_PHONE_NUMBER, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, GroupMembership.GROUP_SOURCE_ID, }); } public void testDistinctDataProjection() { assertProjection(Phone.CONTENT_FILTER_URI.buildUpon().appendPath("123").build(), new String[]{ Data._ID, Data.DATA_VERSION, Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY, Data.RES_PACKAGE, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4, Data.CONTACT_ID, Data.PRESENCE, Data.CHAT_CAPABILITY, Data.STATUS, Data.STATUS_TIMESTAMP, Data.STATUS_RES_PACKAGE, Data.STATUS_LABEL, Data.STATUS_ICON, RawContacts.RAW_CONTACT_IS_USER_PROFILE, Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.HAS_PHONE_NUMBER, Contacts.CUSTOM_RINGTONE, Contacts.SEND_TO_VOICEMAIL, Contacts.LOOKUP_KEY, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, GroupMembership.GROUP_SOURCE_ID, }); } public void testEntityProjection() { assertProjection( Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, 0), Contacts.Entity.CONTENT_DIRECTORY), new String[]{ Contacts.Entity._ID, Contacts.Entity.DATA_ID, Contacts.Entity.RAW_CONTACT_ID, Data.DATA_VERSION, Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY, Data.RES_PACKAGE, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4, Data.CONTACT_ID, Data.PRESENCE, Data.CHAT_CAPABILITY, Data.STATUS, Data.STATUS_TIMESTAMP, Data.STATUS_RES_PACKAGE, Data.STATUS_LABEL, Data.STATUS_ICON, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET, RawContacts.ACCOUNT_TYPE_AND_DATA_SET, RawContacts.SOURCE_ID, RawContacts.VERSION, RawContacts.DELETED, RawContacts.DIRTY, RawContacts.NAME_VERIFIED, RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4, Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.DISPLAY_NAME_SOURCE, Contacts.PHONETIC_NAME, Contacts.PHONETIC_NAME_STYLE, Contacts.SORT_KEY_PRIMARY, Contacts.SORT_KEY_ALTERNATIVE, Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, Contacts.PHOTO_URI, Contacts.PHOTO_THUMBNAIL_URI, Contacts.CUSTOM_RINGTONE, Contacts.SEND_TO_VOICEMAIL, Contacts.IS_USER_PROFILE, Contacts.LOOKUP_KEY, Contacts.NAME_RAW_CONTACT_ID, Contacts.HAS_PHONE_NUMBER, Contacts.CONTACT_PRESENCE, Contacts.CONTACT_CHAT_CAPABILITY, Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, Contacts.CONTACT_STATUS_ICON, GroupMembership.GROUP_SOURCE_ID, }); } public void testRawEntityProjection() { assertProjection(RawContactsEntity.CONTENT_URI, new String[]{ RawContacts.Entity.DATA_ID, RawContacts._ID, RawContacts.CONTACT_ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET, RawContacts.ACCOUNT_TYPE_AND_DATA_SET, RawContacts.SOURCE_ID, RawContacts.VERSION, RawContacts.DIRTY, RawContacts.NAME_VERIFIED, RawContacts.DELETED, RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4, RawContacts.STARRED, RawContacts.RAW_CONTACT_IS_USER_PROFILE, Data.DATA_VERSION, Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY, Data.RES_PACKAGE, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4, GroupMembership.GROUP_SOURCE_ID, }); } public void testPhoneLookupProjection() { assertProjection(PhoneLookup.CONTENT_FILTER_URI.buildUpon().appendPath("123").build(), new String[]{ PhoneLookup._ID, PhoneLookup.LOOKUP_KEY, PhoneLookup.DISPLAY_NAME, PhoneLookup.LAST_TIME_CONTACTED, PhoneLookup.TIMES_CONTACTED, PhoneLookup.STARRED, PhoneLookup.IN_VISIBLE_GROUP, PhoneLookup.PHOTO_ID, PhoneLookup.PHOTO_URI, PhoneLookup.PHOTO_THUMBNAIL_URI, PhoneLookup.CUSTOM_RINGTONE, PhoneLookup.HAS_PHONE_NUMBER, PhoneLookup.SEND_TO_VOICEMAIL, PhoneLookup.NUMBER, PhoneLookup.TYPE, PhoneLookup.LABEL, PhoneLookup.NORMALIZED_NUMBER, }); } public void testGroupsProjection() { assertProjection(Groups.CONTENT_URI, new String[]{ Groups._ID, Groups.ACCOUNT_NAME, Groups.ACCOUNT_TYPE, Groups.DATA_SET, Groups.ACCOUNT_TYPE_AND_DATA_SET, Groups.SOURCE_ID, Groups.DIRTY, Groups.VERSION, Groups.RES_PACKAGE, Groups.TITLE, Groups.TITLE_RES, Groups.GROUP_VISIBLE, Groups.SYSTEM_ID, Groups.DELETED, Groups.NOTES, Groups.SHOULD_SYNC, Groups.FAVORITES, Groups.AUTO_ADD, Groups.GROUP_IS_READ_ONLY, Groups.SYNC1, Groups.SYNC2, Groups.SYNC3, Groups.SYNC4, }); } public void testGroupsSummaryProjection() { assertProjection(Groups.CONTENT_SUMMARY_URI, new String[]{ Groups._ID, Groups.ACCOUNT_NAME, Groups.ACCOUNT_TYPE, Groups.DATA_SET, Groups.ACCOUNT_TYPE_AND_DATA_SET, Groups.SOURCE_ID, Groups.DIRTY, Groups.VERSION, Groups.RES_PACKAGE, Groups.TITLE, Groups.TITLE_RES, Groups.GROUP_VISIBLE, Groups.SYSTEM_ID, Groups.DELETED, Groups.NOTES, Groups.SHOULD_SYNC, Groups.FAVORITES, Groups.AUTO_ADD, Groups.GROUP_IS_READ_ONLY, Groups.SYNC1, Groups.SYNC2, Groups.SYNC3, Groups.SYNC4, Groups.SUMMARY_COUNT, Groups.SUMMARY_WITH_PHONES, Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, }); } public void testAggregateExceptionProjection() { assertProjection(AggregationExceptions.CONTENT_URI, new String[]{ AggregationExceptionColumns._ID, AggregationExceptions.TYPE, AggregationExceptions.RAW_CONTACT_ID1, AggregationExceptions.RAW_CONTACT_ID2, }); } public void testSettingsProjection() { assertProjection(Settings.CONTENT_URI, new String[]{ Settings.ACCOUNT_NAME, Settings.ACCOUNT_TYPE, Settings.DATA_SET, Settings.UNGROUPED_VISIBLE, Settings.SHOULD_SYNC, Settings.ANY_UNSYNCED, Settings.UNGROUPED_COUNT, Settings.UNGROUPED_WITH_PHONES, }); } public void testStatusUpdatesProjection() { assertProjection(StatusUpdates.CONTENT_URI, new String[]{ PresenceColumns.RAW_CONTACT_ID, StatusUpdates.DATA_ID, StatusUpdates.IM_ACCOUNT, StatusUpdates.IM_HANDLE, StatusUpdates.PROTOCOL, StatusUpdates.CUSTOM_PROTOCOL, StatusUpdates.PRESENCE, StatusUpdates.CHAT_CAPABILITY, StatusUpdates.STATUS, StatusUpdates.STATUS_TIMESTAMP, StatusUpdates.STATUS_RES_PACKAGE, StatusUpdates.STATUS_ICON, StatusUpdates.STATUS_LABEL, }); } public void testDirectoryProjection() { assertProjection(Directory.CONTENT_URI, new String[]{ Directory._ID, Directory.PACKAGE_NAME, Directory.TYPE_RESOURCE_ID, Directory.DISPLAY_NAME, Directory.DIRECTORY_AUTHORITY, Directory.ACCOUNT_TYPE, Directory.ACCOUNT_NAME, Directory.EXPORT_SUPPORT, Directory.SHORTCUT_SUPPORT, Directory.PHOTO_SUPPORT, }); } public void testRawContactsInsert() { ContentValues values = new ContentValues(); values.put(RawContacts.ACCOUNT_NAME, "a"); values.put(RawContacts.ACCOUNT_TYPE, "b"); values.put(RawContacts.DATA_SET, "ds"); values.put(RawContacts.SOURCE_ID, "c"); values.put(RawContacts.VERSION, 42); values.put(RawContacts.DIRTY, 1); values.put(RawContacts.DELETED, 1); values.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); values.put(RawContacts.LAST_TIME_CONTACTED, 12345); values.put(RawContacts.STARRED, 1); values.put(RawContacts.SYNC1, "e"); values.put(RawContacts.SYNC2, "f"); values.put(RawContacts.SYNC3, "g"); values.put(RawContacts.SYNC4, "h"); Uri rowUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rowUri); assertStoredValues(rowUri, values); assertSelection(RawContacts.CONTENT_URI, values, RawContacts._ID, rawContactId); assertNetworkNotified(true); } public void testDataDirectoryWithLookupUri() { ContentValues values = new ContentValues(); long rawContactId = createRawContactWithName(); insertPhoneNumber(rawContactId, "555-GOOG-411"); insertEmail(rawContactId, "google@android.com"); long contactId = queryContactId(rawContactId); String lookupKey = queryLookupKey(contactId); // Complete and valid lookup URI Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey); Uri dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY); assertDataRows(dataUri, values); // Complete but stale lookup URI lookupUri = ContactsContract.Contacts.getLookupUri(contactId + 1, lookupKey); dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY); assertDataRows(dataUri, values); // Incomplete lookup URI (lookup key only, no contact ID) dataUri = Uri.withAppendedPath(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), Contacts.Data.CONTENT_DIRECTORY); assertDataRows(dataUri, values); } private void assertDataRows(Uri dataUri, ContentValues values) { Cursor cursor = mResolver.query(dataUri, new String[]{ Data.DATA1 }, null, null, Data._ID); assertEquals(3, cursor.getCount()); cursor.moveToFirst(); values.put(Data.DATA1, "John Doe"); assertCursorValues(cursor, values); cursor.moveToNext(); values.put(Data.DATA1, "555-GOOG-411"); assertCursorValues(cursor, values); cursor.moveToNext(); values.put(Data.DATA1, "google@android.com"); assertCursorValues(cursor, values); cursor.close(); } public void testContactEntitiesWithIdBasedUri() { ContentValues values = new ContentValues(); Account account1 = new Account("act1", "actype1"); Account account2 = new Account("act2", "actype2"); long rawContactId1 = createRawContactWithName(account1); insertImHandle(rawContactId1, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk"); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", StatusUpdates.IDLE, "Busy", 90, StatusUpdates.CAPABILITY_HAS_CAMERA, false); long rawContactId2 = createRawContact(account2); setAggregationException( AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); long contactId = queryContactId(rawContactId1); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY); assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2); } public void testContactEntitiesWithLookupUri() { ContentValues values = new ContentValues(); Account account1 = new Account("act1", "actype1"); Account account2 = new Account("act2", "actype2"); long rawContactId1 = createRawContactWithName(account1); insertImHandle(rawContactId1, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk"); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", StatusUpdates.IDLE, "Busy", 90, StatusUpdates.CAPABILITY_HAS_CAMERA, false); long rawContactId2 = createRawContact(account2); setAggregationException( AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); long contactId = queryContactId(rawContactId1); String lookupKey = queryLookupKey(contactId); // First try with a matching contact ID Uri contactLookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey); Uri entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY); assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2); // Now try with a contact ID mismatch contactLookupUri = ContactsContract.Contacts.getLookupUri(contactId + 1, lookupKey); entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY); assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2); // Now try without an ID altogether contactLookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); entityUri = Uri.withAppendedPath(contactLookupUri, Contacts.Entity.CONTENT_DIRECTORY); assertEntityRows(entityUri, contactId, rawContactId1, rawContactId2); } private void assertEntityRows(Uri entityUri, long contactId, long rawContactId1, long rawContactId2) { ContentValues values = new ContentValues(); Cursor cursor = mResolver.query(entityUri, null, null, null, Contacts.Entity.RAW_CONTACT_ID + "," + Contacts.Entity.DATA_ID); assertEquals(3, cursor.getCount()); // First row - name cursor.moveToFirst(); values.put(Contacts.Entity.CONTACT_ID, contactId); values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId1); values.put(Contacts.Entity.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); values.put(Contacts.Entity.DATA1, "John Doe"); values.put(Contacts.Entity.ACCOUNT_NAME, "act1"); values.put(Contacts.Entity.ACCOUNT_TYPE, "actype1"); values.put(Contacts.Entity.DISPLAY_NAME, "John Doe"); values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John"); values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1); values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA); values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE); values.put(Contacts.Entity.CONTACT_STATUS, "Busy"); values.putNull(Contacts.Entity.PRESENCE); assertCursorValues(cursor, values); // Second row - IM cursor.moveToNext(); values.put(Contacts.Entity.CONTACT_ID, contactId); values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId1); values.put(Contacts.Entity.MIMETYPE, Im.CONTENT_ITEM_TYPE); values.put(Contacts.Entity.DATA1, "gtalk"); values.put(Contacts.Entity.ACCOUNT_NAME, "act1"); values.put(Contacts.Entity.ACCOUNT_TYPE, "actype1"); values.put(Contacts.Entity.DISPLAY_NAME, "John Doe"); values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John"); values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1); values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA); values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE); values.put(Contacts.Entity.CONTACT_STATUS, "Busy"); values.put(Contacts.Entity.PRESENCE, StatusUpdates.IDLE); assertCursorValues(cursor, values); // Third row - second raw contact, not data cursor.moveToNext(); values.put(Contacts.Entity.CONTACT_ID, contactId); values.put(Contacts.Entity.RAW_CONTACT_ID, rawContactId2); values.putNull(Contacts.Entity.MIMETYPE); values.putNull(Contacts.Entity.DATA_ID); values.putNull(Contacts.Entity.DATA1); values.put(Contacts.Entity.ACCOUNT_NAME, "act2"); values.put(Contacts.Entity.ACCOUNT_TYPE, "actype2"); values.put(Contacts.Entity.DISPLAY_NAME, "John Doe"); values.put(Contacts.Entity.DISPLAY_NAME_ALTERNATIVE, "Doe, John"); values.put(Contacts.Entity.NAME_RAW_CONTACT_ID, rawContactId1); values.put(Contacts.Entity.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA); values.put(Contacts.Entity.CONTACT_PRESENCE, StatusUpdates.IDLE); values.put(Contacts.Entity.CONTACT_STATUS, "Busy"); values.putNull(Contacts.Entity.PRESENCE); assertCursorValues(cursor, values); cursor.close(); } public void testDataInsert() { long rawContactId = createRawContactWithName("John", "Doe"); ContentValues values = new ContentValues(); putDataValues(values, rawContactId); Uri dataUri = mResolver.insert(Data.CONTENT_URI, values); long dataId = ContentUris.parseId(dataUri); long contactId = queryContactId(rawContactId); values.put(RawContacts.CONTACT_ID, contactId); assertStoredValues(dataUri, values); assertSelection(Data.CONTENT_URI, values, Data._ID, dataId); // Access the same data through the directory under RawContacts Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); Uri rawContactDataUri = Uri.withAppendedPath(rawContactUri, RawContacts.Data.CONTENT_DIRECTORY); assertSelection(rawContactDataUri, values, Data._ID, dataId); // Access the same data through the directory under Contacts Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri contactDataUri = Uri.withAppendedPath(contactUri, Contacts.Data.CONTENT_DIRECTORY); assertSelection(contactDataUri, values, Data._ID, dataId); assertNetworkNotified(true); } public void testRawContactDataQuery() { Account account1 = new Account("a", "b"); Account account2 = new Account("c", "d"); long rawContactId1 = createRawContact(account1); Uri dataUri1 = insertStructuredName(rawContactId1, "John", "Doe"); long rawContactId2 = createRawContact(account2); Uri dataUri2 = insertStructuredName(rawContactId2, "Jane", "Doe"); Uri uri1 = maybeAddAccountQueryParameters(dataUri1, account1); Uri uri2 = maybeAddAccountQueryParameters(dataUri2, account2); assertStoredValue(uri1, Data._ID, ContentUris.parseId(dataUri1)) ; assertStoredValue(uri2, Data._ID, ContentUris.parseId(dataUri2)) ; } public void testPhonesQuery() { ContentValues values = new ContentValues(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); values.put(RawContacts.LAST_TIME_CONTACTED, 12345); values.put(RawContacts.TIMES_CONTACTED, 54321); values.put(RawContacts.STARRED, 1); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Meghan", "Knox"); Uri uri = insertPhoneNumber(rawContactId, "18004664411"); long phoneId = ContentUris.parseId(uri); long contactId = queryContactId(rawContactId); values.clear(); values.put(Data._ID, phoneId); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(RawContacts.CONTACT_ID, contactId); values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values.put(Phone.NUMBER, "18004664411"); values.put(Phone.TYPE, Phone.TYPE_HOME); values.putNull(Phone.LABEL); values.put(Contacts.DISPLAY_NAME, "Meghan Knox"); values.put(Contacts.CUSTOM_RINGTONE, "d"); values.put(Contacts.SEND_TO_VOICEMAIL, 1); values.put(Contacts.LAST_TIME_CONTACTED, 12345); values.put(Contacts.TIMES_CONTACTED, 54321); values.put(Contacts.STARRED, 1); assertStoredValues(ContentUris.withAppendedId(Phone.CONTENT_URI, phoneId), values); assertSelection(Phone.CONTENT_URI, values, Data._ID, phoneId); } public void testPhonesWithMergedContacts() { long rawContactId1 = createRawContact(); insertPhoneNumber(rawContactId1, "123456789", true); long rawContactId2 = createRawContact(); insertPhoneNumber(rawContactId2, "123456789", true); setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, rawContactId1, rawContactId2); assertNotAggregated(rawContactId1, rawContactId2); ContentValues values1 = new ContentValues(); values1.put(Contacts.DISPLAY_NAME, "123456789"); values1.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values1.put(Phone.NUMBER, "123456789"); // There are two phone numbers, so we should get two rows. assertStoredValues(Phone.CONTENT_URI, new ContentValues[] {values1, values1}); // Now set the dedupe flag. But still we should get two rows, because they're two // different contacts. We only dedupe within each contact. final Uri dedupeUri = Phone.CONTENT_URI.buildUpon() .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") .build(); assertStoredValues(dedupeUri, new ContentValues[] {values1, values1}); // Now join them into a single contact. setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); assertAggregated(rawContactId1, rawContactId2, "123456789"); // Contact merge won't affect the default result of Phone Uri, where we don't dedupe. assertStoredValues(Phone.CONTENT_URI, new ContentValues[] {values1, values1}); // Now we dedupe them. assertStoredValues(dedupeUri, values1); } public void testPhonesNormalizedNumber() { final long rawContactId = createRawContact(); // Write both a number and a normalized number. Those should be written as-is final ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values.put(Phone.NUMBER, "1234"); values.put(Phone.NORMALIZED_NUMBER, "5678"); values.put(Phone.TYPE, Phone.TYPE_HOME); final Uri dataUri = mResolver.insert(Data.CONTENT_URI, values); // Check the lookup table. assertEquals(1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "1234"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "5678"), null, null)); // Check the data table. assertStoredValues(dataUri, cv(Phone.NUMBER, "1234", Phone.NORMALIZED_NUMBER, "5678") ); // Replace both in an UPDATE values.clear(); values.put(Phone.NUMBER, "4321"); values.put(Phone.NORMALIZED_NUMBER, "8765"); mResolver.update(dataUri, values, null, null); assertEquals(0, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "1234"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "4321"), null, null)); assertEquals(0, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "5678"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "8765"), null, null)); assertStoredValues(dataUri, cv(Phone.NUMBER, "4321", Phone.NORMALIZED_NUMBER, "8765") ); // Replace only NUMBER ==> NORMALIZED_NUMBER will be inferred (we test that by making // sure the old manual value can not be found anymore) values.clear(); values.put(Phone.NUMBER, "+1-800-466-5432"); mResolver.update(dataUri, values, null, null); assertEquals( 1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "+1-800-466-5432"), null, null)); assertEquals(0, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "8765"), null, null)); assertStoredValues(dataUri, cv(Phone.NUMBER, "+1-800-466-5432", Phone.NORMALIZED_NUMBER, "+18004665432") ); // Replace only NORMALIZED_NUMBER ==> call is ignored, things will be unchanged values.clear(); values.put(Phone.NORMALIZED_NUMBER, "8765"); mResolver.update(dataUri, values, null, null); assertEquals( 1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "+1-800-466-5432"), null, null)); assertEquals(0, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "8765"), null, null)); assertStoredValues(dataUri, cv(Phone.NUMBER, "+1-800-466-5432", Phone.NORMALIZED_NUMBER, "+18004665432") ); // Replace NUMBER with an "invalid" number which can't be normalized. It should clear // NORMALIZED_NUMBER. // 1. Set 999 to NORMALIZED_NUMBER explicitly. values.clear(); values.put(Phone.NUMBER, "888"); values.put(Phone.NORMALIZED_NUMBER, "999"); mResolver.update(dataUri, values, null, null); assertEquals(1, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "999"), null, null)); assertStoredValues(dataUri, cv(Phone.NUMBER, "888", Phone.NORMALIZED_NUMBER, "999") ); // 2. Set an invalid number to NUMBER. values.clear(); values.put(Phone.NUMBER, "1"); mResolver.update(dataUri, values, null, null); assertEquals(0, getCount(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "999"), null, null)); assertStoredValues(dataUri, cv(Phone.NUMBER, "1", Phone.NORMALIZED_NUMBER, null) ); } public void testPhonesFilterQuery() { testPhonesFilterQueryInter(Phone.CONTENT_FILTER_URI); } /** * A convenient method for {@link #testPhonesFilterQuery()} and * {@link #testCallablesFilterQuery()}. * * This confirms if both URIs return identical results for phone-only contacts and * appropriately different results for contacts with sip addresses. * * @param baseFilterUri Either {@link Phone#CONTENT_FILTER_URI} or * {@link Callable#CONTENT_FILTER_URI}. */ private void testPhonesFilterQueryInter(Uri baseFilterUri) { assertTrue("Unsupported Uri (" + baseFilterUri + ")", Phone.CONTENT_FILTER_URI.equals(baseFilterUri) || Callable.CONTENT_FILTER_URI.equals(baseFilterUri)); final long rawContactId1 = createRawContactWithName("Hot", "Tamale", ACCOUNT_1); insertPhoneNumber(rawContactId1, "1-800-466-4411"); final long rawContactId2 = createRawContactWithName("Chilled", "Guacamole", ACCOUNT_2); insertPhoneNumber(rawContactId2, "1-800-466-5432"); insertPhoneNumber(rawContactId2, "0@example.com", false, Phone.TYPE_PAGER); insertPhoneNumber(rawContactId2, "1@example.com", false, Phone.TYPE_PAGER); final Uri filterUri1 = Uri.withAppendedPath(baseFilterUri, "tamale"); ContentValues values = new ContentValues(); values.put(Contacts.DISPLAY_NAME, "Hot Tamale"); values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values.put(Phone.NUMBER, "1-800-466-4411"); values.put(Phone.TYPE, Phone.TYPE_HOME); values.putNull(Phone.LABEL); assertStoredValuesWithProjection(filterUri1, values); final Uri filterUri2 = Uri.withAppendedPath(baseFilterUri, "1-800-GOOG-411"); assertStoredValues(filterUri2, values); final Uri filterUri3 = Uri.withAppendedPath(baseFilterUri, "18004664"); assertStoredValues(filterUri3, values); final Uri filterUri4 = Uri.withAppendedPath(baseFilterUri, "encilada"); assertEquals(0, getCount(filterUri4, null, null)); final Uri filterUri5 = Uri.withAppendedPath(baseFilterUri, "*"); assertEquals(0, getCount(filterUri5, null, null)); ContentValues values1 = new ContentValues(); values1.put(Contacts.DISPLAY_NAME, "Chilled Guacamole"); values1.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values1.put(Phone.NUMBER, "1-800-466-5432"); values1.put(Phone.TYPE, Phone.TYPE_HOME); values1.putNull(Phone.LABEL); ContentValues values2 = new ContentValues(); values2.put(Contacts.DISPLAY_NAME, "Chilled Guacamole"); values2.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values2.put(Phone.NUMBER, "0@example.com"); values2.put(Phone.TYPE, Phone.TYPE_PAGER); values2.putNull(Phone.LABEL); ContentValues values3 = new ContentValues(); values3.put(Contacts.DISPLAY_NAME, "Chilled Guacamole"); values3.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values3.put(Phone.NUMBER, "1@example.com"); values3.put(Phone.TYPE, Phone.TYPE_PAGER); values3.putNull(Phone.LABEL); final Uri filterUri6 = Uri.withAppendedPath(baseFilterUri, "Chilled"); assertStoredValues(filterUri6, new ContentValues[]{values1, values2, values3}); // Insert a SIP address. From here, Phone URI and Callable URI may return different results // than each other. insertSipAddress(rawContactId1, "sip_hot_tamale@example.com"); insertSipAddress(rawContactId1, "sip:sip_hot@example.com"); final Uri filterUri7 = Uri.withAppendedPath(baseFilterUri, "sip_hot"); final Uri filterUri8 = Uri.withAppendedPath(baseFilterUri, "sip_hot_tamale"); if (Callable.CONTENT_FILTER_URI.equals(baseFilterUri)) { ContentValues values4 = new ContentValues(); values4.put(Contacts.DISPLAY_NAME, "Hot Tamale"); values4.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); values4.put(SipAddress.SIP_ADDRESS, "sip_hot_tamale@example.com"); ContentValues values5 = new ContentValues(); values5.put(Contacts.DISPLAY_NAME, "Hot Tamale"); values5.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); values5.put(SipAddress.SIP_ADDRESS, "sip:sip_hot@example.com"); assertStoredValues(filterUri1, new ContentValues[] {values, values4, values5}); assertStoredValues(filterUri7, new ContentValues[] {values4, values5}); assertStoredValues(filterUri8, values4); } else { // Sip address should not affect Phone URI. assertStoredValuesWithProjection(filterUri1, values); assertEquals(0, getCount(filterUri7, null, null)); } // Sanity test. Run tests for "Chilled Guacamole" again and see nothing changes // after the Sip address being inserted. assertStoredValues(filterUri2, values); assertStoredValues(filterUri3, values); assertEquals(0, getCount(filterUri4, null, null)); assertEquals(0, getCount(filterUri5, null, null)); assertStoredValues(filterUri6, new ContentValues[] {values1, values2, values3} ); } public void testPhonesFilterSearchParams() { final long rid1 = createRawContactWithName("Dad", null); insertPhoneNumber(rid1, "123-456-7890"); final long rid2 = createRawContactWithName("Mam", null); insertPhoneNumber(rid2, "323-123-4567"); // By default, "dad" will match both the display name and the phone number. // Because "dad" is "323" after the dialpad conversion, it'll match "Mam" too. assertStoredValues( Phone.CONTENT_FILTER_URI.buildUpon().appendPath("dad").build(), cv(Phone.DISPLAY_NAME, "Dad", Phone.NUMBER, "123-456-7890"), cv(Phone.DISPLAY_NAME, "Mam", Phone.NUMBER, "323-123-4567") ); assertStoredValues( Phone.CONTENT_FILTER_URI.buildUpon().appendPath("dad") .appendQueryParameter(Phone.SEARCH_PHONE_NUMBER_KEY, "0") .build(), cv(Phone.DISPLAY_NAME, "Dad", Phone.NUMBER, "123-456-7890") ); assertStoredValues( Phone.CONTENT_FILTER_URI.buildUpon().appendPath("dad") .appendQueryParameter(Phone.SEARCH_DISPLAY_NAME_KEY, "0") .build(), cv(Phone.DISPLAY_NAME, "Mam", Phone.NUMBER, "323-123-4567") ); assertStoredValues( Phone.CONTENT_FILTER_URI.buildUpon().appendPath("dad") .appendQueryParameter(Phone.SEARCH_DISPLAY_NAME_KEY, "0") .appendQueryParameter(Phone.SEARCH_PHONE_NUMBER_KEY, "0") .build() ); } public void testPhoneLookup() { ContentValues values = new ContentValues(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Hot", "Tamale"); insertPhoneNumber(rawContactId, "18004664411"); // We'll create two lookup records, 18004664411 and +18004664411, and the below lookup // will match both. Uri lookupUri1 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "8004664411"); values.clear(); values.put(PhoneLookup._ID, queryContactId(rawContactId)); values.put(PhoneLookup.DISPLAY_NAME, "Hot Tamale"); values.put(PhoneLookup.NUMBER, "18004664411"); values.put(PhoneLookup.TYPE, Phone.TYPE_HOME); values.putNull(PhoneLookup.LABEL); values.put(PhoneLookup.CUSTOM_RINGTONE, "d"); values.put(PhoneLookup.SEND_TO_VOICEMAIL, 1); assertStoredValues(lookupUri1, null, null, new ContentValues[] {values, values}); // In the context that 8004664411 is a valid number, "4664411" as a // call id should match to both "8004664411" and "+18004664411". Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "4664411"); assertEquals(2, getCount(lookupUri2, null, null)); // A wrong area code 799 vs 800 should not be matched lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "7994664411"); assertEquals(0, getCount(lookupUri2, null, null)); } public void testPhoneLookupUseCases() { ContentValues values = new ContentValues(); Uri rawContactUri; long rawContactId; Uri lookupUri2; values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); // International format in contacts rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Hot", "Tamale"); insertPhoneNumber(rawContactId, "+1-650-861-0000"); values.clear(); // match with international format lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0000"); assertEquals(1, getCount(lookupUri2, null, null)); // match with national format lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0000"); assertEquals(1, getCount(lookupUri2, null, null)); // does not match with wrong area code lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "649 861 0000"); assertEquals(0, getCount(lookupUri2, null, null)); // does not match with missing digits in mistyped area code lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "5 861 0000"); assertEquals(0, getCount(lookupUri2, null, null)); // does not match with missing digit in mistyped area code lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "65 861 0000"); assertEquals(0, getCount(lookupUri2, null, null)); // National format in contacts values.clear(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Hot1", "Tamale"); insertPhoneNumber(rawContactId, "650-861-0001"); values.clear(); // match with international format lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0001"); assertEquals(2, getCount(lookupUri2, null, null)); // match with national format lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0001"); assertEquals(2, getCount(lookupUri2, null, null)); // Local format in contacts values.clear(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Hot2", "Tamale"); insertPhoneNumber(rawContactId, "861-0002"); values.clear(); // match with international format lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "+1 650 861 0002"); assertEquals(1, getCount(lookupUri2, null, null)); // match with national format lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "650 861 0002"); assertEquals(1, getCount(lookupUri2, null, null)); } public void testIntlPhoneLookupUseCases() { // Checks the logic that relies on phone_number_compare_loose(Gingerbread) as a fallback //for phone number lookups. String fullNumber = "01197297427289"; ContentValues values = new ContentValues(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); long rawContactId = ContentUris.parseId(mResolver.insert(RawContacts.CONTENT_URI, values)); insertStructuredName(rawContactId, "Senor", "Chang"); insertPhoneNumber(rawContactId, fullNumber); // Full number should definitely match. assertEquals(2, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, fullNumber), null, null)); // Shorter (local) number with 0 prefix should also match. assertEquals(2, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "097427289"), null, null)); // Number with international (+972) prefix should also match. assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "+97297427289"), null, null)); // Same shorter number with dashes should match. assertEquals(2, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "09-742-7289"), null, null)); // Same shorter number with spaces should match. assertEquals(2, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "09 742 7289"), null, null)); // Some other number should not match. assertEquals(0, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "049102395"), null, null)); } public void testPhoneLookupB5252190() { // Test cases from b/5252190 String storedNumber = "796010101"; ContentValues values = new ContentValues(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); long rawContactId = ContentUris.parseId(mResolver.insert(RawContacts.CONTENT_URI, values)); insertStructuredName(rawContactId, "Senor", "Chang"); insertPhoneNumber(rawContactId, storedNumber); assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "0796010101"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "+48796010101"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "48796010101"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "4-879-601-0101"), null, null)); assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "4 879 601 0101"), null, null)); } public void testPhoneLookupUseStrictPhoneNumberCompare() { // Test lookup cases when mUseStrictPhoneNumberComparison is true final ContactsProvider2 cp = (ContactsProvider2) getProvider(); final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest(); // Get and save the original value of mUseStrictPhoneNumberComparison so that we // can restore it when we are done with the test final boolean oldUseStrict = dbHelper.getUseStrictPhoneNumberComparisonForTest(); dbHelper.setUseStrictPhoneNumberComparisonForTest(true); try { String fullNumber = "01197297427289"; ContentValues values = new ContentValues(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); long rawContactId = ContentUris.parseId( mResolver.insert(RawContacts.CONTENT_URI, values)); insertStructuredName(rawContactId, "Senor", "Chang"); insertPhoneNumber(rawContactId, fullNumber); insertPhoneNumber(rawContactId, "5103337596"); insertPhoneNumber(rawContactId, "+19012345678"); // One match for full number assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, fullNumber), null, null)); // No matches for extra digit at the front assertEquals(0, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "55103337596"), null, null)); // No matches for mispelled area code assertEquals(0, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "5123337596"), null, null)); // One match for matching number with dashes assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "510-333-7596"), null, null)); // One match for matching number with international code assertEquals(1, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "+1-510-333-7596"), null, null)); values.clear(); // No matches for extra 0 in front assertEquals(0, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "0-510-333-7596"), null, null)); values.clear(); // No matches for different country code assertEquals(0, getCount(Uri.withAppendedPath( PhoneLookup.CONTENT_FILTER_URI, "+819012345678"), null, null)); values.clear(); } finally { // restore the original value of mUseStrictPhoneNumberComparison // upon test completion or failure dbHelper.setUseStrictPhoneNumberComparisonForTest(oldUseStrict); } } public void testPhoneUpdate() { ContentValues values = new ContentValues(); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Hot", "Tamale"); Uri phoneUri = insertPhoneNumber(rawContactId, "18004664411"); Uri lookupUri1 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "8004664411"); Uri lookupUri2 = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, "8004664422"); assertEquals(2, getCount(lookupUri1, null, null)); assertEquals(0, getCount(lookupUri2, null, null)); values.clear(); values.put(Phone.NUMBER, "18004664422"); mResolver.update(phoneUri, values, null, null); assertEquals(0, getCount(lookupUri1, null, null)); assertEquals(2, getCount(lookupUri2, null, null)); // Setting number to null will remove the phone lookup record values.clear(); values.putNull(Phone.NUMBER); mResolver.update(phoneUri, values, null, null); assertEquals(0, getCount(lookupUri1, null, null)); assertEquals(0, getCount(lookupUri2, null, null)); // Let's restore that phone lookup record values.clear(); values.put(Phone.NUMBER, "18004664422"); mResolver.update(phoneUri, values, null, null); assertEquals(0, getCount(lookupUri1, null, null)); assertEquals(2, getCount(lookupUri2, null, null)); assertNetworkNotified(true); } /** Tests if {@link Callable#CONTENT_URI} returns both phones and sip addresses. */ public void testCallablesQuery() { long rawContactId1 = createRawContactWithName("Meghan", "Knox"); long phoneId1 = ContentUris.parseId(insertPhoneNumber(rawContactId1, "18004664411")); long contactId1 = queryContactId(rawContactId1); long rawContactId2 = createRawContactWithName("John", "Doe"); long sipAddressId2 = ContentUris.parseId( insertSipAddress(rawContactId2, "sip@example.com")); long contactId2 = queryContactId(rawContactId2); ContentValues values1 = new ContentValues(); values1.put(Data._ID, phoneId1); values1.put(Data.RAW_CONTACT_ID, rawContactId1); values1.put(RawContacts.CONTACT_ID, contactId1); values1.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); values1.put(Phone.NUMBER, "18004664411"); values1.put(Phone.TYPE, Phone.TYPE_HOME); values1.putNull(Phone.LABEL); values1.put(Contacts.DISPLAY_NAME, "Meghan Knox"); ContentValues values2 = new ContentValues(); values2.put(Data._ID, sipAddressId2); values2.put(Data.RAW_CONTACT_ID, rawContactId2); values2.put(RawContacts.CONTACT_ID, contactId2); values2.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE); values2.put(SipAddress.SIP_ADDRESS, "sip@example.com"); values2.put(Contacts.DISPLAY_NAME, "John Doe"); assertEquals(2, getCount(Callable.CONTENT_URI, null, null)); assertStoredValues(Callable.CONTENT_URI, new ContentValues[] { values1, values2 }); } public void testCallablesFilterQuery() { testPhonesFilterQueryInter(Callable.CONTENT_FILTER_URI); } public void testEmailsQuery() { ContentValues values = new ContentValues(); values.put(RawContacts.CUSTOM_RINGTONE, "d"); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); values.put(RawContacts.LAST_TIME_CONTACTED, 12345); values.put(RawContacts.TIMES_CONTACTED, 54321); values.put(RawContacts.STARRED, 1); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); final long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "Meghan", "Knox"); final Uri emailUri = insertEmail(rawContactId, "meghan@acme.com"); final long emailId = ContentUris.parseId(emailUri); final long contactId = queryContactId(rawContactId); values.clear(); values.put(Data._ID, emailId); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(RawContacts.CONTACT_ID, contactId); values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); values.put(Email.DATA, "meghan@acme.com"); values.put(Email.TYPE, Email.TYPE_HOME); values.putNull(Email.LABEL); values.put(Contacts.DISPLAY_NAME, "Meghan Knox"); values.put(Contacts.CUSTOM_RINGTONE, "d"); values.put(Contacts.SEND_TO_VOICEMAIL, 1); values.put(Contacts.LAST_TIME_CONTACTED, 12345); values.put(Contacts.TIMES_CONTACTED, 54321); values.put(Contacts.STARRED, 1); assertStoredValues(Email.CONTENT_URI, values); assertStoredValues(ContentUris.withAppendedId(Email.CONTENT_URI, emailId), values); assertSelection(Email.CONTENT_URI, values, Data._ID, emailId); // Check if the provider detects duplicated email addresses. final Uri emailUri2 = insertEmail(rawContactId, "meghan@acme.com"); final long emailId2 = ContentUris.parseId(emailUri2); final ContentValues values2 = new ContentValues(values); values2.put(Data._ID, emailId2); final Uri dedupeUri = Email.CONTENT_URI.buildUpon() .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") .build(); // URI with ID should return a correct result. assertStoredValues(ContentUris.withAppendedId(Email.CONTENT_URI, emailId), values); assertStoredValues(ContentUris.withAppendedId(dedupeUri, emailId), values); assertStoredValues(ContentUris.withAppendedId(Email.CONTENT_URI, emailId2), values2); assertStoredValues(ContentUris.withAppendedId(dedupeUri, emailId2), values2); assertStoredValues(Email.CONTENT_URI, new ContentValues[] {values, values2}); // If requested to remove duplicates, the query should return just one result, // whose _ID won't be deterministic. values.remove(Data._ID); assertStoredValues(dedupeUri, values); } public void testEmailsLookupQuery() { long rawContactId = createRawContactWithName("Hot", "Tamale"); insertEmail(rawContactId, "tamale@acme.com"); Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, "tamale@acme.com"); ContentValues values = new ContentValues(); values.put(Contacts.DISPLAY_NAME, "Hot Tamale"); values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); values.put(Email.DATA, "tamale@acme.com"); values.put(Email.TYPE, Email.TYPE_HOME); values.putNull(Email.LABEL); assertStoredValues(filterUri1, values); Uri filterUri2 = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, "Ta"); assertStoredValues(filterUri2, values); Uri filterUri3 = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, "encilada@acme.com"); assertEquals(0, getCount(filterUri3, null, null)); } public void testEmailsFilterQuery() { long rawContactId1 = createRawContactWithName("Hot", "Tamale", ACCOUNT_1); insertEmail(rawContactId1, "tamale@acme.com"); insertEmail(rawContactId1, "tamale@acme.com"); long rawContactId2 = createRawContactWithName("Hot", "Tamale", ACCOUNT_2); insertEmail(rawContactId2, "tamale@acme.com"); Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "tam"); ContentValues values = new ContentValues(); values.put(Contacts.DISPLAY_NAME, "Hot Tamale"); values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); values.put(Email.DATA, "tamale@acme.com"); values.put(Email.TYPE, Email.TYPE_HOME); values.putNull(Email.LABEL); assertStoredValuesWithProjection(filterUri1, values); Uri filterUri2 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "hot"); assertStoredValuesWithProjection(filterUri2, values); Uri filterUri3 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "hot tamale"); assertStoredValuesWithProjection(filterUri3, values); Uri filterUri4 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "tamale@acme"); assertStoredValuesWithProjection(filterUri4, values); Uri filterUri5 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "encilada"); assertEquals(0, getCount(filterUri5, null, null)); } /** * Tests if ContactsProvider2 returns addresses according to registration order. */ public void testEmailFilterDefaultSortOrder() { long rawContactId1 = createRawContact(); insertEmail(rawContactId1, "address1@email.com"); insertEmail(rawContactId1, "address2@email.com"); insertEmail(rawContactId1, "address3@email.com"); ContentValues v1 = new ContentValues(); v1.put(Email.ADDRESS, "address1@email.com"); ContentValues v2 = new ContentValues(); v2.put(Email.ADDRESS, "address2@email.com"); ContentValues v3 = new ContentValues(); v3.put(Email.ADDRESS, "address3@email.com"); Uri filterUri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address"); assertStoredValuesOrderly(filterUri, new ContentValues[]{v1, v2, v3}); } /** * Tests if ContactsProvider2 returns primary addresses before the other addresses. */ public void testEmailFilterPrimaryAddress() { long rawContactId1 = createRawContact(); insertEmail(rawContactId1, "address1@email.com"); insertEmail(rawContactId1, "address2@email.com", true); ContentValues v1 = new ContentValues(); v1.put(Email.ADDRESS, "address1@email.com"); ContentValues v2 = new ContentValues(); v2.put(Email.ADDRESS, "address2@email.com"); Uri filterUri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address"); assertStoredValuesOrderly(filterUri, new ContentValues[] { v2, v1 }); } /** * Tests if ContactsProvider2 has email address associated with a primary account before the * other address. */ public void testEmailFilterPrimaryAccount() { long rawContactId1 = createRawContact(ACCOUNT_1); insertEmail(rawContactId1, "account1@email.com"); long rawContactId2 = createRawContact(ACCOUNT_2); insertEmail(rawContactId2, "account2@email.com"); ContentValues v1 = new ContentValues(); v1.put(Email.ADDRESS, "account1@email.com"); ContentValues v2 = new ContentValues(); v2.put(Email.ADDRESS, "account2@email.com"); Uri filterUri1 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc") .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_1.name) .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_TYPE, ACCOUNT_1.type) .build(); assertStoredValuesOrderly(filterUri1, new ContentValues[] { v1, v2 }); Uri filterUri2 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc") .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_2.name) .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_TYPE, ACCOUNT_2.type) .build(); assertStoredValuesOrderly(filterUri2, new ContentValues[] { v2, v1 }); // Just with PRIMARY_ACCOUNT_NAME Uri filterUri3 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc") .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_1.name) .build(); assertStoredValuesOrderly(filterUri3, new ContentValues[]{v1, v2}); Uri filterUri4 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc") .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, ACCOUNT_2.name) .build(); assertStoredValuesOrderly(filterUri4, new ContentValues[] { v2, v1 }); } /** * Test emails with the same domain as primary account are ordered first. */ public void testEmailFilterSameDomainAccountOrder() { final Account account = new Account("tester@email.com", "not_used"); final long rawContactId = createRawContact(account); insertEmail(rawContactId, "account1@testemail.com"); insertEmail(rawContactId, "account1@email.com"); final ContentValues v1 = cv(Email.ADDRESS, "account1@testemail.com"); final ContentValues v2 = cv(Email.ADDRESS, "account1@email.com"); Uri filterUri1 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("acc") .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_NAME, account.name) .appendQueryParameter(ContactsContract.PRIMARY_ACCOUNT_TYPE, account.type) .build(); assertStoredValuesOrderly(filterUri1, v2, v1); } /** * Test "default" emails are sorted above emails used last. */ public void testEmailFilterDefaultOverUsageSort() { final long rawContactId = createRawContact(ACCOUNT_1); final Uri emailUri1 = insertEmail(rawContactId, "account1@testemail.com"); final Uri emailUri2 = insertEmail(rawContactId, "account2@testemail.com"); insertEmail(rawContactId, "account3@testemail.com", true); // Update account1 and account 2 to have higher usage. updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, emailUri1); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, emailUri1); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, emailUri2); final ContentValues v1 = cv(Email.ADDRESS, "account1@testemail.com"); final ContentValues v2 = cv(Email.ADDRESS, "account2@testemail.com"); final ContentValues v3 = cv(Email.ADDRESS, "account3@testemail.com"); // Test that account 3 is first even though account 1 and 2 have higher usage. Uri filterUri = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "acc"); assertStoredValuesOrderly(filterUri, v3, v1, v2); } /** Tests {@link DataUsageFeedback} correctly promotes a data row instead of a raw contact. */ public void testEmailFilterSortOrderWithFeedback() { long rawContactId1 = createRawContact(); String address1 = "address1@email.com"; insertEmail(rawContactId1, address1); long rawContactId2 = createRawContact(); String address2 = "address2@email.com"; insertEmail(rawContactId2, address2); String address3 = "address3@email.com"; ContentUris.parseId(insertEmail(rawContactId2, address3)); ContentValues v1 = new ContentValues(); v1.put(Email.ADDRESS, "address1@email.com"); ContentValues v2 = new ContentValues(); v2.put(Email.ADDRESS, "address2@email.com"); ContentValues v3 = new ContentValues(); v3.put(Email.ADDRESS, "address3@email.com"); Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address"); Uri filterUri2 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("address") .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_CALL) .build(); Uri filterUri3 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("address") .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_LONG_TEXT) .build(); Uri filterUri4 = Email.CONTENT_FILTER_URI.buildUpon().appendPath("address") .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT) .build(); assertStoredValuesOrderly(filterUri1, new ContentValues[] { v1, v2, v3 }); assertStoredValuesOrderly(filterUri2, new ContentValues[] { v1, v2, v3 }); assertStoredValuesOrderly(filterUri3, new ContentValues[] { v1, v2, v3 }); assertStoredValuesOrderly(filterUri4, new ContentValues[] { v1, v2, v3 }); sendFeedback(address3, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, v3); assertStoredValuesWithProjection(RawContacts.CONTENT_URI, cv(RawContacts._ID, rawContactId1, RawContacts.TIMES_CONTACTED, 0 ), cv(RawContacts._ID, rawContactId2, RawContacts.TIMES_CONTACTED, 1 ) ); // account3@email.com should be the first. assertStoredValuesOrderly(filterUri1, new ContentValues[] { v3, v1, v2 }); assertStoredValuesOrderly(filterUri3, new ContentValues[] { v3, v1, v2 }); } /** * Tests {@link DataUsageFeedback} correctly bucketize contacts using each * {@link DataUsageStatColumns#LAST_TIME_USED} */ public void testEmailFilterSortOrderWithOldHistory() { long rawContactId1 = createRawContact(); long dataId1 = ContentUris.parseId(insertEmail(rawContactId1, "address1@email.com")); long dataId2 = ContentUris.parseId(insertEmail(rawContactId1, "address2@email.com")); long dataId3 = ContentUris.parseId(insertEmail(rawContactId1, "address3@email.com")); long dataId4 = ContentUris.parseId(insertEmail(rawContactId1, "address4@email.com")); Uri filterUri1 = Uri.withAppendedPath(Email.CONTENT_FILTER_URI, "address"); ContentValues v1 = new ContentValues(); v1.put(Email.ADDRESS, "address1@email.com"); ContentValues v2 = new ContentValues(); v2.put(Email.ADDRESS, "address2@email.com"); ContentValues v3 = new ContentValues(); v3.put(Email.ADDRESS, "address3@email.com"); ContentValues v4 = new ContentValues(); v4.put(Email.ADDRESS, "address4@email.com"); final ContactsProvider2 provider = (ContactsProvider2) getProvider(); long nowInMillis = System.currentTimeMillis(); long yesterdayInMillis = (nowInMillis - 24 * 60 * 60 * 1000); long sevenDaysAgoInMillis = (nowInMillis - 7 * 24 * 60 * 60 * 1000); long oneYearAgoInMillis = (nowInMillis - 365L * 24 * 60 * 60 * 1000); // address4 is contacted just once yesterday. provider.updateDataUsageStat(Arrays.asList(dataId4), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, yesterdayInMillis); // address3 is contacted twice 1 week ago. provider.updateDataUsageStat(Arrays.asList(dataId3), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, sevenDaysAgoInMillis); provider.updateDataUsageStat(Arrays.asList(dataId3), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, sevenDaysAgoInMillis); // address2 is contacted three times 1 year ago. provider.updateDataUsageStat(Arrays.asList(dataId2), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, oneYearAgoInMillis); provider.updateDataUsageStat(Arrays.asList(dataId2), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, oneYearAgoInMillis); provider.updateDataUsageStat(Arrays.asList(dataId2), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, oneYearAgoInMillis); // auto-complete should prefer recently contacted methods assertStoredValuesOrderly(filterUri1, new ContentValues[] { v4, v3, v2, v1 }); // Pretend address2 is contacted right now provider.updateDataUsageStat(Arrays.asList(dataId2), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, nowInMillis); // Now address2 is the most recently used address assertStoredValuesOrderly(filterUri1, new ContentValues[] { v2, v4, v3, v1 }); // Pretend address1 is contacted right now provider.updateDataUsageStat(Arrays.asList(dataId1), DataUsageFeedback.USAGE_TYPE_LONG_TEXT, nowInMillis); // address2 is preferred to address1 as address2 is used 4 times in total assertStoredValuesOrderly(filterUri1, new ContentValues[] { v2, v1, v4, v3 }); } public void testPostalsQuery() { long rawContactId = createRawContactWithName("Alice", "Nextore"); Uri dataUri = insertPostalAddress(rawContactId, "1600 Amphiteatre Ave, Mountain View"); final long dataId = ContentUris.parseId(dataUri); final long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); values.put(Data._ID, dataId); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(RawContacts.CONTACT_ID, contactId); values.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE); values.put(StructuredPostal.FORMATTED_ADDRESS, "1600 Amphiteatre Ave, Mountain View"); values.put(Contacts.DISPLAY_NAME, "Alice Nextore"); assertStoredValues(StructuredPostal.CONTENT_URI, values); assertStoredValues(ContentUris.withAppendedId(StructuredPostal.CONTENT_URI, dataId), values); assertSelection(StructuredPostal.CONTENT_URI, values, Data._ID, dataId); // Check if the provider detects duplicated addresses. Uri dataUri2 = insertPostalAddress(rawContactId, "1600 Amphiteatre Ave, Mountain View"); final long dataId2 = ContentUris.parseId(dataUri2); final ContentValues values2 = new ContentValues(values); values2.put(Data._ID, dataId2); final Uri dedupeUri = StructuredPostal.CONTENT_URI.buildUpon() .appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true") .build(); // URI with ID should return a correct result. assertStoredValues(ContentUris.withAppendedId(StructuredPostal.CONTENT_URI, dataId), values); assertStoredValues(ContentUris.withAppendedId(dedupeUri, dataId), values); assertStoredValues(ContentUris.withAppendedId(StructuredPostal.CONTENT_URI, dataId2), values2); assertStoredValues(ContentUris.withAppendedId(dedupeUri, dataId2), values2); assertStoredValues(StructuredPostal.CONTENT_URI, new ContentValues[] {values, values2}); // If requested to remove duplicates, the query should return just one result, // whose _ID won't be deterministic. values.remove(Data._ID); assertStoredValues(dedupeUri, values); } public void testQueryContactData() { ContentValues values = new ContentValues(); long contactId = createContact(values, "John", "Doe", "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); assertStoredValues(contactUri, values); assertSelection(Contacts.CONTENT_URI, values, Contacts._ID, contactId); } public void testQueryContactWithStatusUpdate() { ContentValues values = new ContentValues(); long contactId = createContact(values, "John", "Doe", "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE); values.put(Contacts.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); assertStoredValuesWithProjection(contactUri, values); assertSelectionWithProjection(Contacts.CONTENT_URI, values, Contacts._ID, contactId); } public void testQueryContactFilterByName() { ContentValues values = new ContentValues(); long rawContactId = createRawContact(values, "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE); ContentValues nameValues = new ContentValues(); nameValues.put(StructuredName.GIVEN_NAME, "Stu"); nameValues.put(StructuredName.FAMILY_NAME, "Goulash"); nameValues.put(StructuredName.PHONETIC_FAMILY_NAME, "goo"); nameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "LASH"); Uri nameUri = insertStructuredName(rawContactId, nameValues); long contactId = queryContactId(rawContactId); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE); Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goulash"); assertStoredValuesWithProjection(filterUri1, values); assertContactFilter(contactId, "goolash"); assertContactFilter(contactId, "lash"); assertContactFilterNoResult("goolish"); // Phonetic name with given/family reversed should not match assertContactFilterNoResult("lashgoo"); nameValues.clear(); nameValues.put(StructuredName.PHONETIC_FAMILY_NAME, "ga"); nameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "losh"); mResolver.update(nameUri, nameValues, null, null); assertContactFilter(contactId, "galosh"); assertContactFilterNoResult("goolish"); } public void testQueryContactFilterByEmailAddress() { ContentValues values = new ContentValues(); long rawContactId = createRawContact(values, "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE); insertStructuredName(rawContactId, "James", "Bond"); long contactId = queryContactId(rawContactId); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE); Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goog411@acme.com"); assertStoredValuesWithProjection(filterUri1, values); assertContactFilter(contactId, "goog"); assertContactFilter(contactId, "goog411"); assertContactFilter(contactId, "goog411@"); assertContactFilter(contactId, "goog411@acme"); assertContactFilter(contactId, "goog411@acme.com"); assertContactFilterNoResult("goog411@acme.combo"); assertContactFilterNoResult("goog411@le.com"); assertContactFilterNoResult("goolish"); } public void testQueryContactFilterByPhoneNumber() { ContentValues values = new ContentValues(); long rawContactId = createRawContact(values, "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE); insertStructuredName(rawContactId, "James", "Bond"); long contactId = queryContactId(rawContactId); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE); Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "18004664411"); assertStoredValuesWithProjection(filterUri1, values); assertContactFilter(contactId, "18004664411"); assertContactFilter(contactId, "1800466"); assertContactFilter(contactId, "+18004664411"); assertContactFilter(contactId, "8004664411"); assertContactFilterNoResult("78004664411"); assertContactFilterNoResult("18004664412"); assertContactFilterNoResult("8884664411"); } /** * Checks ContactsProvider2 works well with strequent Uris. The provider should return starred * contacts and frequently used contacts. */ public void testQueryContactStrequent() { ContentValues values1 = new ContentValues(); final String email1 = "a@acme.com"; final int timesContacted1 = 0; createContact(values1, "Noah", "Tever", "18004664411", email1, StatusUpdates.OFFLINE, timesContacted1, 0, 0, StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO); final String phoneNumber2 = "18004664412"; ContentValues values2 = new ContentValues(); createContact(values2, "Sam", "Times", phoneNumber2, "b@acme.com", StatusUpdates.INVISIBLE, 3, 0, 0, StatusUpdates.CAPABILITY_HAS_CAMERA); ContentValues values3 = new ContentValues(); final String phoneNumber3 = "18004664413"; final int timesContacted3 = 5; createContact(values3, "Lotta", "Calling", phoneNumber3, "c@acme.com", StatusUpdates.AWAY, timesContacted3, 0, 0, StatusUpdates.CAPABILITY_HAS_VIDEO); ContentValues values4 = new ContentValues(); final long rawContactId4 = createRawContact(values4, "Fay", "Veritt", null, "d@acme.com", StatusUpdates.AVAILABLE, 0, 1, 0, StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE); // Starred contacts should be returned. TIMES_CONTACTED should be ignored and only data // usage feedback should be used for "frequently contacted" listing. assertStoredValues(Contacts.CONTENT_STREQUENT_URI, values4); // Send feedback for the 3rd phone number, pretending we called that person via phone. sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3); // After the feedback, 3rd contact should be shown after starred one. assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI, new ContentValues[] { values4, values3 }); sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1); // Twice. sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1); // After the feedback, 1st and 3rd contacts should be shown after starred one. assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI, new ContentValues[] { values4, values1, values3 }); // With phone-only parameter, 1st and 4th contacts shouldn't be returned because: // 1st: feedbacks are only about email, not about phone call. // 4th: it has no phone number though starred. Uri phoneOnlyStrequentUri = Contacts.CONTENT_STREQUENT_URI.buildUpon() .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true") .build(); assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values3 }); // Now the 4th contact has a phone number. insertPhoneNumber(rawContactId4, "18004664414"); // Phone only strequent should return 4th contact. assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values4, values3 }); // Send feedback for the 2rd phone number, pretending we send the person a SMS message. sendFeedback(phoneNumber2, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1); // SMS feedback shouldn't affect phone-only results. assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values4, values3 }); Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_STREQUENT_FILTER_URI, "fay"); assertStoredValues(filterUri, values4); } public void testQueryContactStrequentFrequentOrder() { // Prepare test data final long rid1 = createRawContact(); final long did1 = ContentUris.parseId(insertPhoneNumber(rid1, "1")); final long did1e = ContentUris.parseId(insertEmail(rid1, "1@email.com")); final long rid2 = createRawContact(); final long did2 = ContentUris.parseId(insertPhoneNumber(rid2, "2")); final long rid3 = createRawContact(); final long did3 = ContentUris.parseId(insertPhoneNumber(rid3, "3")); final long rid4 = createRawContact(); final long did4 = ContentUris.parseId(insertPhoneNumber(rid4, "4")); final long rid5 = createRawContact(); final long did5 = ContentUris.parseId(insertPhoneNumber(rid5, "5")); final long rid6 = createRawContact(); final long did6 = ContentUris.parseId(insertPhoneNumber(rid6, "6")); final long cid1 = queryContactId(rid1); final long cid2 = queryContactId(rid2); final long cid3 = queryContactId(rid3); final long cid4 = queryContactId(rid4); final long cid5 = queryContactId(rid5); final long cid6 = queryContactId(rid6); // Make sure they aren't aggregated. EvenMoreAsserts.assertUnique(cid1, cid2, cid3, cid4, cid5, cid6); // Prepare the clock sMockClock.install(); // We check the timestamp in SQL, which doesn't know about the MockClock. So we need to // use the actual (roughly) time. final long nowInMillis = System.currentTimeMillis(); final long yesterdayInMillis = (nowInMillis - 24 * 60 * 60 * 1000); final long sevenDaysAgoInMillis = (nowInMillis - 7 * 24 * 60 * 60 * 1000); final long oneYearAgoInMillis = (nowInMillis - 365L * 24 * 60 * 60 * 1000); // A year ago... sMockClock.setCurrentTimeMillis(oneYearAgoInMillis); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did1, did2); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did1); // 7 days ago... sMockClock.setCurrentTimeMillis(sevenDaysAgoInMillis); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did3, did4); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did3); // Yesterday... sMockClock.setCurrentTimeMillis(yesterdayInMillis); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did5, did6); updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did5); // Contact cid1 again, but it's an email, not a phone call. updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e); // Check the order -- The regular frequent, which is contact based. // Note because we contacted cid1 yesterday, it's been contacted 3 times, so it comes // first. assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI, cv(Contacts._ID, cid1), cv(Contacts._ID, cid5), cv(Contacts._ID, cid6), cv(Contacts._ID, cid3), cv(Contacts._ID, cid4), cv(Contacts._ID, cid2)); // Check the order -- phone only frequent, which is data based. // Note this is based on data, and only looks at phone numbers, so the order is different // now. assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI.buildUpon() .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "1").build(), cv(Data._ID, did5), cv(Data._ID, did6), cv(Data._ID, did3), cv(Data._ID, did4), cv(Data._ID, did1), cv(Data._ID, did2)); } /** * Checks ContactsProvider2 works well with frequent Uri. The provider should return frequently * contacted person ordered by number of times contacted. */ public void testQueryContactFrequent() { ContentValues values1 = new ContentValues(); final String email1 = "a@acme.com"; createContact(values1, "Noah", "Tever", "18004664411", email1, StatusUpdates.OFFLINE, 0, 0, 0, 0); ContentValues values2 = new ContentValues(); final String email2 = "b@acme.com"; createContact(values2, "Sam", "Times", "18004664412", email2, StatusUpdates.INVISIBLE, 0, 0, 0, 0); ContentValues values3 = new ContentValues(); final String phoneNumber3 = "18004664413"; final long contactId3 = createContact(values3, "Lotta", "Calling", phoneNumber3, "c@acme.com", StatusUpdates.AWAY, 0, 1, 0, 0); ContentValues values4 = new ContentValues(); createContact(values4, "Fay", "Veritt", "18004664414", "d@acme.com", StatusUpdates.AVAILABLE, 0, 1, 0, 0); sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1); assertStoredValues(Contacts.CONTENT_FREQUENT_URI, values1); // Pretend email was sent to the address twice. sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2); sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2); assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values2, values1}); // Three times sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3); sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3); sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3); assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values3, values2, values1}); // Test it works with selection/selectionArgs assertStoredValues(Contacts.CONTENT_FREQUENT_URI, Contacts.STARRED + "=?", new String[] {"0"}, new ContentValues[] {values2, values1}); assertStoredValues(Contacts.CONTENT_FREQUENT_URI, Contacts.STARRED + "=?", new String[] {"1"}, new ContentValues[] {values3}); values3.put(Contacts.STARRED, 0); assertEquals(1, mResolver.update(Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(contactId3)), values3, null, null)); assertStoredValues(Contacts.CONTENT_FREQUENT_URI, Contacts.STARRED + "=?", new String[] {"0"}, new ContentValues[] {values3, values2, values1}); assertStoredValues(Contacts.CONTENT_FREQUENT_URI, Contacts.STARRED + "=?", new String[] {"1"}, new ContentValues[] {}); } public void testQueryContactFrequentExcludingInvisible() { ContentValues values1 = new ContentValues(); final String email1 = "a@acme.com"; final long cid1 = createContact(values1, "Noah", "Tever", "18004664411", email1, StatusUpdates.OFFLINE, 0, 0, 0, 0); ContentValues values2 = new ContentValues(); final String email2 = "b@acme.com"; final long cid2 = createContact(values2, "Sam", "Times", "18004664412", email2, StatusUpdates.INVISIBLE, 0, 0, 0, 0); sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1); sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2); // First, we have two contacts in frequent. assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values2, values1}); // Contact 2 goes invisible. markInvisible(cid2); // Now we have only 1 frequent. assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values1}); } public void testQueryContactGroup() { long groupId = createGroup(null, "testGroup", "Test Group"); ContentValues values1 = new ContentValues(); createContact(values1, "Best", "West", "18004664411", "west@acme.com", StatusUpdates.OFFLINE, 0, 0, groupId, StatusUpdates.CAPABILITY_HAS_CAMERA); ContentValues values2 = new ContentValues(); createContact(values2, "Rest", "East", "18004664422", "east@acme.com", StatusUpdates.AVAILABLE, 0, 0, 0, StatusUpdates.CAPABILITY_HAS_VOICE); Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, "Test Group"); Cursor c = mResolver.query(filterUri1, null, null, null, Contacts._ID); assertEquals(1, c.getCount()); c.moveToFirst(); assertCursorValues(c, values1); c.close(); Uri filterUri2 = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, "Test Group"); c = mResolver.query(filterUri2, null, Contacts.DISPLAY_NAME + "=?", new String[] { "Best West" }, Contacts._ID); assertEquals(1, c.getCount()); c.close(); Uri filterUri3 = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, "Next Group"); c = mResolver.query(filterUri3, null, null, null, Contacts._ID); assertEquals(0, c.getCount()); c.close(); } private void expectSecurityException(String failureMessage, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor c = null; try { c = mResolver.query(uri, projection, selection, selectionArgs, sortOrder); fail(failureMessage); } catch (SecurityException expected) { // The security exception is expected to occur because we're missing a permission. } finally { if (c != null) { c.close(); } } } public void testQueryProfileRequiresReadPermission() { mActor.removePermissions("android.permission.READ_PROFILE"); createBasicProfileContact(new ContentValues()); // Case 1: Retrieving profile contact. expectSecurityException( "Querying for the profile without READ_PROFILE access should fail.", Profile.CONTENT_URI, null, null, null, Contacts._ID); // Case 2: Retrieving profile data. expectSecurityException( "Querying for the profile data without READ_PROFILE access should fail.", Profile.CONTENT_URI.buildUpon().appendPath("data").build(), null, null, null, Contacts._ID); // Case 3: Retrieving profile entities. expectSecurityException( "Querying for the profile entities without READ_PROFILE access should fail.", Profile.CONTENT_URI.buildUpon() .appendPath("entities").build(), null, null, null, Contacts._ID); } public void testQueryProfileByContactIdRequiresReadPermission() { long profileRawContactId = createBasicProfileContact(new ContentValues()); long profileContactId = queryContactId(profileRawContactId); mActor.removePermissions("android.permission.READ_PROFILE"); // A query for the profile contact by ID should fail. expectSecurityException( "Querying for the profile by contact ID without READ_PROFILE access should fail.", ContentUris.withAppendedId(Contacts.CONTENT_URI, profileContactId), null, null, null, Contacts._ID); } public void testQueryProfileByRawContactIdRequiresReadPermission() { long profileRawContactId = createBasicProfileContact(new ContentValues()); // Remove profile read permission and attempt to retrieve the raw contact. mActor.removePermissions("android.permission.READ_PROFILE"); expectSecurityException( "Querying for the raw contact profile without READ_PROFILE access should fail.", ContentUris.withAppendedId(RawContacts.CONTENT_URI, profileRawContactId), null, null, null, RawContacts._ID); } public void testQueryProfileRawContactRequiresReadPermission() { long profileRawContactId = createBasicProfileContact(new ContentValues()); // Remove profile read permission and attempt to retrieve the profile's raw contact data. mActor.removePermissions("android.permission.READ_PROFILE"); // Case 1: Retrieve the overall raw contact set for the profile. expectSecurityException( "Querying for the raw contact profile without READ_PROFILE access should fail.", Profile.CONTENT_RAW_CONTACTS_URI, null, null, null, null); // Case 2: Retrieve the raw contact profile data for the inserted raw contact ID. expectSecurityException( "Querying for the raw profile data without READ_PROFILE access should fail.", ContentUris.withAppendedId( Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon() .appendPath("data").build(), null, null, null, null); // Case 3: Retrieve the raw contact profile entity for the inserted raw contact ID. expectSecurityException( "Querying for the raw profile entities without READ_PROFILE access should fail.", ContentUris.withAppendedId( Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon() .appendPath("entity").build(), null, null, null, null); } public void testQueryProfileDataByDataIdRequiresReadPermission() { createBasicProfileContact(new ContentValues()); Cursor c = mResolver.query(Profile.CONTENT_URI.buildUpon().appendPath("data").build(), new String[]{Data._ID, Data.MIMETYPE}, null, null, null); assertEquals(4, c.getCount()); // Photo, phone, email, name. c.moveToFirst(); long profileDataId = c.getLong(0); c.close(); // Remove profile read permission and attempt to retrieve the data mActor.removePermissions("android.permission.READ_PROFILE"); expectSecurityException( "Querying for the data in the profile without READ_PROFILE access should fail.", ContentUris.withAppendedId(Data.CONTENT_URI, profileDataId), null, null, null, null); } public void testQueryProfileDataRequiresReadPermission() { createBasicProfileContact(new ContentValues()); // Remove profile read permission and attempt to retrieve all profile data. mActor.removePermissions("android.permission.READ_PROFILE"); expectSecurityException( "Querying for the data in the profile without READ_PROFILE access should fail.", Profile.CONTENT_URI.buildUpon().appendPath("data").build(), null, null, null, null); } public void testInsertProfileRequiresWritePermission() { mActor.removePermissions("android.permission.WRITE_PROFILE"); // Creating a non-profile contact should be fine. createBasicNonProfileContact(new ContentValues()); // Creating a profile contact should throw an exception. try { createBasicProfileContact(new ContentValues()); fail("Creating a profile contact should fail without WRITE_PROFILE access."); } catch (SecurityException expected) { } } public void testInsertProfileDataRequiresWritePermission() { long profileRawContactId = createBasicProfileContact(new ContentValues()); mActor.removePermissions("android.permission.WRITE_PROFILE"); try { insertEmail(profileRawContactId, "foo@bar.net", false); fail("Inserting data into a profile contact should fail without WRITE_PROFILE access."); } catch (SecurityException expected) { } } public void testUpdateDataDoesNotRequireProfilePermission() { mActor.removePermissions("android.permission.READ_PROFILE"); mActor.removePermissions("android.permission.WRITE_PROFILE"); // Create a non-profile contact. long rawContactId = createRawContactWithName("Domo", "Arigato"); long dataId = getStoredLongValue(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", new String[]{String.valueOf(rawContactId), StructuredName.CONTENT_ITEM_TYPE}, Data._ID); // Updates its name using a selection. ContentValues values = new ContentValues(); values.put(StructuredName.GIVEN_NAME, "Bob"); values.put(StructuredName.FAMILY_NAME, "Blob"); mResolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[]{String.valueOf(dataId)}); // Check that the update went through. assertStoredValues(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), values); } public void testQueryContactThenProfile() { ContentValues profileValues = new ContentValues(); long profileRawContactId = createBasicProfileContact(profileValues); long profileContactId = queryContactId(profileRawContactId); ContentValues nonProfileValues = new ContentValues(); long nonProfileRawContactId = createBasicNonProfileContact(nonProfileValues); long nonProfileContactId = queryContactId(nonProfileRawContactId); assertStoredValues(Contacts.CONTENT_URI, nonProfileValues); assertSelection(Contacts.CONTENT_URI, nonProfileValues, Contacts._ID, nonProfileContactId); assertStoredValues(Profile.CONTENT_URI, profileValues); } public void testQueryContactExcludeProfile() { // Create a profile contact (it should not be returned by the general contact URI). createBasicProfileContact(new ContentValues()); // Create a non-profile contact - this should be returned. ContentValues nonProfileValues = new ContentValues(); createBasicNonProfileContact(nonProfileValues); assertStoredValues(Contacts.CONTENT_URI, new ContentValues[] {nonProfileValues}); } public void testQueryProfile() { ContentValues profileValues = new ContentValues(); createBasicProfileContact(profileValues); assertStoredValues(Profile.CONTENT_URI, profileValues); } private ContentValues[] getExpectedProfileDataValues() { // Expected photo data values (only field is the photo BLOB, which we can't check). ContentValues photoRow = new ContentValues(); photoRow.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); // Expected phone data values. ContentValues phoneRow = new ContentValues(); phoneRow.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); phoneRow.put(Phone.NUMBER, "18005554411"); // Expected email data values. ContentValues emailRow = new ContentValues(); emailRow.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); emailRow.put(Email.ADDRESS, "mia.prophyl@acme.com"); // Expected name data values. ContentValues nameRow = new ContentValues(); nameRow.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); nameRow.put(StructuredName.DISPLAY_NAME, "Mia Prophyl"); nameRow.put(StructuredName.GIVEN_NAME, "Mia"); nameRow.put(StructuredName.FAMILY_NAME, "Prophyl"); return new ContentValues[]{photoRow, phoneRow, emailRow, nameRow}; } public void testQueryProfileData() { createBasicProfileContact(new ContentValues()); assertStoredValues(Profile.CONTENT_URI.buildUpon().appendPath("data").build(), getExpectedProfileDataValues()); } public void testQueryProfileEntities() { createBasicProfileContact(new ContentValues()); assertStoredValues(Profile.CONTENT_URI.buildUpon().appendPath("entities").build(), getExpectedProfileDataValues()); } public void testQueryRawProfile() { ContentValues profileValues = new ContentValues(); createBasicProfileContact(profileValues); // The raw contact view doesn't include the photo ID. profileValues.remove(Contacts.PHOTO_ID); assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, profileValues); } public void testQueryRawProfileById() { ContentValues profileValues = new ContentValues(); long profileRawContactId = createBasicProfileContact(profileValues); // The raw contact view doesn't include the photo ID. profileValues.remove(Contacts.PHOTO_ID); assertStoredValues(ContentUris.withAppendedId( Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId), profileValues); } public void testQueryRawProfileData() { long profileRawContactId = createBasicProfileContact(new ContentValues()); assertStoredValues(ContentUris.withAppendedId( Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon() .appendPath("data").build(), getExpectedProfileDataValues()); } public void testQueryRawProfileEntity() { long profileRawContactId = createBasicProfileContact(new ContentValues()); assertStoredValues(ContentUris.withAppendedId( Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId).buildUpon() .appendPath("entity").build(), getExpectedProfileDataValues()); } public void testQueryDataForProfile() { createBasicProfileContact(new ContentValues()); assertStoredValues(Profile.CONTENT_URI.buildUpon().appendPath("data").build(), getExpectedProfileDataValues()); } public void testUpdateProfileRawContact() { createBasicProfileContact(new ContentValues()); ContentValues updatedValues = new ContentValues(); updatedValues.put(RawContacts.SEND_TO_VOICEMAIL, 0); updatedValues.put(RawContacts.CUSTOM_RINGTONE, "rachmaninoff3"); updatedValues.put(RawContacts.STARRED, 1); mResolver.update(Profile.CONTENT_RAW_CONTACTS_URI, updatedValues, null, null); assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, updatedValues); } public void testInsertProfileWithDataSetTriggersAccountCreation() { // Check that we have no profile raw contacts. assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, new ContentValues[]{}); // Insert a profile record with a new data set. Account account = new Account("a", "b"); String dataSet = "c"; Uri profileUri = maybeAddAccountQueryParameters(Profile.CONTENT_RAW_CONTACTS_URI, account) .buildUpon().appendQueryParameter(RawContacts.DATA_SET, dataSet).build(); ContentValues values = new ContentValues(); long rawContactId = ContentUris.parseId(mResolver.insert(profileUri, values)); values.put(RawContacts._ID, rawContactId); // Check that querying for the profile gets the created raw contact. assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, values); } public void testLoadProfilePhoto() throws IOException { long rawContactId = createBasicProfileContact(new ContentValues()); insertPhoto(rawContactId, R.drawable.earth_normal); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.THUMBNAIL), Contacts.openContactPhotoInputStream(mResolver, Profile.CONTENT_URI, false)); } public void testLoadProfileDisplayPhoto() throws IOException { long rawContactId = createBasicProfileContact(new ContentValues()); insertPhoto(rawContactId, R.drawable.earth_normal); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), Contacts.openContactPhotoInputStream(mResolver, Profile.CONTENT_URI, true)); } public void testPhonesWithStatusUpdate() { ContentValues values = new ContentValues(); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "John", "Doe"); Uri photoUri = insertPhoto(rawContactId); long photoId = ContentUris.parseId(photoUri); insertPhoneNumber(rawContactId, "18004664411"); insertPhoneNumber(rawContactId, "18004664412"); insertEmail(rawContactId, "goog411@acme.com"); insertEmail(rawContactId, "goog412@acme.com"); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com", StatusUpdates.INVISIBLE, "Bad", StatusUpdates.CAPABILITY_HAS_CAMERA); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog412@acme.com", StatusUpdates.AVAILABLE, "Good", StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VOICE); long contactId = queryContactId(rawContactId); Uri uri = Data.CONTENT_URI; Cursor c = mResolver.query(uri, null, RawContacts.CONTACT_ID + "=" + contactId + " AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'", null, Phone.NUMBER); assertEquals(2, c.getCount()); c.moveToFirst(); values.clear(); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE); values.put(Contacts.CONTACT_STATUS, "Bad"); values.put(Contacts.DISPLAY_NAME, "John Doe"); values.put(Phone.NUMBER, "18004664411"); values.putNull(Phone.LABEL); values.put(RawContacts.CONTACT_ID, contactId); assertCursorValues(c, values); c.moveToNext(); values.clear(); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE); values.put(Contacts.CONTACT_STATUS, "Bad"); values.put(Contacts.DISPLAY_NAME, "John Doe"); values.put(Phone.NUMBER, "18004664412"); values.putNull(Phone.LABEL); values.put(RawContacts.CONTACT_ID, contactId); assertCursorValues(c, values); c.close(); } public void testGroupQuery() { Account account1 = new Account("a", "b"); Account account2 = new Account("c", "d"); long groupId1 = createGroup(account1, "e", "f"); long groupId2 = createGroup(account2, "g", "h"); Uri uri1 = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account1); Uri uri2 = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account2); assertEquals(1, getCount(uri1, null, null)); assertEquals(1, getCount(uri2, null, null)); assertStoredValue(uri1, Groups._ID + "=" + groupId1, null, Groups._ID, groupId1) ; assertStoredValue(uri2, Groups._ID + "=" + groupId2, null, Groups._ID, groupId2) ; } public void testGroupInsert() { ContentValues values = new ContentValues(); values.put(Groups.ACCOUNT_NAME, "a"); values.put(Groups.ACCOUNT_TYPE, "b"); values.put(Groups.DATA_SET, "ds"); values.put(Groups.SOURCE_ID, "c"); values.put(Groups.VERSION, 42); values.put(Groups.GROUP_VISIBLE, 1); values.put(Groups.TITLE, "d"); values.put(Groups.TITLE_RES, 1234); values.put(Groups.NOTES, "e"); values.put(Groups.RES_PACKAGE, "f"); values.put(Groups.SYSTEM_ID, "g"); values.put(Groups.DELETED, 1); values.put(Groups.SYNC1, "h"); values.put(Groups.SYNC2, "i"); values.put(Groups.SYNC3, "j"); values.put(Groups.SYNC4, "k"); Uri rowUri = mResolver.insert(Groups.CONTENT_URI, values); values.put(Groups.DIRTY, 1); assertStoredValues(rowUri, values); } public void testGroupCreationAfterMembershipInsert() { long rawContactId1 = createRawContact(mAccount); Uri groupMembershipUri = insertGroupMembership(rawContactId1, "gsid1"); long groupId = assertSingleGroup(NO_LONG, mAccount, "gsid1", null); assertSingleGroupMembership(ContentUris.parseId(groupMembershipUri), rawContactId1, groupId, "gsid1"); } public void testGroupReuseAfterMembershipInsert() { long rawContactId1 = createRawContact(mAccount); long groupId1 = createGroup(mAccount, "gsid1", "title1"); Uri groupMembershipUri = insertGroupMembership(rawContactId1, "gsid1"); assertSingleGroup(groupId1, mAccount, "gsid1", "title1"); assertSingleGroupMembership(ContentUris.parseId(groupMembershipUri), rawContactId1, groupId1, "gsid1"); } public void testGroupInsertFailureOnGroupIdConflict() { long rawContactId1 = createRawContact(mAccount); long groupId1 = createGroup(mAccount, "gsid1", "title1"); ContentValues values = new ContentValues(); values.put(GroupMembership.RAW_CONTACT_ID, rawContactId1); values.put(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE); values.put(GroupMembership.GROUP_SOURCE_ID, "gsid1"); values.put(GroupMembership.GROUP_ROW_ID, groupId1); try { mResolver.insert(Data.CONTENT_URI, values); fail("the insert was expected to fail, but it succeeded"); } catch (IllegalArgumentException e) { // this was expected } } public void testGroupDelete_byAccountSelection() { final Account account1 = new Account("accountName1", "accountType1"); final Account account2 = new Account("accountName2", "accountType2"); final long groupId1 = createGroup(account1, "sourceId1", "title1"); final long groupId2 = createGroup(account2, "sourceId2", "title2"); final long groupId3 = createGroup(account2, "sourceId3", "title3"); final int numDeleted = mResolver.delete(Groups.CONTENT_URI, Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?", new String[]{account2.name, account2.type}); assertEquals(2, numDeleted); ContentValues v1 = new ContentValues(); v1.put(Groups._ID, groupId1); v1.put(Groups.DELETED, 0); ContentValues v2 = new ContentValues(); v2.put(Groups._ID, groupId2); v2.put(Groups.DELETED, 1); ContentValues v3 = new ContentValues(); v3.put(Groups._ID, groupId3); v3.put(Groups.DELETED, 1); assertStoredValues(Groups.CONTENT_URI, new ContentValues[] { v1, v2, v3 }); } public void testGroupDelete_byAccountParam() { final Account account1 = new Account("accountName1", "accountType1"); final Account account2 = new Account("accountName2", "accountType2"); final long groupId1 = createGroup(account1, "sourceId1", "title1"); final long groupId2 = createGroup(account2, "sourceId2", "title2"); final long groupId3 = createGroup(account2, "sourceId3", "title3"); final int numDeleted = mResolver.delete( Groups.CONTENT_URI.buildUpon() .appendQueryParameter(Groups.ACCOUNT_NAME, account2.name) .appendQueryParameter(Groups.ACCOUNT_TYPE, account2.type) .build(), null, null); assertEquals(2, numDeleted); ContentValues v1 = new ContentValues(); v1.put(Groups._ID, groupId1); v1.put(Groups.DELETED, 0); ContentValues v2 = new ContentValues(); v2.put(Groups._ID, groupId2); v2.put(Groups.DELETED, 1); ContentValues v3 = new ContentValues(); v3.put(Groups._ID, groupId3); v3.put(Groups.DELETED, 1); assertStoredValues(Groups.CONTENT_URI, new ContentValues[] { v1, v2, v3 }); } public void testGroupSummaryQuery() { final Account account1 = new Account("accountName1", "accountType1"); final Account account2 = new Account("accountName2", "accountType2"); final long groupId1 = createGroup(account1, "sourceId1", "title1"); final long groupId2 = createGroup(account2, "sourceId2", "title2"); final long groupId3 = createGroup(account2, "sourceId3", "title3"); // Prepare raw contact id not used at all, to test group summary uri won't be confused // with it. final long rawContactId0 = createRawContactWithName("firstName0", "lastName0"); final long rawContactId1 = createRawContactWithName("firstName1", "lastName1"); insertEmail(rawContactId1, "address1@email.com"); insertGroupMembership(rawContactId1, groupId1); final long rawContactId2 = createRawContactWithName("firstName2", "lastName2"); insertEmail(rawContactId2, "address2@email.com"); insertPhoneNumber(rawContactId2, "222-222-2222"); insertGroupMembership(rawContactId2, groupId1); ContentValues v1 = new ContentValues(); v1.put(Groups._ID, groupId1); v1.put(Groups.TITLE, "title1"); v1.put(Groups.SOURCE_ID, "sourceId1"); v1.put(Groups.ACCOUNT_NAME, account1.name); v1.put(Groups.ACCOUNT_TYPE, account1.type); v1.put(Groups.SUMMARY_COUNT, 2); v1.put(Groups.SUMMARY_WITH_PHONES, 1); ContentValues v2 = new ContentValues(); v2.put(Groups._ID, groupId2); v2.put(Groups.TITLE, "title2"); v2.put(Groups.SOURCE_ID, "sourceId2"); v2.put(Groups.ACCOUNT_NAME, account2.name); v2.put(Groups.ACCOUNT_TYPE, account2.type); v2.put(Groups.SUMMARY_COUNT, 0); v2.put(Groups.SUMMARY_WITH_PHONES, 0); ContentValues v3 = new ContentValues(); v3.put(Groups._ID, groupId3); v3.put(Groups.TITLE, "title3"); v3.put(Groups.SOURCE_ID, "sourceId3"); v3.put(Groups.ACCOUNT_NAME, account2.name); v3.put(Groups.ACCOUNT_TYPE, account2.type); v3.put(Groups.SUMMARY_COUNT, 0); v3.put(Groups.SUMMARY_WITH_PHONES, 0); assertStoredValues(Groups.CONTENT_SUMMARY_URI, new ContentValues[] { v1, v2, v3 }); // Now rawContactId1 has two phone numbers. insertPhoneNumber(rawContactId1, "111-111-1111"); insertPhoneNumber(rawContactId1, "111-111-1112"); // Result should reflect it correctly (don't count phone numbers but raw contacts) v1.put(Groups.SUMMARY_WITH_PHONES, v1.getAsInteger(Groups.SUMMARY_WITH_PHONES) + 1); assertStoredValues(Groups.CONTENT_SUMMARY_URI, new ContentValues[] { v1, v2, v3 }); // Introduce new raw contact, pretending the user added another info. final long rawContactId3 = createRawContactWithName("firstName3", "lastName3"); insertEmail(rawContactId3, "address3@email.com"); insertPhoneNumber(rawContactId3, "333-333-3333"); insertGroupMembership(rawContactId3, groupId2); v2.put(Groups.SUMMARY_COUNT, v2.getAsInteger(Groups.SUMMARY_COUNT) + 1); v2.put(Groups.SUMMARY_WITH_PHONES, v2.getAsInteger(Groups.SUMMARY_WITH_PHONES) + 1); assertStoredValues(Groups.CONTENT_SUMMARY_URI, new ContentValues[] { v1, v2, v3 }); final Uri uri = Groups.CONTENT_SUMMARY_URI; // TODO Once SUMMARY_GROUP_COUNT_PER_ACCOUNT is supported remove all the if(false). if (false) { v1.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 1); v2.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 2); v3.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 2); } else { v1.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 0); v2.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 0); v3.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 0); } assertStoredValues(uri, new ContentValues[] { v1, v2, v3 }); // Introduce another group in account1, testing SUMMARY_GROUP_COUNT_PER_ACCOUNT correctly // reflects the change. final long groupId4 = createGroup(account1, "sourceId4", "title4"); if (false) { v1.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, v1.getAsInteger(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT) + 1); } else { v1.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 0); } ContentValues v4 = new ContentValues(); v4.put(Groups._ID, groupId4); v4.put(Groups.TITLE, "title4"); v4.put(Groups.SOURCE_ID, "sourceId4"); v4.put(Groups.ACCOUNT_NAME, account1.name); v4.put(Groups.ACCOUNT_TYPE, account1.type); v4.put(Groups.SUMMARY_COUNT, 0); v4.put(Groups.SUMMARY_WITH_PHONES, 0); if (false) { v4.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, v1.getAsInteger(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT)); } else { v4.put(Groups.SUMMARY_GROUP_COUNT_PER_ACCOUNT, 0); } assertStoredValues(uri, new ContentValues[] { v1, v2, v3, v4 }); // We change the tables dynamically according to the requested projection. // Make sure the SUMMARY_COUNT column exists v1.clear(); v1.put(Groups.SUMMARY_COUNT, 2); v2.clear(); v2.put(Groups.SUMMARY_COUNT, 1); v3.clear(); v3.put(Groups.SUMMARY_COUNT, 0); v4.clear(); v4.put(Groups.SUMMARY_COUNT, 0); assertStoredValuesWithProjection(uri, new ContentValues[] { v1, v2, v3, v4 }); } public void testSettingsQuery() { Account account1 = new Account("a", "b"); Account account2 = new Account("c", "d"); AccountWithDataSet account3 = new AccountWithDataSet("e", "f", "plus"); createSettings(account1, "0", "0"); createSettings(account2, "1", "1"); createSettings(account3, "1", "0"); Uri uri1 = maybeAddAccountQueryParameters(Settings.CONTENT_URI, account1); Uri uri2 = maybeAddAccountQueryParameters(Settings.CONTENT_URI, account2); Uri uri3 = Settings.CONTENT_URI.buildUpon() .appendQueryParameter(RawContacts.ACCOUNT_NAME, account3.getAccountName()) .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account3.getAccountType()) .appendQueryParameter(RawContacts.DATA_SET, account3.getDataSet()) .build(); assertEquals(1, getCount(uri1, null, null)); assertEquals(1, getCount(uri2, null, null)); assertEquals(1, getCount(uri3, null, null)); assertStoredValue(uri1, Settings.SHOULD_SYNC, "0") ; assertStoredValue(uri1, Settings.UNGROUPED_VISIBLE, "0"); assertStoredValue(uri2, Settings.SHOULD_SYNC, "1") ; assertStoredValue(uri2, Settings.UNGROUPED_VISIBLE, "1"); assertStoredValue(uri3, Settings.SHOULD_SYNC, "1"); assertStoredValue(uri3, Settings.UNGROUPED_VISIBLE, "0"); } public void testSettingsInsertionPreventsDuplicates() { Account account1 = new Account("a", "b"); AccountWithDataSet account2 = new AccountWithDataSet("c", "d", "plus"); createSettings(account1, "0", "0"); createSettings(account2, "1", "1"); // Now try creating the settings rows again. It should update the existing settings rows. createSettings(account1, "1", "0"); assertStoredValue(Settings.CONTENT_URI, Settings.ACCOUNT_NAME + "=? AND " + Settings.ACCOUNT_TYPE + "=?", new String[] {"a", "b"}, Settings.SHOULD_SYNC, "1"); createSettings(account2, "0", "1"); assertStoredValue(Settings.CONTENT_URI, Settings.ACCOUNT_NAME + "=? AND " + Settings.ACCOUNT_TYPE + "=? AND " + Settings.DATA_SET + "=?", new String[] {"c", "d", "plus"}, Settings.SHOULD_SYNC, "0"); } public void testDisplayNameParsingWhenPartsUnspecified() { long rawContactId = createRawContact(); ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr."); insertStructuredName(rawContactId, values); assertStructuredName(rawContactId, "Mr.", "John", "Kevin", "von Smith", "Jr."); } public void testDisplayNameParsingWhenPartsAreNull() { long rawContactId = createRawContact(); ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr."); values.putNull(StructuredName.GIVEN_NAME); values.putNull(StructuredName.FAMILY_NAME); insertStructuredName(rawContactId, values); assertStructuredName(rawContactId, "Mr.", "John", "Kevin", "von Smith", "Jr."); } public void testDisplayNameParsingWhenPartsSpecified() { long rawContactId = createRawContact(); ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, "Mr.John Kevin von Smith, Jr."); values.put(StructuredName.FAMILY_NAME, "Johnson"); insertStructuredName(rawContactId, values); assertStructuredName(rawContactId, null, null, null, "Johnson", null); } public void testContactWithoutPhoneticName() { final long rawContactId = createRawContact(null); ContentValues values = new ContentValues(); values.put(StructuredName.PREFIX, "Mr"); values.put(StructuredName.GIVEN_NAME, "John"); values.put(StructuredName.MIDDLE_NAME, "K."); values.put(StructuredName.FAMILY_NAME, "Doe"); values.put(StructuredName.SUFFIX, "Jr."); Uri dataUri = insertStructuredName(rawContactId, values); values.clear(); values.put(RawContacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); values.put(RawContacts.DISPLAY_NAME_PRIMARY, "Mr John K. Doe, Jr."); values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "Mr Doe, John K., Jr."); values.putNull(RawContacts.PHONETIC_NAME); values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED); values.put(RawContacts.SORT_KEY_PRIMARY, "John K. Doe, Jr."); values.put(RawContacts.SORT_KEY_ALTERNATIVE, "Doe, John K., Jr."); Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); assertStoredValues(rawContactUri, values); values.clear(); values.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); values.put(Contacts.DISPLAY_NAME_PRIMARY, "Mr John K. Doe, Jr."); values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "Mr Doe, John K., Jr."); values.putNull(Contacts.PHONETIC_NAME); values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED); values.put(Contacts.SORT_KEY_PRIMARY, "John K. Doe, Jr."); values.put(Contacts.SORT_KEY_ALTERNATIVE, "Doe, John K., Jr."); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)); assertStoredValues(contactUri, values); // The same values should be available through a join with Data assertStoredValues(dataUri, values); } public void testContactWithChineseName() { // Only run this test when Chinese collation is supported if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.CHINA)) { return; } long rawContactId = createRawContact(null); ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, "\u6BB5\u5C0F\u6D9B"); Uri dataUri = insertStructuredName(rawContactId, values); values.clear(); values.put(RawContacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); values.put(RawContacts.DISPLAY_NAME_PRIMARY, "\u6BB5\u5C0F\u6D9B"); values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B"); values.putNull(RawContacts.PHONETIC_NAME); values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED); values.put(RawContacts.SORT_KEY_PRIMARY, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B"); values.put(RawContacts.SORT_KEY_ALTERNATIVE, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B"); Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); assertStoredValues(rawContactUri, values); values.clear(); values.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); values.put(Contacts.DISPLAY_NAME_PRIMARY, "\u6BB5\u5C0F\u6D9B"); values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B"); values.putNull(Contacts.PHONETIC_NAME); values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED); values.put(Contacts.SORT_KEY_PRIMARY, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B"); values.put(Contacts.SORT_KEY_ALTERNATIVE, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B"); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)); assertStoredValues(contactUri, values); // The same values should be available through a join with Data assertStoredValues(dataUri, values); } public void testContactWithJapaneseName() { long rawContactId = createRawContact(null); ContentValues values = new ContentValues(); values.put(StructuredName.GIVEN_NAME, "\u7A7A\u6D77"); values.put(StructuredName.PHONETIC_GIVEN_NAME, "\u304B\u3044\u304F\u3046"); Uri dataUri = insertStructuredName(rawContactId, values); values.clear(); values.put(RawContacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); values.put(RawContacts.DISPLAY_NAME_PRIMARY, "\u7A7A\u6D77"); values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "\u7A7A\u6D77"); values.put(RawContacts.PHONETIC_NAME, "\u304B\u3044\u304F\u3046"); values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE); values.put(RawContacts.SORT_KEY_PRIMARY, "\u304B\u3044\u304F\u3046"); values.put(RawContacts.SORT_KEY_ALTERNATIVE, "\u304B\u3044\u304F\u3046"); Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); assertStoredValues(rawContactUri, values); values.clear(); values.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); values.put(Contacts.DISPLAY_NAME_PRIMARY, "\u7A7A\u6D77"); values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "\u7A7A\u6D77"); values.put(Contacts.PHONETIC_NAME, "\u304B\u3044\u304F\u3046"); values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE); values.put(Contacts.SORT_KEY_PRIMARY, "\u304B\u3044\u304F\u3046"); values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u304B\u3044\u304F\u3046"); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)); assertStoredValues(contactUri, values); // The same values should be available through a join with Data assertStoredValues(dataUri, values); } public void testDisplayNameUpdate() { long rawContactId1 = createRawContact(); insertEmail(rawContactId1, "potato@acme.com", true); long rawContactId2 = createRawContact(); insertPhoneNumber(rawContactId2, "123456789", true); setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); assertAggregated(rawContactId1, rawContactId2, "123456789"); insertStructuredName(rawContactId2, "Potato", "Head"); assertAggregated(rawContactId1, rawContactId2, "Potato Head"); assertNetworkNotified(true); } public void testDisplayNameFromData() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); assertStoredValue(uri, Contacts.DISPLAY_NAME, null); insertEmail(rawContactId, "mike@monstersinc.com"); assertStoredValue(uri, Contacts.DISPLAY_NAME, "mike@monstersinc.com"); insertEmail(rawContactId, "james@monstersinc.com", true); assertStoredValue(uri, Contacts.DISPLAY_NAME, "james@monstersinc.com"); insertPhoneNumber(rawContactId, "1-800-466-4411"); assertStoredValue(uri, Contacts.DISPLAY_NAME, "1-800-466-4411"); // If there are title and company, the company is display name. values.clear(); values.put(Organization.COMPANY, "Monsters Inc"); Uri organizationUri = insertOrganization(rawContactId, values); assertStoredValue(uri, Contacts.DISPLAY_NAME, "Monsters Inc"); // If there is nickname, that is display name. insertNickname(rawContactId, "Sully"); assertStoredValue(uri, Contacts.DISPLAY_NAME, "Sully"); // If there is structured name, that is display name. values.clear(); values.put(StructuredName.GIVEN_NAME, "James"); values.put(StructuredName.MIDDLE_NAME, "P."); values.put(StructuredName.FAMILY_NAME, "Sullivan"); insertStructuredName(rawContactId, values); assertStoredValue(uri, Contacts.DISPLAY_NAME, "James P. Sullivan"); } public void testDisplayNameFromOrganizationWithoutPhoneticName() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); // If there is title without company, the title is display name. values.clear(); values.put(Organization.TITLE, "Protagonist"); Uri organizationUri = insertOrganization(rawContactId, values); assertStoredValue(uri, Contacts.DISPLAY_NAME, "Protagonist"); // If there are title and company, the company is display name. values.clear(); values.put(Organization.COMPANY, "Monsters Inc"); mResolver.update(organizationUri, values, null, null); values.clear(); values.put(Contacts.DISPLAY_NAME, "Monsters Inc"); values.putNull(Contacts.PHONETIC_NAME); values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED); values.put(Contacts.SORT_KEY_PRIMARY, "Monsters Inc"); values.put(Contacts.SORT_KEY_ALTERNATIVE, "Monsters Inc"); assertStoredValues(uri, values); } public void testDisplayNameFromOrganizationWithJapanesePhoneticName() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); // If there is title without company, the title is display name. values.clear(); values.put(Organization.COMPANY, "DoCoMo"); values.put(Organization.PHONETIC_NAME, "\u30C9\u30B3\u30E2"); Uri organizationUri = insertOrganization(rawContactId, values); values.clear(); values.put(Contacts.DISPLAY_NAME, "DoCoMo"); values.put(Contacts.PHONETIC_NAME, "\u30C9\u30B3\u30E2"); values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE); values.put(Contacts.SORT_KEY_PRIMARY, "\u30C9\u30B3\u30E2"); values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u30C9\u30B3\u30E2"); assertStoredValues(uri, values); } public void testDisplayNameFromOrganizationWithChineseName() { boolean hasChineseCollator = false; final Locale locale[] = Collator.getAvailableLocales(); for (int i = 0; i < locale.length; i++) { if (locale[i].equals(Locale.CHINA)) { hasChineseCollator = true; break; } } if (!hasChineseCollator) { return; } long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); // If there is title without company, the title is display name. values.clear(); values.put(Organization.COMPANY, "\u4E2D\u56FD\u7535\u4FE1"); Uri organizationUri = insertOrganization(rawContactId, values); values.clear(); values.put(Contacts.DISPLAY_NAME, "\u4E2D\u56FD\u7535\u4FE1"); values.putNull(Contacts.PHONETIC_NAME); values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED); values.put(Contacts.SORT_KEY_PRIMARY, "ZHONG \u4E2D GUO \u56FD DIAN \u7535 XIN \u4FE1"); values.put(Contacts.SORT_KEY_ALTERNATIVE, "ZHONG \u4E2D GUO \u56FD DIAN \u7535 XIN \u4FE1"); assertStoredValues(uri, values); } public void testLookupByOrganization() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); values.clear(); values.put(Organization.COMPANY, "acmecorp"); values.put(Organization.TITLE, "president"); Uri organizationUri = insertOrganization(rawContactId, values); assertContactFilter(contactId, "acmecorp"); assertContactFilter(contactId, "president"); values.clear(); values.put(Organization.DEPARTMENT, "software"); mResolver.update(organizationUri, values, null, null); assertContactFilter(contactId, "acmecorp"); assertContactFilter(contactId, "president"); values.clear(); values.put(Organization.COMPANY, "incredibles"); mResolver.update(organizationUri, values, null, null); assertContactFilter(contactId, "incredibles"); assertContactFilter(contactId, "president"); values.clear(); values.put(Organization.TITLE, "director"); mResolver.update(organizationUri, values, null, null); assertContactFilter(contactId, "incredibles"); assertContactFilter(contactId, "director"); values.clear(); values.put(Organization.COMPANY, "monsters"); values.put(Organization.TITLE, "scarer"); mResolver.update(organizationUri, values, null, null); assertContactFilter(contactId, "monsters"); assertContactFilter(contactId, "scarer"); } private void assertContactFilter(long contactId, String filter) { Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(filter)); assertStoredValue(filterUri, Contacts._ID, contactId); } private void assertContactFilterNoResult(String filter) { Uri filterUri4 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, filter); assertEquals(0, getCount(filterUri4, null, null)); } public void testSearchSnippetOrganization() throws Exception { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); // Some random data element insertEmail(rawContactId, "inc@corp.com"); ContentValues values = new ContentValues(); values.clear(); values.put(Organization.COMPANY, "acmecorp"); values.put(Organization.TITLE, "engineer"); Uri organizationUri = insertOrganization(rawContactId, values); // Add another matching organization values.put(Organization.COMPANY, "acmeinc"); insertOrganization(rawContactId, values); // Add another non-matching organization values.put(Organization.COMPANY, "corpacme"); insertOrganization(rawContactId, values); // And another data element insertEmail(rawContactId, "emca@corp.com", true, Email.TYPE_CUSTOM, "Custom"); Uri filterUri = buildFilterUri("acme", true); values.clear(); values.put(Contacts._ID, contactId); values.put(SearchSnippetColumns.SNIPPET, "engineer, [acmecorp]"); assertStoredValues(filterUri, values); } public void testSearchSnippetEmail() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); insertStructuredName(rawContactId, "John", "Doe"); Uri dataUri = insertEmail(rawContactId, "acme@corp.com", true, Email.TYPE_CUSTOM, "Custom"); Uri filterUri = buildFilterUri("acme", true); values.clear(); values.put(Contacts._ID, contactId); values.put(SearchSnippetColumns.SNIPPET, "[acme@corp.com]"); assertStoredValues(filterUri, values); } public void testCountPhoneNumberDigits() { assertEquals(10, ContactsProvider2.countPhoneNumberDigits("86 (0) 5-55-12-34")); assertEquals(10, ContactsProvider2.countPhoneNumberDigits("860 555-1234")); assertEquals(3, ContactsProvider2.countPhoneNumberDigits("860")); assertEquals(10, ContactsProvider2.countPhoneNumberDigits("8605551234")); assertEquals(6, ContactsProvider2.countPhoneNumberDigits("860555")); assertEquals(6, ContactsProvider2.countPhoneNumberDigits("860 555")); assertEquals(6, ContactsProvider2.countPhoneNumberDigits("860-555")); assertEquals(12, ContactsProvider2.countPhoneNumberDigits("+441234098765")); assertEquals(0, ContactsProvider2.countPhoneNumberDigits("44+1234098765")); assertEquals(0, ContactsProvider2.countPhoneNumberDigits("+441234098foo")); } public void testSearchSnippetPhone() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); insertStructuredName(rawContactId, "Cave", "Johnson"); insertPhoneNumber(rawContactId, "(860) 555-1234"); values.clear(); values.put(Contacts._ID, contactId); values.put(SearchSnippetColumns.SNIPPET, "[(860) 555-1234]"); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("86 (0) 5-55-12-34")), values); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("860 555-1234")), values); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("860")), values); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("8605551234")), values); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("860555")), values); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("860 555")), values); assertStoredValues(Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode("860-555")), values); } private Uri buildFilterUri(String query, boolean deferredSnippeting) { Uri.Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon() .appendPath(Uri.encode(query)); if (deferredSnippeting) { builder.appendQueryParameter(ContactsContract.DEFERRED_SNIPPETING, "1"); } return builder.build(); } public void testSearchSnippetNickname() throws Exception { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); ContentValues values = new ContentValues(); Uri dataUri = insertNickname(rawContactId, "Incredible"); Uri filterUri = buildFilterUri("inc", true); values.clear(); values.put(Contacts._ID, contactId); values.put(SearchSnippetColumns.SNIPPET, "[Incredible]"); assertStoredValues(filterUri, values); } public void testSearchSnippetEmptyForNameInDisplayName() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); insertStructuredName(rawContactId, "Cave", "Johnson"); insertEmail(rawContactId, "cave@aperturescience.com", true); ContentValues emptySnippet = new ContentValues(); emptySnippet.clear(); emptySnippet.put(Contacts._ID, contactId); emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null); assertStoredValues(buildFilterUri("cave", true), emptySnippet); assertStoredValues(buildFilterUri("john", true), emptySnippet); } public void testSearchSnippetEmptyForNicknameInDisplayName() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); insertNickname(rawContactId, "Caveman"); insertEmail(rawContactId, "cave@aperturescience.com", true); ContentValues emptySnippet = new ContentValues(); emptySnippet.clear(); emptySnippet.put(Contacts._ID, contactId); emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null); assertStoredValues(buildFilterUri("cave", true), emptySnippet); } public void testSearchSnippetEmptyForCompanyInDisplayName() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues company = new ContentValues(); company.clear(); company.put(Organization.COMPANY, "Aperture Science"); company.put(Organization.TITLE, "President"); insertOrganization(rawContactId, company); insertEmail(rawContactId, "aperturepresident@aperturescience.com", true); ContentValues emptySnippet = new ContentValues(); emptySnippet.clear(); emptySnippet.put(Contacts._ID, contactId); emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null); assertStoredValues(buildFilterUri("aperture", true), emptySnippet); } public void testSearchSnippetEmptyForPhoneInDisplayName() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); insertPhoneNumber(rawContactId, "860-555-1234"); insertEmail(rawContactId, "860@aperturescience.com", true); ContentValues emptySnippet = new ContentValues(); emptySnippet.clear(); emptySnippet.put(Contacts._ID, contactId); emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null); assertStoredValues(buildFilterUri("860", true), emptySnippet); } public void testSearchSnippetEmptyForEmailInDisplayName() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); insertEmail(rawContactId, "cave@aperturescience.com", true); insertNote(rawContactId, "Cave Johnson is president of Aperture Science"); ContentValues emptySnippet = new ContentValues(); emptySnippet.clear(); emptySnippet.put(Contacts._ID, contactId); emptySnippet.put(SearchSnippetColumns.SNIPPET, (String) null); assertStoredValues(buildFilterUri("cave", true), emptySnippet); } public void testDisplayNameUpdateFromStructuredNameUpdate() { long rawContactId = createRawContact(); Uri nameUri = insertStructuredName(rawContactId, "Slinky", "Dog"); long contactId = queryContactId(rawContactId); Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); assertStoredValue(uri, Contacts.DISPLAY_NAME, "Slinky Dog"); ContentValues values = new ContentValues(); values.putNull(StructuredName.FAMILY_NAME); mResolver.update(nameUri, values, null, null); assertStoredValue(uri, Contacts.DISPLAY_NAME, "Slinky"); values.putNull(StructuredName.GIVEN_NAME); mResolver.update(nameUri, values, null, null); assertStoredValue(uri, Contacts.DISPLAY_NAME, null); values.put(StructuredName.FAMILY_NAME, "Dog"); mResolver.update(nameUri, values, null, null); assertStoredValue(uri, Contacts.DISPLAY_NAME, "Dog"); } public void testInsertDataWithContentProviderOperations() throws Exception { ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValues(new ContentValues()) .build(); ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, 0) .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) .withValue(StructuredName.GIVEN_NAME, "John") .withValue(StructuredName.FAMILY_NAME, "Doe") .build(); ContentProviderResult[] results = mResolver.applyBatch(ContactsContract.AUTHORITY, Lists.newArrayList(cpo1, cpo2)); long contactId = queryContactId(ContentUris.parseId(results[0].uri)); Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); assertStoredValue(uri, Contacts.DISPLAY_NAME, "John Doe"); } public void testSendToVoicemailDefault() { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); Cursor c = queryContact(contactId); assertTrue(c.moveToNext()); int sendToVoicemail = c.getInt(c.getColumnIndex(Contacts.SEND_TO_VOICEMAIL)); assertEquals(0, sendToVoicemail); c.close(); } public void testSetSendToVoicemailAndRingtone() { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); updateSendToVoicemailAndRingtone(contactId, true, "foo"); assertSendToVoicemailAndRingtone(contactId, true, "foo"); assertNetworkNotified(false); updateSendToVoicemailAndRingtoneWithSelection(contactId, false, "bar"); assertSendToVoicemailAndRingtone(contactId, false, "bar"); assertNetworkNotified(false); } public void testSendToVoicemailAndRingtoneAfterAggregation() { long rawContactId1 = createRawContactWithName("a", "b"); long contactId1 = queryContactId(rawContactId1); updateSendToVoicemailAndRingtone(contactId1, true, "foo"); long rawContactId2 = createRawContactWithName("c", "d"); long contactId2 = queryContactId(rawContactId2); updateSendToVoicemailAndRingtone(contactId2, true, "bar"); // Aggregate them setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); // Both contacts had "send to VM", the contact now has the same value assertSendToVoicemailAndRingtone(contactId1, true, "foo,bar"); // Either foo or bar } public void testDoNotSendToVoicemailAfterAggregation() { long rawContactId1 = createRawContactWithName("e", "f"); long contactId1 = queryContactId(rawContactId1); updateSendToVoicemailAndRingtone(contactId1, true, null); long rawContactId2 = createRawContactWithName("g", "h"); long contactId2 = queryContactId(rawContactId2); updateSendToVoicemailAndRingtone(contactId2, false, null); // Aggregate them setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); // Since one of the contacts had "don't send to VM" that setting wins for the aggregate assertSendToVoicemailAndRingtone(queryContactId(rawContactId1), false, null); } public void testSetSendToVoicemailAndRingtonePreservedAfterJoinAndSplit() { long rawContactId1 = createRawContactWithName("i", "j"); long contactId1 = queryContactId(rawContactId1); updateSendToVoicemailAndRingtone(contactId1, true, "foo"); long rawContactId2 = createRawContactWithName("k", "l"); long contactId2 = queryContactId(rawContactId2); updateSendToVoicemailAndRingtone(contactId2, false, "bar"); // Aggregate them setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); // Split them setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, rawContactId1, rawContactId2); assertSendToVoicemailAndRingtone(queryContactId(rawContactId1), true, "foo"); assertSendToVoicemailAndRingtone(queryContactId(rawContactId2), false, "bar"); } public void testStatusUpdateInsert() { long rawContactId = createRawContact(); Uri imUri = insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim"); long dataId = ContentUris.parseId(imUri); ContentValues values = new ContentValues(); values.put(StatusUpdates.DATA_ID, dataId); values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_AIM); values.putNull(StatusUpdates.CUSTOM_PROTOCOL); values.put(StatusUpdates.IM_HANDLE, "aim"); values.put(StatusUpdates.PRESENCE, StatusUpdates.INVISIBLE); values.put(StatusUpdates.STATUS, "Hiding"); values.put(StatusUpdates.STATUS_TIMESTAMP, 100); values.put(StatusUpdates.STATUS_RES_PACKAGE, "a.b.c"); values.put(StatusUpdates.STATUS_ICON, 1234); values.put(StatusUpdates.STATUS_LABEL, 2345); Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values); assertStoredValues(resultUri, values); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); values.clear(); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE); values.put(Contacts.CONTACT_STATUS, "Hiding"); values.put(Contacts.CONTACT_STATUS_TIMESTAMP, 100); values.put(Contacts.CONTACT_STATUS_RES_PACKAGE, "a.b.c"); values.put(Contacts.CONTACT_STATUS_ICON, 1234); values.put(Contacts.CONTACT_STATUS_LABEL, 2345); assertStoredValues(contactUri, values); values.clear(); values.put(StatusUpdates.DATA_ID, dataId); values.put(StatusUpdates.STATUS, "Cloaked"); values.put(StatusUpdates.STATUS_TIMESTAMP, 200); values.put(StatusUpdates.STATUS_RES_PACKAGE, "d.e.f"); values.put(StatusUpdates.STATUS_ICON, 4321); values.put(StatusUpdates.STATUS_LABEL, 5432); mResolver.insert(StatusUpdates.CONTENT_URI, values); values.clear(); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE); values.put(Contacts.CONTACT_STATUS, "Cloaked"); values.put(Contacts.CONTACT_STATUS_TIMESTAMP, 200); values.put(Contacts.CONTACT_STATUS_RES_PACKAGE, "d.e.f"); values.put(Contacts.CONTACT_STATUS_ICON, 4321); values.put(Contacts.CONTACT_STATUS_LABEL, 5432); assertStoredValues(contactUri, values); } public void testStatusUpdateInferAttribution() { long rawContactId = createRawContact(); Uri imUri = insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim"); long dataId = ContentUris.parseId(imUri); ContentValues values = new ContentValues(); values.put(StatusUpdates.DATA_ID, dataId); values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_AIM); values.put(StatusUpdates.IM_HANDLE, "aim"); values.put(StatusUpdates.STATUS, "Hiding"); Uri resultUri = mResolver.insert(StatusUpdates.CONTENT_URI, values); values.clear(); values.put(StatusUpdates.DATA_ID, dataId); values.put(StatusUpdates.STATUS_LABEL, com.android.internal.R.string.imProtocolAim); values.put(StatusUpdates.STATUS, "Hiding"); assertStoredValues(resultUri, values); } public void testStatusUpdateMatchingImOrEmail() { long rawContactId = createRawContact(); insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim"); insertImHandle(rawContactId, Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im"); insertEmail(rawContactId, "m@acme.com"); // Match on IM (standard) insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AVAILABLE, "Available", StatusUpdates.CAPABILITY_HAS_CAMERA); // Match on IM (custom) insertStatusUpdate(Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im", StatusUpdates.IDLE, "Idle", StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO); // Match on Email insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "m@acme.com", StatusUpdates.AWAY, "Away", StatusUpdates.CAPABILITY_HAS_VOICE); // No match insertStatusUpdate(Im.PROTOCOL_ICQ, null, "12345", StatusUpdates.DO_NOT_DISTURB, "Go away", StatusUpdates.CAPABILITY_HAS_CAMERA); Cursor c = mResolver.query(StatusUpdates.CONTENT_URI, new String[] { StatusUpdates.DATA_ID, StatusUpdates.PROTOCOL, StatusUpdates.CUSTOM_PROTOCOL, StatusUpdates.PRESENCE, StatusUpdates.STATUS}, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null, StatusUpdates.DATA_ID); assertTrue(c.moveToNext()); assertStatusUpdate(c, Im.PROTOCOL_AIM, null, StatusUpdates.AVAILABLE, "Available"); assertTrue(c.moveToNext()); assertStatusUpdate(c, Im.PROTOCOL_CUSTOM, "my_im_proto", StatusUpdates.IDLE, "Idle"); assertTrue(c.moveToNext()); assertStatusUpdate(c, Im.PROTOCOL_GOOGLE_TALK, null, StatusUpdates.AWAY, "Away"); assertFalse(c.moveToNext()); c.close(); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); ContentValues values = new ContentValues(); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE); values.put(Contacts.CONTACT_STATUS, "Available"); assertStoredValuesWithProjection(contactUri, values); } public void testStatusUpdateUpdateAndDelete() { long rawContactId = createRawContact(); insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim"); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); ContentValues values = new ContentValues(); values.putNull(Contacts.CONTACT_PRESENCE); values.putNull(Contacts.CONTACT_STATUS); assertStoredValuesWithProjection(contactUri, values); insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AWAY, "BUSY", StatusUpdates.CAPABILITY_HAS_CAMERA); insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.DO_NOT_DISTURB, "GO AWAY", StatusUpdates.CAPABILITY_HAS_CAMERA); Uri statusUri = insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AVAILABLE, "Available", StatusUpdates.CAPABILITY_HAS_CAMERA); long statusId = ContentUris.parseId(statusUri); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE); values.put(Contacts.CONTACT_STATUS, "Available"); assertStoredValuesWithProjection(contactUri, values); // update status_updates table to set new values for // status_updates.status // status_updates.status_ts // presence long updatedTs = 200; String testUpdate = "test_update"; String selection = StatusUpdates.DATA_ID + "=" + statusId; values.clear(); values.put(StatusUpdates.STATUS_TIMESTAMP, updatedTs); values.put(StatusUpdates.STATUS, testUpdate); values.put(StatusUpdates.PRESENCE, "presence_test"); mResolver.update(StatusUpdates.CONTENT_URI, values, StatusUpdates.DATA_ID + "=" + statusId, null); assertStoredValuesWithProjection(StatusUpdates.CONTENT_URI, values); // update status_updates table to set new values for columns in status_updates table ONLY // i.e., no rows in presence table are to be updated. updatedTs = 300; testUpdate = "test_update_new"; selection = StatusUpdates.DATA_ID + "=" + statusId; values.clear(); values.put(StatusUpdates.STATUS_TIMESTAMP, updatedTs); values.put(StatusUpdates.STATUS, testUpdate); mResolver.update(StatusUpdates.CONTENT_URI, values, StatusUpdates.DATA_ID + "=" + statusId, null); // make sure the presence column value is still the old value values.put(StatusUpdates.PRESENCE, "presence_test"); assertStoredValuesWithProjection(StatusUpdates.CONTENT_URI, values); // update status_updates table to set new values for columns in presence table ONLY // i.e., no rows in status_updates table are to be updated. selection = StatusUpdates.DATA_ID + "=" + statusId; values.clear(); values.put(StatusUpdates.PRESENCE, "presence_test_new"); mResolver.update(StatusUpdates.CONTENT_URI, values, StatusUpdates.DATA_ID + "=" + statusId, null); // make sure the status_updates table is not updated values.put(StatusUpdates.STATUS_TIMESTAMP, updatedTs); values.put(StatusUpdates.STATUS, testUpdate); assertStoredValuesWithProjection(StatusUpdates.CONTENT_URI, values); // effect "delete status_updates" operation and expect the following // data deleted from status_updates table // presence set to null mResolver.delete(StatusUpdates.CONTENT_URI, StatusUpdates.DATA_ID + "=" + statusId, null); values.clear(); values.putNull(Contacts.CONTACT_PRESENCE); assertStoredValuesWithProjection(contactUri, values); } public void testStatusUpdateUpdateToNull() { long rawContactId = createRawContact(); insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim"); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); ContentValues values = new ContentValues(); Uri statusUri = insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", StatusUpdates.AVAILABLE, "Available", StatusUpdates.CAPABILITY_HAS_CAMERA); long statusId = ContentUris.parseId(statusUri); values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.AVAILABLE); values.put(Contacts.CONTACT_STATUS, "Available"); assertStoredValuesWithProjection(contactUri, values); values.clear(); values.putNull(StatusUpdates.PRESENCE); mResolver.update(StatusUpdates.CONTENT_URI, values, StatusUpdates.DATA_ID + "=" + statusId, null); values.clear(); values.putNull(Contacts.CONTACT_PRESENCE); values.put(Contacts.CONTACT_STATUS, "Available"); assertStoredValuesWithProjection(contactUri, values); } public void testStatusUpdateWithTimestamp() { long rawContactId = createRawContact(); insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim"); insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "gtalk"); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", 0, "Offline", 80, StatusUpdates.CAPABILITY_HAS_CAMERA, false); insertStatusUpdate(Im.PROTOCOL_AIM, null, "aim", 0, "Available", 100, StatusUpdates.CAPABILITY_HAS_CAMERA, false); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "gtalk", 0, "Busy", 90, StatusUpdates.CAPABILITY_HAS_CAMERA, false); // Should return the latest status ContentValues values = new ContentValues(); values.put(Contacts.CONTACT_STATUS_TIMESTAMP, 100); values.put(Contacts.CONTACT_STATUS, "Available"); assertStoredValuesWithProjection(contactUri, values); } private void assertStatusUpdate(Cursor c, int protocol, String customProtocol, int presence, String status) { ContentValues values = new ContentValues(); values.put(StatusUpdates.PROTOCOL, protocol); values.put(StatusUpdates.CUSTOM_PROTOCOL, customProtocol); values.put(StatusUpdates.PRESENCE, presence); values.put(StatusUpdates.STATUS, status); assertCursorValues(c, values); } // Stream item query test cases. public void testQueryStreamItemsByRawContactId() { long rawContactId = createRawContact(mAccount); ContentValues values = buildGenericStreamItemValues(); insertStreamItem(rawContactId, values, mAccount); assertStoredValues( Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), values); } public void testQueryStreamItemsByContactId() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); ContentValues values = buildGenericStreamItemValues(); insertStreamItem(rawContactId, values, null); assertStoredValues( Uri.withAppendedPath( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.StreamItems.CONTENT_DIRECTORY), values); } public void testQueryStreamItemsByLookupKey() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); String lookupKey = queryLookupKey(contactId); ContentValues values = buildGenericStreamItemValues(); insertStreamItem(rawContactId, values, null); assertStoredValues( Uri.withAppendedPath( Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), Contacts.StreamItems.CONTENT_DIRECTORY), values); } public void testQueryStreamItemsByLookupKeyAndContactId() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); String lookupKey = queryLookupKey(contactId); ContentValues values = buildGenericStreamItemValues(); insertStreamItem(rawContactId, values, null); assertStoredValues( Uri.withAppendedPath( ContentUris.withAppendedId( Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId), Contacts.StreamItems.CONTENT_DIRECTORY), values); } public void testQueryStreamItems() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); insertStreamItem(rawContactId, values, null); assertStoredValues(StreamItems.CONTENT_URI, values); } public void testQueryStreamItemsWithSelection() { long rawContactId = createRawContact(); ContentValues firstValues = buildGenericStreamItemValues(); insertStreamItem(rawContactId, firstValues, null); ContentValues secondValues = buildGenericStreamItemValues(); secondValues.put(StreamItems.TEXT, "Goodbye world"); insertStreamItem(rawContactId, secondValues, null); // Select only the first stream item. assertStoredValues(StreamItems.CONTENT_URI, StreamItems.TEXT + "=?", new String[]{"Hello world"}, firstValues); // Select only the second stream item. assertStoredValues(StreamItems.CONTENT_URI, StreamItems.TEXT + "=?", new String[]{"Goodbye world"}, secondValues); } public void testQueryStreamItemById() { long rawContactId = createRawContact(); ContentValues firstValues = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, firstValues, null); long firstStreamItemId = ContentUris.parseId(resultUri); ContentValues secondValues = buildGenericStreamItemValues(); secondValues.put(StreamItems.TEXT, "Goodbye world"); resultUri = insertStreamItem(rawContactId, secondValues, null); long secondStreamItemId = ContentUris.parseId(resultUri); // Select only the first stream item. assertStoredValues(ContentUris.withAppendedId(StreamItems.CONTENT_URI, firstStreamItemId), firstValues); // Select only the second stream item. assertStoredValues(ContentUris.withAppendedId(StreamItems.CONTENT_URI, secondStreamItemId), secondValues); } // Stream item photo insertion + query test cases. public void testQueryStreamItemPhotoWithSelection() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); long streamItemId = ContentUris.parseId(resultUri); ContentValues photo1Values = buildGenericStreamItemPhotoValues(1); insertStreamItemPhoto(streamItemId, photo1Values, null); photo1Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. ContentValues photo2Values = buildGenericStreamItemPhotoValues(2); insertStreamItemPhoto(streamItemId, photo2Values, null); // Select only the first photo. assertStoredValues(StreamItems.CONTENT_PHOTO_URI, StreamItemPhotos.SORT_INDEX + "=?", new String[]{"1"}, photo1Values); } public void testQueryStreamItemPhotoByStreamItemId() { long rawContactId = createRawContact(); // Insert a first stream item. ContentValues firstValues = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, firstValues, null); long firstStreamItemId = ContentUris.parseId(resultUri); // Insert a second stream item. ContentValues secondValues = buildGenericStreamItemValues(); resultUri = insertStreamItem(rawContactId, secondValues, null); long secondStreamItemId = ContentUris.parseId(resultUri); // Add a photo to the first stream item. ContentValues photo1Values = buildGenericStreamItemPhotoValues(1); insertStreamItemPhoto(firstStreamItemId, photo1Values, null); photo1Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Add a photo to the second stream item. ContentValues photo2Values = buildGenericStreamItemPhotoValues(1); photo2Values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( R.drawable.nebula, PhotoSize.ORIGINAL)); insertStreamItemPhoto(secondStreamItemId, photo2Values, null); photo2Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Select only the photos from the second stream item. assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, secondStreamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), photo2Values); } public void testQueryStreamItemPhotoByStreamItemPhotoId() { long rawContactId = createRawContact(); // Insert a first stream item. ContentValues firstValues = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, firstValues, null); long firstStreamItemId = ContentUris.parseId(resultUri); // Insert a second stream item. ContentValues secondValues = buildGenericStreamItemValues(); resultUri = insertStreamItem(rawContactId, secondValues, null); long secondStreamItemId = ContentUris.parseId(resultUri); // Add a photo to the first stream item. ContentValues photo1Values = buildGenericStreamItemPhotoValues(1); resultUri = insertStreamItemPhoto(firstStreamItemId, photo1Values, null); long firstPhotoId = ContentUris.parseId(resultUri); photo1Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Add a photo to the second stream item. ContentValues photo2Values = buildGenericStreamItemPhotoValues(1); photo2Values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( R.drawable.galaxy, PhotoSize.ORIGINAL)); resultUri = insertStreamItemPhoto(secondStreamItemId, photo2Values, null); long secondPhotoId = ContentUris.parseId(resultUri); photo2Values.remove(StreamItemPhotos.PHOTO); // Removed during processing. // Select the first photo. assertStoredValues(ContentUris.withAppendedId( Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, firstStreamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), firstPhotoId), photo1Values); // Select the second photo. assertStoredValues(ContentUris.withAppendedId( Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, secondStreamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), secondPhotoId), photo2Values); } // Stream item insertion test cases. public void testInsertStreamItemInProfileRequiresWriteProfileAccess() { long profileRawContactId = createBasicProfileContact(new ContentValues()); // With our (default) write profile permission, we should be able to insert a stream item. ContentValues values = buildGenericStreamItemValues(); insertStreamItem(profileRawContactId, values, null); // Now take away write profile permission. mActor.removePermissions("android.permission.WRITE_PROFILE"); // Try inserting another stream item. try { insertStreamItem(profileRawContactId, values, null); fail("Should require WRITE_PROFILE access to insert a stream item in the profile."); } catch (SecurityException expected) { // Trying to insert a stream item in the profile without WRITE_PROFILE permission // should fail. } } public void testInsertStreamItemWithContentValues() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); values.put(StreamItems.RAW_CONTACT_ID, rawContactId); mResolver.insert(StreamItems.CONTENT_URI, values); assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), values); } public void testInsertStreamItemOverLimit() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); values.put(StreamItems.RAW_CONTACT_ID, rawContactId); List streamItemIds = Lists.newArrayList(); // Insert MAX + 1 stream items. long baseTime = System.currentTimeMillis(); for (int i = 0; i < 6; i++) { values.put(StreamItems.TIMESTAMP, baseTime + i); Uri resultUri = mResolver.insert(StreamItems.CONTENT_URI, values); streamItemIds.add(ContentUris.parseId(resultUri)); } Long doomedStreamItemId = streamItemIds.get(0); // There should only be MAX items. The oldest one should have been cleaned up. Cursor c = mResolver.query( Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), new String[]{StreamItems._ID}, null, null, null); try { while(c.moveToNext()) { long streamItemId = c.getLong(0); streamItemIds.remove(streamItemId); } } finally { c.close(); } assertEquals(1, streamItemIds.size()); assertEquals(doomedStreamItemId, streamItemIds.get(0)); } public void testInsertStreamItemOlderThanOldestInLimit() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); values.put(StreamItems.RAW_CONTACT_ID, rawContactId); // Insert MAX stream items. long baseTime = System.currentTimeMillis(); for (int i = 0; i < 5; i++) { values.put(StreamItems.TIMESTAMP, baseTime + i); Uri resultUri = mResolver.insert(StreamItems.CONTENT_URI, values); assertNotSame("Expected non-0 stream item ID to be inserted", 0L, ContentUris.parseId(resultUri)); } // Now try to insert a stream item that's older. It should be deleted immediately // and return an ID of 0. values.put(StreamItems.TIMESTAMP, baseTime - 1); Uri resultUri = mResolver.insert(StreamItems.CONTENT_URI, values); assertEquals(0L, ContentUris.parseId(resultUri)); } // Stream item photo insertion test cases. public void testInsertStreamItemsAndPhotosInBatch() throws Exception { long rawContactId = createRawContact(); ContentValues streamItemValues = buildGenericStreamItemValues(); ContentValues streamItemPhotoValues = buildGenericStreamItemPhotoValues(0); ArrayList ops = Lists.newArrayList(); ops.add(ContentProviderOperation.newInsert( Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY)) .withValues(streamItemValues).build()); for (int i = 0; i < 5; i++) { streamItemPhotoValues.put(StreamItemPhotos.SORT_INDEX, i); ops.add(ContentProviderOperation.newInsert(StreamItems.CONTENT_PHOTO_URI) .withValues(streamItemPhotoValues) .withValueBackReference(StreamItemPhotos.STREAM_ITEM_ID, 0) .build()); } mResolver.applyBatch(ContactsContract.AUTHORITY, ops); // Check that all five photos were inserted under the raw contact. Cursor c = mResolver.query(StreamItems.CONTENT_URI, new String[]{StreamItems._ID}, StreamItems.RAW_CONTACT_ID + "=?", new String[]{String.valueOf(rawContactId)}, null); long streamItemId = 0; try { assertEquals(1, c.getCount()); c.moveToFirst(); streamItemId = c.getLong(0); } finally { c.close(); } c = mResolver.query(Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), new String[]{StreamItemPhotos._ID, StreamItemPhotos.PHOTO_URI}, null, null, null); try { assertEquals(5, c.getCount()); byte[] expectedPhotoBytes = loadPhotoFromResource( R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO); while (c.moveToNext()) { String photoUri = c.getString(1); EvenMoreAsserts.assertImageRawData(getContext(), expectedPhotoBytes, mResolver.openInputStream(Uri.parse(photoUri))); } } finally { c.close(); } } // Stream item update test cases. public void testUpdateStreamItemById() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); long streamItemId = ContentUris.parseId(resultUri); values.put(StreamItems.TEXT, "Goodbye world"); mResolver.update(ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), values, null, null); assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), values); } public void testUpdateStreamItemWithContentValues() { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); long streamItemId = ContentUris.parseId(resultUri); values.put(StreamItems._ID, streamItemId); values.put(StreamItems.TEXT, "Goodbye world"); mResolver.update(StreamItems.CONTENT_URI, values, null, null); assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), values); } // Stream item photo update test cases. public void testUpdateStreamItemPhotoById() throws IOException { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); long streamItemId = ContentUris.parseId(resultUri); ContentValues photoValues = buildGenericStreamItemPhotoValues(1); resultUri = insertStreamItemPhoto(streamItemId, photoValues, null); long streamItemPhotoId = ContentUris.parseId(resultUri); photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( R.drawable.nebula, PhotoSize.ORIGINAL)); Uri photoUri = ContentUris.withAppendedId( Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), streamItemPhotoId); mResolver.update(photoUri, photoValues, null, null); photoValues.remove(StreamItemPhotos.PHOTO); // Removed during processing. assertStoredValues(photoUri, photoValues); // Check that the photo stored is the expected one. String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(Uri.parse(displayPhotoUri))); } public void testUpdateStreamItemPhotoWithContentValues() throws IOException { long rawContactId = createRawContact(); ContentValues values = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, values, null); long streamItemId = ContentUris.parseId(resultUri); ContentValues photoValues = buildGenericStreamItemPhotoValues(1); resultUri = insertStreamItemPhoto(streamItemId, photoValues, null); long streamItemPhotoId = ContentUris.parseId(resultUri); photoValues.put(StreamItemPhotos._ID, streamItemPhotoId); photoValues.put(StreamItemPhotos.PHOTO, loadPhotoFromResource( R.drawable.nebula, PhotoSize.ORIGINAL)); Uri photoUri = Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY); mResolver.update(photoUri, photoValues, null, null); photoValues.remove(StreamItemPhotos.PHOTO); // Removed during processing. assertStoredValues(photoUri, photoValues); // Check that the photo stored is the expected one. String displayPhotoUri = getStoredValue(photoUri, StreamItemPhotos.PHOTO_URI); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.nebula, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(Uri.parse(displayPhotoUri))); } // Stream item deletion test cases. public void testDeleteStreamItemById() { long rawContactId = createRawContact(); ContentValues firstValues = buildGenericStreamItemValues(); Uri resultUri = insertStreamItem(rawContactId, firstValues, null); long firstStreamItemId = ContentUris.parseId(resultUri); ContentValues secondValues = buildGenericStreamItemValues(); secondValues.put(StreamItems.TEXT, "Goodbye world"); insertStreamItem(rawContactId, secondValues, null); // Delete the first stream item. mResolver.delete(ContentUris.withAppendedId(StreamItems.CONTENT_URI, firstStreamItemId), null, null); // Check that only the second item remains. assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), secondValues); } public void testDeleteStreamItemWithSelection() { long rawContactId = createRawContact(); ContentValues firstValues = buildGenericStreamItemValues(); insertStreamItem(rawContactId, firstValues, null); ContentValues secondValues = buildGenericStreamItemValues(); secondValues.put(StreamItems.TEXT, "Goodbye world"); insertStreamItem(rawContactId, secondValues, null); // Delete the first stream item with a custom selection. mResolver.delete(StreamItems.CONTENT_URI, StreamItems.TEXT + "=?", new String[]{"Hello world"}); // Check that only the second item remains. assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), secondValues); } // Stream item photo deletion test cases. public void testDeleteStreamItemPhotoById() { long rawContactId = createRawContact(); long streamItemId = ContentUris.parseId( insertStreamItem(rawContactId, buildGenericStreamItemValues(), null)); long streamItemPhotoId = ContentUris.parseId( insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), null)); mResolver.delete( ContentUris.withAppendedId( Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), streamItemPhotoId), null, null); Cursor c = mResolver.query(StreamItems.CONTENT_PHOTO_URI, new String[]{StreamItemPhotos._ID}, StreamItemPhotos.STREAM_ITEM_ID + "=?", new String[]{String.valueOf(streamItemId)}, null); try { assertEquals("Expected photo to be deleted.", 0, c.getCount()); } finally { c.close(); } } public void testDeleteStreamItemPhotoWithSelection() { long rawContactId = createRawContact(); long streamItemId = ContentUris.parseId( insertStreamItem(rawContactId, buildGenericStreamItemValues(), null)); ContentValues firstPhotoValues = buildGenericStreamItemPhotoValues(0); ContentValues secondPhotoValues = buildGenericStreamItemPhotoValues(1); insertStreamItemPhoto(streamItemId, firstPhotoValues, null); firstPhotoValues.remove(StreamItemPhotos.PHOTO); // Removed while processing. insertStreamItemPhoto(streamItemId, secondPhotoValues, null); Uri photoUri = Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY); mResolver.delete(photoUri, StreamItemPhotos.SORT_INDEX + "=1", null); assertStoredValues(photoUri, firstPhotoValues); } public void testDeleteStreamItemsWhenRawContactDeleted() { long rawContactId = createRawContact(mAccount); Uri streamItemUri = insertStreamItem(rawContactId, buildGenericStreamItemValues(), mAccount); Uri streamItemPhotoUri = insertStreamItemPhoto(ContentUris.parseId(streamItemUri), buildGenericStreamItemPhotoValues(0), mAccount); mResolver.delete(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), null, null); ContentValues[] emptyValues = new ContentValues[0]; // The stream item and its photo should be gone. assertStoredValues(streamItemUri, emptyValues); assertStoredValues(streamItemPhotoUri, emptyValues); } public void testQueryStreamItemLimit() { ContentValues values = new ContentValues(); values.put(StreamItems.MAX_ITEMS, 5); assertStoredValues(StreamItems.CONTENT_LIMIT_URI, values); } // Tests for inserting or updating stream items as a side-effect of making status updates // (forward-compatibility of status updates into the new social stream API). public void testStreamItemInsertedOnStatusUpdate() { // This method of creating a raw contact automatically inserts a status update with // the status message "hacking". ContentValues values = new ContentValues(); long rawContactId = createRawContact(values, "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE); ContentValues expectedValues = new ContentValues(); expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId); expectedValues.put(StreamItems.TEXT, "hacking"); assertStoredValues(RawContacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(rawContactId)) .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(), expectedValues); } public void testStreamItemInsertedOnStatusUpdate_HtmlQuoting() { // This method of creating a raw contact automatically inserts a status update with // the status message "hacking". ContentValues values = new ContentValues(); long rawContactId = createRawContact(values, "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_VOICE); // Insert a new status update for the raw contact. insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com", StatusUpdates.INVISIBLE, "& test '", StatusUpdates.CAPABILITY_HAS_VOICE); ContentValues expectedValues = new ContentValues(); expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId); expectedValues.put(StreamItems.TEXT, "& <b> test &#39;"); assertStoredValues(RawContacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(rawContactId)) .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(), expectedValues); } public void testStreamItemUpdatedOnSecondStatusUpdate() { // This method of creating a raw contact automatically inserts a status update with // the status message "hacking". ContentValues values = new ContentValues(); int chatMode = StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO | StatusUpdates.CAPABILITY_HAS_VOICE; long rawContactId = createRawContact(values, "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, chatMode); // Insert a new status update for the raw contact. insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com", StatusUpdates.INVISIBLE, "finished hacking", chatMode); ContentValues expectedValues = new ContentValues(); expectedValues.put(StreamItems.RAW_CONTACT_ID, rawContactId); expectedValues.put(StreamItems.TEXT, "finished hacking"); assertStoredValues(RawContacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(rawContactId)) .appendPath(RawContacts.StreamItems.CONTENT_DIRECTORY).build(), expectedValues); } public void testStreamItemReadRequiresReadSocialStreamPermission() { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); String lookupKey = queryLookupKey(contactId); long streamItemId = ContentUris.parseId( insertStreamItem(rawContactId, buildGenericStreamItemValues(), null)); mActor.removePermissions("android.permission.READ_SOCIAL_STREAM"); // Try selecting the stream item in various ways. expectSecurityException( "Querying stream items by contact ID requires social stream read permission", Uri.withAppendedPath( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.StreamItems.CONTENT_DIRECTORY), null, null, null, null); expectSecurityException( "Querying stream items by lookup key requires social stream read permission", Contacts.CONTENT_LOOKUP_URI.buildUpon().appendPath(lookupKey) .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(), null, null, null, null); expectSecurityException( "Querying stream items by lookup key and ID requires social stream read permission", Uri.withAppendedPath(Contacts.getLookupUri(contactId, lookupKey), Contacts.StreamItems.CONTENT_DIRECTORY), null, null, null, null); expectSecurityException( "Querying stream items by raw contact ID requires social stream read permission", Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), null, null, null, null); expectSecurityException( "Querying stream items by raw contact ID and stream item ID requires social " + "stream read permission", ContentUris.withAppendedId( Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), streamItemId), null, null, null, null); expectSecurityException( "Querying all stream items requires social stream read permission", StreamItems.CONTENT_URI, null, null, null, null); expectSecurityException( "Querying stream item by ID requires social stream read permission", ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), null, null, null, null); } public void testStreamItemPhotoReadRequiresReadSocialStreamPermission() { long rawContactId = createRawContact(); long streamItemId = ContentUris.parseId( insertStreamItem(rawContactId, buildGenericStreamItemValues(), null)); long streamItemPhotoId = ContentUris.parseId( insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), null)); mActor.removePermissions("android.permission.READ_SOCIAL_STREAM"); // Try selecting the stream item photo in various ways. expectSecurityException( "Querying all stream item photos requires social stream read permission", StreamItems.CONTENT_URI.buildUpon() .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY).build(), null, null, null, null); expectSecurityException( "Querying all stream item photos requires social stream read permission", StreamItems.CONTENT_URI.buildUpon() .appendPath(String.valueOf(streamItemId)) .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY) .appendPath(String.valueOf(streamItemPhotoId)).build(), null, null, null, null); } public void testStreamItemModificationRequiresWriteSocialStreamPermission() { long rawContactId = createRawContact(); long streamItemId = ContentUris.parseId( insertStreamItem(rawContactId, buildGenericStreamItemValues(), null)); mActor.removePermissions("android.permission.WRITE_SOCIAL_STREAM"); try { insertStreamItem(rawContactId, buildGenericStreamItemValues(), null); fail("Should not be able to insert to stream without write social stream permission"); } catch (SecurityException expected) { } try { ContentValues values = new ContentValues(); values.put(StreamItems.TEXT, "Goodbye world"); mResolver.update(ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), values, null, null); fail("Should not be able to update stream without write social stream permission"); } catch (SecurityException expected) { } try { mResolver.delete(ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), null, null); fail("Should not be able to delete from stream without write social stream permission"); } catch (SecurityException expected) { } } public void testStreamItemPhotoModificationRequiresWriteSocialStreamPermission() { long rawContactId = createRawContact(); long streamItemId = ContentUris.parseId( insertStreamItem(rawContactId, buildGenericStreamItemValues(), null)); long streamItemPhotoId = ContentUris.parseId( insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(0), null)); mActor.removePermissions("android.permission.WRITE_SOCIAL_STREAM"); Uri photoUri = StreamItems.CONTENT_URI.buildUpon() .appendPath(String.valueOf(streamItemId)) .appendPath(StreamItems.StreamItemPhotos.CONTENT_DIRECTORY) .appendPath(String.valueOf(streamItemPhotoId)).build(); try { insertStreamItemPhoto(streamItemId, buildGenericStreamItemPhotoValues(1), null); fail("Should not be able to insert photos without write social stream permission"); } catch (SecurityException expected) { } try { ContentValues values = new ContentValues(); values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(R.drawable.galaxy, PhotoSize.ORIGINAL)); mResolver.update(photoUri, values, null, null); fail("Should not be able to update photos without write social stream permission"); } catch (SecurityException expected) { } try { mResolver.delete(photoUri, null, null); fail("Should not be able to delete photos without write social stream permission"); } catch (SecurityException expected) { } } public void testStatusUpdateDoesNotRequireReadOrWriteSocialStreamPermission() { int protocol1 = Im.PROTOCOL_GOOGLE_TALK; String handle1 = "test@gmail.com"; long rawContactId = createRawContact(); insertImHandle(rawContactId, protocol1, null, handle1); mActor.removePermissions("android.permission.READ_SOCIAL_STREAM"); mActor.removePermissions("android.permission.WRITE_SOCIAL_STREAM"); insertStatusUpdate(protocol1, null, handle1, StatusUpdates.AVAILABLE, "Green", StatusUpdates.CAPABILITY_HAS_CAMERA); mActor.addPermissions("android.permission.READ_SOCIAL_STREAM"); ContentValues expectedValues = new ContentValues(); expectedValues.put(StreamItems.TEXT, "Green"); assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.StreamItems.CONTENT_DIRECTORY), expectedValues); } private ContentValues buildGenericStreamItemValues() { ContentValues values = new ContentValues(); values.put(StreamItems.TEXT, "Hello world"); values.put(StreamItems.TIMESTAMP, System.currentTimeMillis()); values.put(StreamItems.COMMENTS, "Reshared by 123 others"); return values; } private ContentValues buildGenericStreamItemPhotoValues(int sortIndex) { ContentValues values = new ContentValues(); values.put(StreamItemPhotos.SORT_INDEX, sortIndex); values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.ORIGINAL)); return values; } public void testSingleStatusUpdateRowPerContact() { int protocol1 = Im.PROTOCOL_GOOGLE_TALK; String handle1 = "test@gmail.com"; long rawContactId1 = createRawContact(); insertImHandle(rawContactId1, protocol1, null, handle1); insertStatusUpdate(protocol1, null, handle1, StatusUpdates.AVAILABLE, "Green", StatusUpdates.CAPABILITY_HAS_CAMERA); insertStatusUpdate(protocol1, null, handle1, StatusUpdates.AWAY, "Yellow", StatusUpdates.CAPABILITY_HAS_CAMERA); insertStatusUpdate(protocol1, null, handle1, StatusUpdates.INVISIBLE, "Red", StatusUpdates.CAPABILITY_HAS_CAMERA); Cursor c = queryContact(queryContactId(rawContactId1), new String[] {Contacts.CONTACT_PRESENCE, Contacts.CONTACT_STATUS}); assertEquals(1, c.getCount()); c.moveToFirst(); assertEquals(StatusUpdates.INVISIBLE, c.getInt(0)); assertEquals("Red", c.getString(1)); c.close(); } private void updateSendToVoicemailAndRingtone(long contactId, boolean sendToVoicemail, String ringtone) { ContentValues values = new ContentValues(); values.put(Contacts.SEND_TO_VOICEMAIL, sendToVoicemail); if (ringtone != null) { values.put(Contacts.CUSTOM_RINGTONE, ringtone); } final Uri uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); int count = mResolver.update(uri, values, null, null); assertEquals(1, count); } private void updateSendToVoicemailAndRingtoneWithSelection(long contactId, boolean sendToVoicemail, String ringtone) { ContentValues values = new ContentValues(); values.put(Contacts.SEND_TO_VOICEMAIL, sendToVoicemail); if (ringtone != null) { values.put(Contacts.CUSTOM_RINGTONE, ringtone); } int count = mResolver.update(Contacts.CONTENT_URI, values, Contacts._ID + "=" + contactId, null); assertEquals(1, count); } private void assertSendToVoicemailAndRingtone(long contactId, boolean expectedSendToVoicemail, String expectedRingtone) { Cursor c = queryContact(contactId); assertTrue(c.moveToNext()); int sendToVoicemail = c.getInt(c.getColumnIndex(Contacts.SEND_TO_VOICEMAIL)); assertEquals(expectedSendToVoicemail ? 1 : 0, sendToVoicemail); String ringtone = c.getString(c.getColumnIndex(Contacts.CUSTOM_RINGTONE)); if (expectedRingtone == null) { assertNull(ringtone); } else { assertTrue(ArrayUtils.contains(expectedRingtone.split(","), ringtone)); } c.close(); } public void testContactVisibilityUpdateOnMembershipChange() { long rawContactId = createRawContact(mAccount); assertVisibility(rawContactId, "0"); long visibleGroupId = createGroup(mAccount, "123", "Visible", 1); long invisibleGroupId = createGroup(mAccount, "567", "Invisible", 0); Uri membership1 = insertGroupMembership(rawContactId, visibleGroupId); assertVisibility(rawContactId, "1"); Uri membership2 = insertGroupMembership(rawContactId, invisibleGroupId); assertVisibility(rawContactId, "1"); mResolver.delete(membership1, null, null); assertVisibility(rawContactId, "0"); ContentValues values = new ContentValues(); values.put(GroupMembership.GROUP_ROW_ID, visibleGroupId); mResolver.update(membership2, values, null, null); assertVisibility(rawContactId, "1"); } private void assertVisibility(long rawContactId, String expectedValue) { assertStoredValue(Contacts.CONTENT_URI, Contacts._ID + "=" + queryContactId(rawContactId), null, Contacts.IN_VISIBLE_GROUP, expectedValue); } public void testSupplyingBothValuesAndParameters() throws Exception { Account account = new Account("account 1", "type%/:1"); Uri uri = ContactsContract.Groups.CONTENT_URI.buildUpon() .appendQueryParameter(ContactsContract.Groups.ACCOUNT_NAME, account.name) .appendQueryParameter(ContactsContract.Groups.ACCOUNT_TYPE, account.type) .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") .build(); ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(uri); builder.withValue(ContactsContract.Groups.ACCOUNT_TYPE, account.type); builder.withValue(ContactsContract.Groups.ACCOUNT_NAME, account.name); builder.withValue(ContactsContract.Groups.SYSTEM_ID, "some id"); builder.withValue(ContactsContract.Groups.TITLE, "some name"); builder.withValue(ContactsContract.Groups.GROUP_VISIBLE, 1); mResolver.applyBatch(ContactsContract.AUTHORITY, Lists.newArrayList(builder.build())); builder = ContentProviderOperation.newInsert(uri); builder.withValue(ContactsContract.Groups.ACCOUNT_TYPE, account.type + "diff"); builder.withValue(ContactsContract.Groups.ACCOUNT_NAME, account.name); builder.withValue(ContactsContract.Groups.SYSTEM_ID, "some other id"); builder.withValue(ContactsContract.Groups.TITLE, "some other name"); builder.withValue(ContactsContract.Groups.GROUP_VISIBLE, 1); try { mResolver.applyBatch(ContactsContract.AUTHORITY, Lists.newArrayList(builder.build())); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { // Expected } } public void testContentEntityIterator() { // create multiple contacts and check that the selected ones are returned long id; long groupId1 = createGroup(mAccount, "gsid1", "title1"); long groupId2 = createGroup(mAccount, "gsid2", "title2"); id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c0"); insertGroupMembership(id, "gsid1"); insertEmail(id, "c0@email.com"); insertPhoneNumber(id, "5551212c0"); long c1 = id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c1"); Uri id_1_0 = insertGroupMembership(id, "gsid1"); Uri id_1_1 = insertGroupMembership(id, "gsid2"); Uri id_1_2 = insertEmail(id, "c1@email.com"); Uri id_1_3 = insertPhoneNumber(id, "5551212c1"); long c2 = id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c2"); Uri id_2_0 = insertGroupMembership(id, "gsid1"); Uri id_2_1 = insertEmail(id, "c2@email.com"); Uri id_2_2 = insertPhoneNumber(id, "5551212c2"); long c3 = id = createRawContact(mAccount, RawContacts.SOURCE_ID, "c3"); Uri id_3_0 = insertGroupMembership(id, groupId2); Uri id_3_1 = insertEmail(id, "c3@email.com"); Uri id_3_2 = insertPhoneNumber(id, "5551212c3"); EntityIterator iterator = RawContacts.newEntityIterator(mResolver.query( maybeAddAccountQueryParameters(RawContactsEntity.CONTENT_URI, mAccount), null, RawContacts.SOURCE_ID + " in ('c1', 'c2', 'c3')", null, null)); Entity entity; ContentValues[] subValues; entity = iterator.next(); assertEquals(c1, (long) entity.getEntityValues().getAsLong(RawContacts._ID)); subValues = asSortedContentValuesArray(entity.getSubValues()); assertEquals(4, subValues.length); assertDataRow(subValues[0], GroupMembership.CONTENT_ITEM_TYPE, Data._ID, id_1_0, GroupMembership.GROUP_ROW_ID, groupId1, GroupMembership.GROUP_SOURCE_ID, "gsid1"); assertDataRow(subValues[1], GroupMembership.CONTENT_ITEM_TYPE, Data._ID, id_1_1, GroupMembership.GROUP_ROW_ID, groupId2, GroupMembership.GROUP_SOURCE_ID, "gsid2"); assertDataRow(subValues[2], Email.CONTENT_ITEM_TYPE, Data._ID, id_1_2, Email.DATA, "c1@email.com"); assertDataRow(subValues[3], Phone.CONTENT_ITEM_TYPE, Data._ID, id_1_3, Email.DATA, "5551212c1"); entity = iterator.next(); assertEquals(c2, (long) entity.getEntityValues().getAsLong(RawContacts._ID)); subValues = asSortedContentValuesArray(entity.getSubValues()); assertEquals(3, subValues.length); assertDataRow(subValues[0], GroupMembership.CONTENT_ITEM_TYPE, Data._ID, id_2_0, GroupMembership.GROUP_ROW_ID, groupId1, GroupMembership.GROUP_SOURCE_ID, "gsid1"); assertDataRow(subValues[1], Email.CONTENT_ITEM_TYPE, Data._ID, id_2_1, Email.DATA, "c2@email.com"); assertDataRow(subValues[2], Phone.CONTENT_ITEM_TYPE, Data._ID, id_2_2, Email.DATA, "5551212c2"); entity = iterator.next(); assertEquals(c3, (long) entity.getEntityValues().getAsLong(RawContacts._ID)); subValues = asSortedContentValuesArray(entity.getSubValues()); assertEquals(3, subValues.length); assertDataRow(subValues[0], GroupMembership.CONTENT_ITEM_TYPE, Data._ID, id_3_0, GroupMembership.GROUP_ROW_ID, groupId2, GroupMembership.GROUP_SOURCE_ID, "gsid2"); assertDataRow(subValues[1], Email.CONTENT_ITEM_TYPE, Data._ID, id_3_1, Email.DATA, "c3@email.com"); assertDataRow(subValues[2], Phone.CONTENT_ITEM_TYPE, Data._ID, id_3_2, Email.DATA, "5551212c3"); assertFalse(iterator.hasNext()); iterator.close(); } public void testDataCreateUpdateDeleteByMimeType() throws Exception { long rawContactId = createRawContact(); ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, "testmimetype"); values.put(Data.RES_PACKAGE, "oldpackage"); values.put(Data.IS_PRIMARY, 1); values.put(Data.IS_SUPER_PRIMARY, 1); values.put(Data.DATA1, "old1"); values.put(Data.DATA2, "old2"); values.put(Data.DATA3, "old3"); values.put(Data.DATA4, "old4"); values.put(Data.DATA5, "old5"); values.put(Data.DATA6, "old6"); values.put(Data.DATA7, "old7"); values.put(Data.DATA8, "old8"); values.put(Data.DATA9, "old9"); values.put(Data.DATA10, "old10"); values.put(Data.DATA11, "old11"); values.put(Data.DATA12, "old12"); values.put(Data.DATA13, "old13"); values.put(Data.DATA14, "old14"); values.put(Data.DATA15, "old15"); Uri uri = mResolver.insert(Data.CONTENT_URI, values); assertStoredValues(uri, values); assertNetworkNotified(true); values.clear(); values.put(Data.RES_PACKAGE, "newpackage"); values.put(Data.IS_PRIMARY, 0); values.put(Data.IS_SUPER_PRIMARY, 0); values.put(Data.DATA1, "new1"); values.put(Data.DATA2, "new2"); values.put(Data.DATA3, "new3"); values.put(Data.DATA4, "new4"); values.put(Data.DATA5, "new5"); values.put(Data.DATA6, "new6"); values.put(Data.DATA7, "new7"); values.put(Data.DATA8, "new8"); values.put(Data.DATA9, "new9"); values.put(Data.DATA10, "new10"); values.put(Data.DATA11, "new11"); values.put(Data.DATA12, "new12"); values.put(Data.DATA13, "new13"); values.put(Data.DATA14, "new14"); values.put(Data.DATA15, "new15"); mResolver.update(Data.CONTENT_URI, values, Data.RAW_CONTACT_ID + "=" + rawContactId + " AND " + Data.MIMETYPE + "='testmimetype'", null); assertNetworkNotified(true); assertStoredValues(uri, values); int count = mResolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=" + rawContactId + " AND " + Data.MIMETYPE + "='testmimetype'", null); assertEquals(1, count); assertEquals(0, getCount(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=" + rawContactId + " AND " + Data.MIMETYPE + "='testmimetype'", null)); assertNetworkNotified(true); } public void testRawContactQuery() { Account account1 = new Account("a", "b"); Account account2 = new Account("c", "d"); long rawContactId1 = createRawContact(account1); long rawContactId2 = createRawContact(account2); Uri uri1 = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account1); Uri uri2 = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, account2); assertEquals(1, getCount(uri1, null, null)); assertEquals(1, getCount(uri2, null, null)); assertStoredValue(uri1, RawContacts._ID, rawContactId1) ; assertStoredValue(uri2, RawContacts._ID, rawContactId2) ; Uri rowUri1 = ContentUris.withAppendedId(uri1, rawContactId1); Uri rowUri2 = ContentUris.withAppendedId(uri2, rawContactId2); assertStoredValue(rowUri1, RawContacts._ID, rawContactId1) ; assertStoredValue(rowUri2, RawContacts._ID, rawContactId2) ; } public void testRawContactDeletion() { long rawContactId = createRawContact(mAccount); Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com"); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com", StatusUpdates.AVAILABLE, null, StatusUpdates.CAPABILITY_HAS_CAMERA); long contactId = queryContactId(rawContactId); assertEquals(1, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY), null, null)); assertEquals(1, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null)); mResolver.delete(uri, null, null); assertStoredValue(uri, RawContacts.DELETED, "1"); assertNetworkNotified(true); Uri permanentDeletionUri = setCallerIsSyncAdapter(uri, mAccount); mResolver.delete(permanentDeletionUri, null, null); assertEquals(0, getCount(uri, null, null)); assertEquals(0, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY), null, null)); assertEquals(0, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null)); assertEquals(0, getCount(Contacts.CONTENT_URI, Contacts._ID + "=" + contactId, null)); assertNetworkNotified(false); } public void testRawContactDeletionKeepingAggregateContact() { long rawContactId1 = createRawContactWithName(mAccount); long rawContactId2 = createRawContactWithName(mAccount); setAggregationException( AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); long contactId = queryContactId(rawContactId1); Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1); Uri permanentDeletionUri = setCallerIsSyncAdapter(uri, mAccount); mResolver.delete(permanentDeletionUri, null, null); assertEquals(0, getCount(uri, null, null)); assertEquals(1, getCount(Contacts.CONTENT_URI, Contacts._ID + "=" + contactId, null)); } public void testRawContactDeletion_byAccountParam() { long rawContactId = createRawContact(mAccount); Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com"); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com", StatusUpdates.AVAILABLE, null, StatusUpdates.CAPABILITY_HAS_CAMERA); assertEquals(1, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY), null, null)); assertEquals(1, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null)); // Do not delete if we are deleting with wrong account. Uri deleteWithWrongAccountUri = RawContacts.CONTENT_URI.buildUpon() .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, mAccountTwo.name) .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccountTwo.type) .build(); int numDeleted = mResolver.delete(deleteWithWrongAccountUri, null, null); assertEquals(0, numDeleted); assertStoredValue(uri, RawContacts.DELETED, "0"); // Delete if we are deleting with correct account. Uri deleteWithCorrectAccountUri = RawContacts.CONTENT_URI.buildUpon() .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, mAccount.name) .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccount.type) .build(); numDeleted = mResolver.delete(deleteWithCorrectAccountUri, null, null); assertEquals(1, numDeleted); assertStoredValue(uri, RawContacts.DELETED, "1"); } public void testRawContactDeletion_byAccountSelection() { long rawContactId = createRawContact(mAccount); Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); // Do not delete if we are deleting with wrong account. int numDeleted = mResolver.delete(RawContacts.CONTENT_URI, RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?", new String[] {mAccountTwo.name, mAccountTwo.type}); assertEquals(0, numDeleted); assertStoredValue(uri, RawContacts.DELETED, "0"); // Delete if we are deleting with correct account. numDeleted = mResolver.delete(RawContacts.CONTENT_URI, RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?", new String[] {mAccount.name, mAccount.type}); assertEquals(1, numDeleted); assertStoredValue(uri, RawContacts.DELETED, "1"); } /** * Test for {@link ContactsProvider2#stringToAccounts} and * {@link ContactsProvider2#accountsToString}. */ public void testAccountsToString() { final Set EXPECTED_0 = Sets.newHashSet(); final Set EXPECTED_1 = Sets.newHashSet(ACCOUNT_1); final Set EXPECTED_2 = Sets.newHashSet(ACCOUNT_2); final Set EXPECTED_1_2 = Sets.newHashSet(ACCOUNT_1, ACCOUNT_2); final Set ACTUAL_0 = Sets.newHashSet(); final Set ACTUAL_1 = Sets.newHashSet(ACCOUNT_1); final Set ACTUAL_2 = Sets.newHashSet(ACCOUNT_2); final Set ACTUAL_1_2 = Sets.newHashSet(ACCOUNT_2, ACCOUNT_1); assertTrue(EXPECTED_0.equals(accountsToStringToAccounts(ACTUAL_0))); assertFalse(EXPECTED_0.equals(accountsToStringToAccounts(ACTUAL_1))); assertFalse(EXPECTED_0.equals(accountsToStringToAccounts(ACTUAL_2))); assertFalse(EXPECTED_0.equals(accountsToStringToAccounts(ACTUAL_1_2))); assertFalse(EXPECTED_1.equals(accountsToStringToAccounts(ACTUAL_0))); assertTrue(EXPECTED_1.equals(accountsToStringToAccounts(ACTUAL_1))); assertFalse(EXPECTED_1.equals(accountsToStringToAccounts(ACTUAL_2))); assertFalse(EXPECTED_1.equals(accountsToStringToAccounts(ACTUAL_1_2))); assertFalse(EXPECTED_2.equals(accountsToStringToAccounts(ACTUAL_0))); assertFalse(EXPECTED_2.equals(accountsToStringToAccounts(ACTUAL_1))); assertTrue(EXPECTED_2.equals(accountsToStringToAccounts(ACTUAL_2))); assertFalse(EXPECTED_2.equals(accountsToStringToAccounts(ACTUAL_1_2))); assertFalse(EXPECTED_1_2.equals(accountsToStringToAccounts(ACTUAL_0))); assertFalse(EXPECTED_1_2.equals(accountsToStringToAccounts(ACTUAL_1))); assertFalse(EXPECTED_1_2.equals(accountsToStringToAccounts(ACTUAL_2))); assertTrue(EXPECTED_1_2.equals(accountsToStringToAccounts(ACTUAL_1_2))); try { ContactsProvider2.stringToAccounts("x"); fail("Didn't throw for malformed input"); } catch (IllegalArgumentException expected) { } } private static final Set accountsToStringToAccounts(Set accounts) { return ContactsProvider2.stringToAccounts(ContactsProvider2.accountsToString(accounts)); } /** * Test for {@link ContactsProvider2#haveAccountsChanged} and * {@link ContactsProvider2#saveAccounts}. */ public void testHaveAccountsChanged() { final ContactsProvider2 cp = (ContactsProvider2) getProvider(); final Account[] ACCOUNTS_0 = new Account[] {}; final Account[] ACCOUNTS_1 = new Account[] {ACCOUNT_1}; final Account[] ACCOUNTS_2 = new Account[] {ACCOUNT_2}; final Account[] ACCOUNTS_1_2 = new Account[] {ACCOUNT_1, ACCOUNT_2}; final Account[] ACCOUNTS_2_1 = new Account[] {ACCOUNT_2, ACCOUNT_1}; // Add ACCOUNT_1 assertTrue(cp.haveAccountsChanged(ACCOUNTS_1)); cp.saveAccounts(ACCOUNTS_1); assertFalse(cp.haveAccountsChanged(ACCOUNTS_1)); // Add ACCOUNT_2 assertTrue(cp.haveAccountsChanged(ACCOUNTS_1_2)); // (try with reverse order) assertTrue(cp.haveAccountsChanged(ACCOUNTS_2_1)); cp.saveAccounts(ACCOUNTS_1_2); assertFalse(cp.haveAccountsChanged(ACCOUNTS_1_2)); // (try with reverse order) assertFalse(cp.haveAccountsChanged(ACCOUNTS_2_1)); // Remove ACCOUNT_1 assertTrue(cp.haveAccountsChanged(ACCOUNTS_2)); cp.saveAccounts(ACCOUNTS_2); assertFalse(cp.haveAccountsChanged(ACCOUNTS_2)); // Remove ACCOUNT_2 assertTrue(cp.haveAccountsChanged(ACCOUNTS_0)); cp.saveAccounts(ACCOUNTS_0); assertFalse(cp.haveAccountsChanged(ACCOUNTS_0)); // Test with malformed DB property. final ContactsDatabaseHelper dbHelper = cp.getThreadActiveDatabaseHelperForTest(); dbHelper.setProperty(DbProperties.KNOWN_ACCOUNTS, "x"); // With malformed property the method always return true. assertTrue(cp.haveAccountsChanged(ACCOUNTS_0)); assertTrue(cp.haveAccountsChanged(ACCOUNTS_1)); } public void testAccountsUpdated() { // This is to ensure we do not delete contacts with null, null (account name, type) // accidentally. long rawContactId3 = createRawContactWithName("James", "Sullivan"); insertPhoneNumber(rawContactId3, "5234567890"); Uri rawContact3 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId3); assertEquals(1, getCount(RawContacts.CONTENT_URI, null, null)); ContactsProvider2 cp = (ContactsProvider2) getProvider(); mActor.setAccounts(new Account[]{mAccount, mAccountTwo}); cp.onAccountsUpdated(new Account[]{mAccount, mAccountTwo}); assertEquals(1, getCount(RawContacts.CONTENT_URI, null, null)); assertStoredValue(rawContact3, RawContacts.ACCOUNT_NAME, null); assertStoredValue(rawContact3, RawContacts.ACCOUNT_TYPE, null); long rawContactId1 = createRawContact(mAccount); insertEmail(rawContactId1, "account1@email.com"); long rawContactId2 = createRawContact(mAccountTwo); insertEmail(rawContactId2, "account2@email.com"); insertImHandle(rawContactId2, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com"); insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com", StatusUpdates.AVAILABLE, null, StatusUpdates.CAPABILITY_HAS_CAMERA); mActor.setAccounts(new Account[]{mAccount}); cp.onAccountsUpdated(new Account[]{mAccount}); assertEquals(2, getCount(RawContacts.CONTENT_URI, null, null)); assertEquals(0, getCount(StatusUpdates.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId2, null)); } public void testAccountDeletion() { Account readOnlyAccount = new Account("act", READ_ONLY_ACCOUNT_TYPE); ContactsProvider2 cp = (ContactsProvider2) getProvider(); mActor.setAccounts(new Account[]{readOnlyAccount, mAccount}); cp.onAccountsUpdated(new Account[]{readOnlyAccount, mAccount}); long rawContactId1 = createRawContactWithName("John", "Doe", readOnlyAccount); Uri photoUri1 = insertPhoto(rawContactId1); long rawContactId2 = createRawContactWithName("john", "doe", mAccount); Uri photoUri2 = insertPhoto(rawContactId2); storeValue(photoUri2, Photo.IS_SUPER_PRIMARY, "1"); assertAggregated(rawContactId1, rawContactId2); long contactId = queryContactId(rawContactId1); // The display name should come from the writable account assertStoredValue(Uri.withAppendedPath( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.Data.CONTENT_DIRECTORY), Contacts.DISPLAY_NAME, "john doe"); // The photo should be the one we marked as super-primary assertStoredValue(Contacts.CONTENT_URI, contactId, Contacts.PHOTO_ID, ContentUris.parseId(photoUri2)); mActor.setAccounts(new Account[]{readOnlyAccount}); // Remove the writable account cp.onAccountsUpdated(new Account[]{readOnlyAccount}); // The display name should come from the remaining account assertStoredValue(Uri.withAppendedPath( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.Data.CONTENT_DIRECTORY), Contacts.DISPLAY_NAME, "John Doe"); // The photo should be the remaining one assertStoredValue(Contacts.CONTENT_URI, contactId, Contacts.PHOTO_ID, ContentUris.parseId(photoUri1)); } public void testStreamItemsCleanedUpOnAccountRemoval() { Account doomedAccount = new Account("doom", "doom"); Account safeAccount = mAccount; ContactsProvider2 cp = (ContactsProvider2) getProvider(); mActor.setAccounts(new Account[]{doomedAccount, safeAccount}); cp.onAccountsUpdated(new Account[]{doomedAccount, safeAccount}); // Create a doomed raw contact, stream item, and photo. long doomedRawContactId = createRawContactWithName(doomedAccount); Uri doomedStreamItemUri = insertStreamItem(doomedRawContactId, buildGenericStreamItemValues(), doomedAccount); long doomedStreamItemId = ContentUris.parseId(doomedStreamItemUri); Uri doomedStreamItemPhotoUri = insertStreamItemPhoto( doomedStreamItemId, buildGenericStreamItemPhotoValues(0), doomedAccount); // Create a safe raw contact, stream item, and photo. long safeRawContactId = createRawContactWithName(safeAccount); Uri safeStreamItemUri = insertStreamItem(safeRawContactId, buildGenericStreamItemValues(), safeAccount); long safeStreamItemId = ContentUris.parseId(safeStreamItemUri); Uri safeStreamItemPhotoUri = insertStreamItemPhoto( safeStreamItemId, buildGenericStreamItemPhotoValues(0), safeAccount); long safeStreamItemPhotoId = ContentUris.parseId(safeStreamItemPhotoUri); // Remove the doomed account. mActor.setAccounts(new Account[]{safeAccount}); cp.onAccountsUpdated(new Account[]{safeAccount}); // Check that the doomed stuff has all been nuked. ContentValues[] noValues = new ContentValues[0]; assertStoredValues(ContentUris.withAppendedId(RawContacts.CONTENT_URI, doomedRawContactId), noValues); assertStoredValues(doomedStreamItemUri, noValues); assertStoredValues(doomedStreamItemPhotoUri, noValues); // Check that the safe stuff lives on. assertStoredValue(RawContacts.CONTENT_URI, safeRawContactId, RawContacts._ID, safeRawContactId); assertStoredValue(safeStreamItemUri, StreamItems._ID, safeStreamItemId); assertStoredValue(safeStreamItemPhotoUri, StreamItemPhotos._ID, safeStreamItemPhotoId); } public void testContactDeletion() { long rawContactId1 = createRawContactWithName("John", "Doe", ACCOUNT_1); long rawContactId2 = createRawContactWithName("John", "Doe", ACCOUNT_2); long contactId = queryContactId(rawContactId1); mResolver.delete(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), null, null); assertStoredValue(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1), RawContacts.DELETED, "1"); assertStoredValue(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2), RawContacts.DELETED, "1"); } public void testMarkAsDirtyParameter() { long rawContactId = createRawContact(mAccount); Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); Uri uri = insertStructuredName(rawContactId, "John", "Doe"); clearDirty(rawContactUri); Uri updateUri = setCallerIsSyncAdapter(uri, mAccount); ContentValues values = new ContentValues(); values.put(StructuredName.FAMILY_NAME, "Dough"); mResolver.update(updateUri, values, null, null); assertStoredValue(uri, StructuredName.FAMILY_NAME, "Dough"); assertDirty(rawContactUri, false); assertNetworkNotified(false); } public void testRawContactDirtyAndVersion() { final long rawContactId = createRawContact(mAccount); Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId); assertDirty(uri, false); long version = getVersion(uri); ContentValues values = new ContentValues(); values.put(ContactsContract.RawContacts.DIRTY, 0); values.put(ContactsContract.RawContacts.SEND_TO_VOICEMAIL, 1); values.put(ContactsContract.RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_IMMEDIATE); values.put(ContactsContract.RawContacts.STARRED, 1); assertEquals(1, mResolver.update(uri, values, null, null)); assertEquals(version, getVersion(uri)); assertDirty(uri, false); assertNetworkNotified(false); Uri emailUri = insertEmail(rawContactId, "goo@woo.com"); assertDirty(uri, true); assertNetworkNotified(true); ++version; assertEquals(version, getVersion(uri)); clearDirty(uri); values = new ContentValues(); values.put(Email.DATA, "goo@hoo.com"); mResolver.update(emailUri, values, null, null); assertDirty(uri, true); assertNetworkNotified(true); ++version; assertEquals(version, getVersion(uri)); clearDirty(uri); mResolver.delete(emailUri, null, null); assertDirty(uri, true); assertNetworkNotified(true); ++version; assertEquals(version, getVersion(uri)); } public void testRawContactClearDirty() { final long rawContactId = createRawContact(mAccount); Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId); long version = getVersion(uri); insertEmail(rawContactId, "goo@woo.com"); assertDirty(uri, true); version++; assertEquals(version, getVersion(uri)); clearDirty(uri); assertDirty(uri, false); assertEquals(version, getVersion(uri)); } public void testRawContactDeletionSetsDirty() { final long rawContactId = createRawContact(mAccount); Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId); long version = getVersion(uri); clearDirty(uri); assertDirty(uri, false); mResolver.delete(uri, null, null); assertStoredValue(uri, RawContacts.DELETED, "1"); assertDirty(uri, true); assertNetworkNotified(true); version++; assertEquals(version, getVersion(uri)); } public void testDeleteContactWithoutName() { Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, new ContentValues()); long rawContactId = ContentUris.parseId(rawContactUri); Uri phoneUri = insertPhoneNumber(rawContactId, "555-123-45678", true); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri); int numDeleted = mResolver.delete(lookupUri, null, null); assertEquals(1, numDeleted); } public void testDeleteContactWithoutAnyData() { Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, new ContentValues()); long rawContactId = ContentUris.parseId(rawContactUri); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri); int numDeleted = mResolver.delete(lookupUri, null, null); assertEquals(1, numDeleted); } public void testDeleteContactWithEscapedUri() { ContentValues values = new ContentValues(); values.put(RawContacts.SOURCE_ID, "!@#$%^&*()_+=-/.,<>?;'\":[]}{\\|`~"); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri); assertEquals(1, mResolver.delete(lookupUri, null, null)); } public void testQueryContactWithEscapedUri() { ContentValues values = new ContentValues(); values.put(RawContacts.SOURCE_ID, "!@#$%^&*()_+=-/.,<>?;'\":[]}{\\|`~"); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri); Cursor c = mResolver.query(lookupUri, null, null, null, ""); assertEquals(1, c.getCount()); c.close(); } public void testGetPhotoUri() { ContentValues values = new ContentValues(); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "John", "Doe"); long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal)); long photoFileId = getStoredLongValue(Data.CONTENT_URI, Data._ID + "=?", new String[]{String.valueOf(dataId)}, Photo.PHOTO_FILE_ID); String photoUri = ContentUris.withAppendedId(DisplayPhoto.CONTENT_URI, photoFileId) .toString(); assertStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)), Contacts.PHOTO_URI, photoUri); } public void testGetPhotoViaLookupUri() throws IOException { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); Uri lookupUri = Contacts.getLookupUri(mResolver, contactUri); String lookupKey = lookupUri.getPathSegments().get(2); insertPhoto(rawContactId, R.drawable.earth_small); byte[] thumbnail = loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL); // Two forms of lookup key URIs should be valid - one with the contact ID, one without. Uri photoLookupUriWithId = Uri.withAppendedPath(lookupUri, "photo"); Uri photoLookupUriWithoutId = Contacts.CONTENT_LOOKUP_URI.buildUpon() .appendPath(lookupKey).appendPath("photo").build(); // Try retrieving as a data record. ContentValues values = new ContentValues(); values.put(Photo.PHOTO, thumbnail); assertStoredValues(photoLookupUriWithId, values); assertStoredValues(photoLookupUriWithoutId, values); // Try opening as an input stream. EvenMoreAsserts.assertImageRawData(getContext(), thumbnail, mResolver.openInputStream(photoLookupUriWithId)); EvenMoreAsserts.assertImageRawData(getContext(), thumbnail, mResolver.openInputStream(photoLookupUriWithoutId)); } public void testInputStreamForPhoto() throws Exception { long rawContactId = createRawContact(); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); insertPhoto(rawContactId); Uri photoUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_URI)); Uri photoThumbnailUri = Uri.parse(getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI)); // Check the thumbnail. EvenMoreAsserts.assertImageRawData(getContext(), loadTestPhoto(PhotoSize.THUMBNAIL), mResolver.openInputStream(photoThumbnailUri)); // Then check the display photo. Note because we only inserted a small photo, but not a // display photo, this returns the thumbnail image itself, which was compressed at // the thumnail compression rate, which is why we compare to // loadTestPhoto(PhotoSize.THUMBNAIL) rather than loadTestPhoto(PhotoSize.DISPLAY_PHOTO) // here. // (In other words, loadTestPhoto(PhotoSize.DISPLAY_PHOTO) returns the same photo as // loadTestPhoto(PhotoSize.THUMBNAIL), except it's compressed at a lower compression rate.) EvenMoreAsserts.assertImageRawData(getContext(), loadTestPhoto(PhotoSize.THUMBNAIL), mResolver.openInputStream(photoUri)); } public void testSuperPrimaryPhoto() { long rawContactId1 = createRawContact(new Account("a", "a")); Uri photoUri1 = insertPhoto(rawContactId1, R.drawable.earth_normal); long photoId1 = ContentUris.parseId(photoUri1); long rawContactId2 = createRawContact(new Account("b", "b")); Uri photoUri2 = insertPhoto(rawContactId2, R.drawable.earth_normal); long photoId2 = ContentUris.parseId(photoUri2); setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId1)); long photoFileId1 = getStoredLongValue(Data.CONTENT_URI, Data._ID + "=?", new String[]{String.valueOf(photoId1)}, Photo.PHOTO_FILE_ID); String photoUri = ContentUris.withAppendedId(DisplayPhoto.CONTENT_URI, photoFileId1) .toString(); assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId1); assertStoredValue(contactUri, Contacts.PHOTO_URI, photoUri); setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, rawContactId1, rawContactId2); ContentValues values = new ContentValues(); values.put(Data.IS_SUPER_PRIMARY, 1); mResolver.update(photoUri2, values, null, null); setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId1)); assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId2); mResolver.update(photoUri1, values, null, null); assertStoredValue(contactUri, Contacts.PHOTO_ID, photoId1); } public void testUpdatePhoto() { ContentValues values = new ContentValues(); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); insertStructuredName(rawContactId, "John", "Doe"); Uri twigUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(rawContactId)), Contacts.Photo.CONTENT_DIRECTORY); values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); values.putNull(Photo.PHOTO); Uri dataUri = mResolver.insert(Data.CONTENT_URI, values); long photoId = ContentUris.parseId(dataUri); assertEquals(0, getCount(twigUri, null, null)); values.clear(); values.put(Photo.PHOTO, loadTestPhoto()); mResolver.update(dataUri, values, null, null); assertNetworkNotified(true); long twigId = getStoredLongValue(twigUri, Data._ID); assertEquals(photoId, twigId); } public void testUpdateRawContactDataPhoto() { // setup a contact with a null photo ContentValues values = new ContentValues(); Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values); long rawContactId = ContentUris.parseId(rawContactUri); // setup a photo values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); values.putNull(Photo.PHOTO); // try to do an update before insert should return count == 0 Uri dataUri = Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.Data.CONTENT_DIRECTORY); assertEquals(0, mResolver.update(dataUri, values, Data.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE})); mResolver.insert(Data.CONTENT_URI, values); // save a photo to the db values.clear(); values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); values.put(Photo.PHOTO, loadTestPhoto()); assertEquals(1, mResolver.update(dataUri, values, Data.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE})); // verify the photo Cursor storedPhoto = mResolver.query(dataUri, new String[] {Photo.PHOTO}, Data.MIMETYPE + "=?", new String[] {Photo.CONTENT_ITEM_TYPE}, null); storedPhoto.moveToFirst(); MoreAsserts.assertEquals(loadTestPhoto(PhotoSize.THUMBNAIL), storedPhoto.getBlob(0)); storedPhoto.close(); } public void testOpenDisplayPhotoForContactId() throws IOException { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); insertPhoto(rawContactId, R.drawable.earth_normal); Uri photoUri = Contacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(contactId)) .appendPath(Contacts.Photo.DISPLAY_PHOTO).build(); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(photoUri)); } public void testOpenDisplayPhotoForContactLookupKey() throws IOException { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); String lookupKey = queryLookupKey(contactId); insertPhoto(rawContactId, R.drawable.earth_normal); Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon() .appendPath(lookupKey) .appendPath(Contacts.Photo.DISPLAY_PHOTO).build(); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(photoUri)); } public void testOpenDisplayPhotoForContactLookupKeyAndId() throws IOException { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); String lookupKey = queryLookupKey(contactId); insertPhoto(rawContactId, R.drawable.earth_normal); Uri photoUri = Contacts.CONTENT_LOOKUP_URI.buildUpon() .appendPath(lookupKey) .appendPath(String.valueOf(contactId)) .appendPath(Contacts.Photo.DISPLAY_PHOTO).build(); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(photoUri)); } public void testOpenDisplayPhotoForRawContactId() throws IOException { long rawContactId = createRawContactWithName(); insertPhoto(rawContactId, R.drawable.earth_normal); Uri photoUri = RawContacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(rawContactId)) .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build(); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(photoUri)); } public void testOpenDisplayPhotoByPhotoUri() throws IOException { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); insertPhoto(rawContactId, R.drawable.earth_normal); // Get the photo URI out and check the content. String photoUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(Uri.parse(photoUri))); } public void testPhotoUriForDisplayPhoto() { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); // Photo being inserted is larger than a thumbnail, so it will be stored as a file. long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal)); String photoFileId = getStoredValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), Photo.PHOTO_FILE_ID); String photoUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); // Check that the photo URI differs from the thumbnail. String thumbnailUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_THUMBNAIL_URI); assertFalse(photoUri.equals(thumbnailUri)); // URI should be of the form display_photo/ID assertEquals(Uri.withAppendedPath(DisplayPhoto.CONTENT_URI, photoFileId).toString(), photoUri); } public void testPhotoUriForThumbnailPhoto() throws IOException { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); // Photo being inserted is a thumbnail, so it will only be stored in a BLOB. The photo URI // will fall back to the thumbnail URI. insertPhoto(rawContactId, R.drawable.earth_small); String photoUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); // Check that the photo URI is equal to the thumbnail URI. String thumbnailUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_THUMBNAIL_URI); assertEquals(photoUri, thumbnailUri); // URI should be of the form contacts/ID/photo assertEquals(Uri.withAppendedPath( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.Photo.CONTENT_DIRECTORY).toString(), photoUri); // Loading the photo URI content should get the thumbnail. EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL), mResolver.openInputStream(Uri.parse(photoUri))); } public void testWriteNewPhotoToAssetFile() throws Exception { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); // Load in a huge photo. final byte[] originalPhoto = loadPhotoFromResource( R.drawable.earth_huge, PhotoSize.ORIGINAL); // Write it out. final Uri writeablePhotoUri = RawContacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(rawContactId)) .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build(); writePhotoAsync(writeablePhotoUri, originalPhoto); // Check that the display photo and thumbnail have been set. String photoUri = null; for (int i = 0; i < 10 && photoUri == null; i++) { // Wait a tick for the photo processing to occur. Thread.sleep(100); photoUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); } assertFalse(TextUtils.isEmpty(photoUri)); String thumbnailUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_THUMBNAIL_URI); assertFalse(TextUtils.isEmpty(thumbnailUri)); assertNotSame(photoUri, thumbnailUri); // Check the content of the display photo and thumbnail. EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(Uri.parse(photoUri))); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL), mResolver.openInputStream(Uri.parse(thumbnailUri))); } public void testWriteUpdatedPhotoToAssetFile() throws Exception { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); // Insert a large photo first. insertPhoto(rawContactId, R.drawable.earth_large); String largeEarthPhotoUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); // Load in a huge photo. byte[] originalPhoto = loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL); // Write it out. Uri writeablePhotoUri = RawContacts.CONTENT_URI.buildUpon() .appendPath(String.valueOf(rawContactId)) .appendPath(RawContacts.DisplayPhoto.CONTENT_DIRECTORY).build(); writePhotoAsync(writeablePhotoUri, originalPhoto); // Allow a second for processing to occur. Thread.sleep(1000); // Check that the display photo URI has been modified. String hugeEarthPhotoUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_URI); assertFalse(hugeEarthPhotoUri.equals(largeEarthPhotoUri)); // Check the content of the display photo and thumbnail. String hugeEarthThumbnailUri = getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), Contacts.PHOTO_THUMBNAIL_URI); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.DISPLAY_PHOTO), mResolver.openInputStream(Uri.parse(hugeEarthPhotoUri))); EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.THUMBNAIL), mResolver.openInputStream(Uri.parse(hugeEarthThumbnailUri))); } private void writePhotoAsync(final Uri uri, final byte[] photoBytes) throws Exception { AsyncTask task = new AsyncTask() { @Override protected Object doInBackground(Object... params) { OutputStream os; try { os = mResolver.openOutputStream(uri, "rw"); os.write(photoBytes); os.close(); return null; } catch (IOException ioe) { throw new RuntimeException(ioe); } } }; task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[])null).get(); } public void testPhotoDimensionLimits() { ContentValues values = new ContentValues(); values.put(DisplayPhoto.DISPLAY_MAX_DIM, 256); values.put(DisplayPhoto.THUMBNAIL_MAX_DIM, 96); assertStoredValues(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, values); } public void testPhotoStoreCleanup() throws IOException { SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider; PhotoStore photoStore = provider.getPhotoStore(); // Trigger an initial cleanup so another one won't happen while we're running this test. provider.cleanupPhotoStore(); // Insert a couple of contacts with photos. long rawContactId1 = createRawContactWithName(); long contactId1 = queryContactId(rawContactId1); long dataId1 = ContentUris.parseId(insertPhoto(rawContactId1, R.drawable.earth_normal)); long photoFileId1 = getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId1), Photo.PHOTO_FILE_ID); long rawContactId2 = createRawContactWithName(); long contactId2 = queryContactId(rawContactId2); long dataId2 = ContentUris.parseId(insertPhoto(rawContactId2, R.drawable.earth_normal)); long photoFileId2 = getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId2), Photo.PHOTO_FILE_ID); // Update the second raw contact with a different photo. ContentValues values = new ContentValues(); values.put(Data.RAW_CONTACT_ID, rawContactId2); values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); values.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_huge, PhotoSize.ORIGINAL)); assertEquals(1, mResolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[]{String.valueOf(dataId2)})); long replacementPhotoFileId = getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId2), Photo.PHOTO_FILE_ID); // Insert a third raw contact that has a bogus photo file ID. long bogusFileId = 1234567; long rawContactId3 = createRawContactWithName(); long contactId3 = queryContactId(rawContactId3); values.clear(); values.put(Data.RAW_CONTACT_ID, rawContactId3); values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); values.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_normal, PhotoSize.THUMBNAIL)); values.put(Photo.PHOTO_FILE_ID, bogusFileId); values.put(DataRowHandlerForPhoto.SKIP_PROCESSING_KEY, true); mResolver.insert(Data.CONTENT_URI, values); // Insert a fourth raw contact with a stream item that has a photo, then remove that photo // from the photo store. Account socialAccount = new Account("social", "social"); long rawContactId4 = createRawContactWithName(socialAccount); Uri streamItemUri = insertStreamItem(rawContactId4, buildGenericStreamItemValues(), socialAccount); long streamItemId = ContentUris.parseId(streamItemUri); Uri streamItemPhotoUri = insertStreamItemPhoto( streamItemId, buildGenericStreamItemPhotoValues(0), socialAccount); long streamItemPhotoFileId = getStoredLongValue(streamItemPhotoUri, StreamItemPhotos.PHOTO_FILE_ID); photoStore.remove(streamItemPhotoFileId); // Also insert a bogus photo that nobody is using. long bogusPhotoId = photoStore.insert(new PhotoProcessor(loadPhotoFromResource( R.drawable.earth_huge, PhotoSize.ORIGINAL), 256, 96)); // Manually trigger another cleanup in the provider. provider.cleanupPhotoStore(); // The following things should have happened. // 1. Raw contact 1 and its photo remain unaffected. assertEquals(photoFileId1, (long) getStoredLongValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1), Contacts.PHOTO_FILE_ID)); // 2. Raw contact 2 retains its new photo. The old one is deleted from the photo store. assertEquals(replacementPhotoFileId, (long) getStoredLongValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2), Contacts.PHOTO_FILE_ID)); assertNull(photoStore.get(photoFileId2)); // 3. Raw contact 3 should have its photo file reference cleared. assertNull(getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId3), Contacts.PHOTO_FILE_ID)); // 4. The bogus photo that nobody was using should be cleared from the photo store. assertNull(photoStore.get(bogusPhotoId)); // 5. The bogus stream item photo should be cleared from the stream item. assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), new ContentValues[0]); } public void testPhotoStoreCleanupForProfile() { SynchronousContactsProvider2 provider = (SynchronousContactsProvider2) mActor.provider; PhotoStore profilePhotoStore = provider.getProfilePhotoStore(); // Trigger an initial cleanup so another one won't happen while we're running this test. provider.switchToProfileModeForTest(); provider.cleanupPhotoStore(); // Create the profile contact and add a photo. Account socialAccount = new Account("social", "social"); ContentValues values = new ContentValues(); values.put(RawContacts.ACCOUNT_NAME, socialAccount.name); values.put(RawContacts.ACCOUNT_TYPE, socialAccount.type); long profileRawContactId = createBasicProfileContact(values); long profileContactId = queryContactId(profileRawContactId); long dataId = ContentUris.parseId( insertPhoto(profileRawContactId, R.drawable.earth_normal)); long profilePhotoFileId = getStoredLongValue(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), Photo.PHOTO_FILE_ID); // Also add a stream item with a photo. Uri streamItemUri = insertStreamItem(profileRawContactId, buildGenericStreamItemValues(), socialAccount); long streamItemId = ContentUris.parseId(streamItemUri); Uri streamItemPhotoUri = insertStreamItemPhoto( streamItemId, buildGenericStreamItemPhotoValues(0), socialAccount); long streamItemPhotoFileId = getStoredLongValue(streamItemPhotoUri, StreamItemPhotos.PHOTO_FILE_ID); // Remove the stream item photo and the profile photo. profilePhotoStore.remove(profilePhotoFileId); profilePhotoStore.remove(streamItemPhotoFileId); // Manually trigger another cleanup in the provider. provider.switchToProfileModeForTest(); provider.cleanupPhotoStore(); // The following things should have happened. // The stream item photo should have been removed. assertStoredValues(Uri.withAppendedPath( ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), new ContentValues[0]); // The profile photo should have been cleared. assertNull(getStoredValue( ContentUris.withAppendedId(Contacts.CONTENT_URI, profileContactId), Contacts.PHOTO_FILE_ID)); } public void testOverwritePhotoWithThumbnail() throws IOException { long rawContactId = createRawContactWithName(); long contactId = queryContactId(rawContactId); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); // Write a regular-size photo. long dataId = ContentUris.parseId(insertPhoto(rawContactId, R.drawable.earth_normal)); Long photoFileId = getStoredLongValue(contactUri, Contacts.PHOTO_FILE_ID); assertTrue(photoFileId != null && photoFileId > 0); // Now overwrite the photo with a thumbnail-sized photo. ContentValues update = new ContentValues(); update.put(Photo.PHOTO, loadPhotoFromResource(R.drawable.earth_small, PhotoSize.ORIGINAL)); mResolver.update(ContentUris.withAppendedId(Data.CONTENT_URI, dataId), update, null, null); // Photo file ID should have been nulled out, and the photo URI should be the same as the // thumbnail URI. assertNull(getStoredValue(contactUri, Contacts.PHOTO_FILE_ID)); String photoUri = getStoredValue(contactUri, Contacts.PHOTO_URI); String thumbnailUri = getStoredValue(contactUri, Contacts.PHOTO_THUMBNAIL_URI); assertEquals(photoUri, thumbnailUri); // Retrieving the photo URI should get the thumbnail content. EvenMoreAsserts.assertImageRawData(getContext(), loadPhotoFromResource(R.drawable.earth_small, PhotoSize.THUMBNAIL), mResolver.openInputStream(Uri.parse(photoUri))); } public void testUpdateRawContactSetStarred() { long rawContactId1 = createRawContactWithName(); Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1); long rawContactId2 = createRawContactWithName(); Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2); setAggregationException( AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); long contactId = queryContactId(rawContactId1); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); assertStoredValue(contactUri, Contacts.STARRED, "0"); ContentValues values = new ContentValues(); values.put(RawContacts.STARRED, "1"); mResolver.update(rawContactUri1, values, null, null); assertStoredValue(rawContactUri1, RawContacts.STARRED, "1"); assertStoredValue(rawContactUri2, RawContacts.STARRED, "0"); assertStoredValue(contactUri, Contacts.STARRED, "1"); values.put(RawContacts.STARRED, "0"); mResolver.update(rawContactUri1, values, null, null); assertStoredValue(rawContactUri1, RawContacts.STARRED, "0"); assertStoredValue(rawContactUri2, RawContacts.STARRED, "0"); assertStoredValue(contactUri, Contacts.STARRED, "0"); values.put(Contacts.STARRED, "1"); mResolver.update(contactUri, values, null, null); assertStoredValue(rawContactUri1, RawContacts.STARRED, "1"); assertStoredValue(rawContactUri2, RawContacts.STARRED, "1"); assertStoredValue(contactUri, Contacts.STARRED, "1"); } public void testSetAndClearSuperPrimaryEmail() { long rawContactId1 = createRawContact(new Account("a", "a")); Uri mailUri11 = insertEmail(rawContactId1, "test1@domain1.com"); Uri mailUri12 = insertEmail(rawContactId1, "test2@domain1.com"); long rawContactId2 = createRawContact(new Account("b", "b")); Uri mailUri21 = insertEmail(rawContactId2, "test1@domain2.com"); Uri mailUri22 = insertEmail(rawContactId2, "test2@domain2.com"); assertStoredValue(mailUri11, Data.IS_PRIMARY, 0); assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 0); // Set super primary on the first pair, primary on the second { ContentValues values = new ContentValues(); values.put(Data.IS_SUPER_PRIMARY, 1); mResolver.update(mailUri11, values, null, null); } { ContentValues values = new ContentValues(); values.put(Data.IS_SUPER_PRIMARY, 1); mResolver.update(mailUri22, values, null, null); } assertStoredValue(mailUri11, Data.IS_PRIMARY, 1); assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 1); assertStoredValue(mailUri12, Data.IS_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_PRIMARY, 1); assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1); // Clear primary on the first pair, make sure second is not affected and super_primary is // also cleared { ContentValues values = new ContentValues(); values.put(Data.IS_PRIMARY, 0); mResolver.update(mailUri11, values, null, null); } assertStoredValue(mailUri11, Data.IS_PRIMARY, 0); assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_PRIMARY, 1); assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1); // Ensure that we can only clear super_primary, if we specify the correct data row { ContentValues values = new ContentValues(); values.put(Data.IS_SUPER_PRIMARY, 0); mResolver.update(mailUri21, values, null, null); } assertStoredValue(mailUri21, Data.IS_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_PRIMARY, 1); assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1); // Ensure that we can only clear primary, if we specify the correct data row { ContentValues values = new ContentValues(); values.put(Data.IS_PRIMARY, 0); mResolver.update(mailUri21, values, null, null); } assertStoredValue(mailUri21, Data.IS_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_PRIMARY, 1); assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 1); // Now clear super-primary for real { ContentValues values = new ContentValues(); values.put(Data.IS_SUPER_PRIMARY, 0); mResolver.update(mailUri22, values, null, null); } assertStoredValue(mailUri11, Data.IS_PRIMARY, 0); assertStoredValue(mailUri11, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_PRIMARY, 0); assertStoredValue(mailUri12, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_PRIMARY, 0); assertStoredValue(mailUri21, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri22, Data.IS_PRIMARY, 1); assertStoredValue(mailUri22, Data.IS_SUPER_PRIMARY, 0); } /** * Common function for the testNewPrimaryIn* functions. Its four configurations * are each called from its own test */ public void testChangingPrimary(boolean inUpdate, boolean withSuperPrimary) { long rawContactId = createRawContact(new Account("a", "a")); Uri mailUri1 = insertEmail(rawContactId, "test1@domain1.com", true); if (withSuperPrimary) { final ContentValues values = new ContentValues(); values.put(Data.IS_SUPER_PRIMARY, 1); mResolver.update(mailUri1, values, null, null); } assertStoredValue(mailUri1, Data.IS_PRIMARY, 1); assertStoredValue(mailUri1, Data.IS_SUPER_PRIMARY, withSuperPrimary ? 1 : 0); // Insert another item final Uri mailUri2; if (inUpdate) { mailUri2 = insertEmail(rawContactId, "test2@domain1.com"); assertStoredValue(mailUri1, Data.IS_PRIMARY, 1); assertStoredValue(mailUri1, Data.IS_SUPER_PRIMARY, withSuperPrimary ? 1 : 0); assertStoredValue(mailUri2, Data.IS_PRIMARY, 0); assertStoredValue(mailUri2, Data.IS_SUPER_PRIMARY, 0); final ContentValues values = new ContentValues(); values.put(Data.IS_PRIMARY, 1); mResolver.update(mailUri2, values, null, null); } else { // directly add as default mailUri2 = insertEmail(rawContactId, "test2@domain1.com", true); } // Ensure that primary has been unset on the first // If withSuperPrimary is set, also ensure that is has been moved to the new item assertStoredValue(mailUri1, Data.IS_PRIMARY, 0); assertStoredValue(mailUri1, Data.IS_SUPER_PRIMARY, 0); assertStoredValue(mailUri2, Data.IS_PRIMARY, 1); assertStoredValue(mailUri2, Data.IS_SUPER_PRIMARY, withSuperPrimary ? 1 : 0); } public void testNewPrimaryInInsert() { testChangingPrimary(false, false); } public void testNewPrimaryInInsertWithSuperPrimary() { testChangingPrimary(false, true); } public void testNewPrimaryInUpdate() { testChangingPrimary(true, false); } public void testNewPrimaryInUpdateWithSuperPrimary() { testChangingPrimary(true, true); } public void testContactCounts() { Uri uri = Contacts.CONTENT_URI.buildUpon() .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build(); createRawContact(); createRawContactWithName("James", "Sullivan"); createRawContactWithName("The Abominable", "Snowman"); createRawContactWithName("Mike", "Wazowski"); createRawContactWithName("randall", "boggs"); createRawContactWithName("Boo", null); createRawContactWithName("Mary", null); createRawContactWithName("Roz", null); Cursor cursor = mResolver.query(uri, new String[]{Contacts.DISPLAY_NAME}, null, null, Contacts.SORT_KEY_PRIMARY + " COLLATE LOCALIZED"); assertFirstLetterValues(cursor, "", "B", "J", "M", "R", "T"); assertFirstLetterCounts(cursor, 1, 1, 1, 2, 2, 1); cursor.close(); cursor = mResolver.query(uri, new String[]{Contacts.DISPLAY_NAME}, null, null, Contacts.SORT_KEY_ALTERNATIVE + " COLLATE LOCALIZED DESC"); assertFirstLetterValues(cursor, "W", "S", "R", "M", "B", ""); assertFirstLetterCounts(cursor, 1, 2, 1, 1, 2, 1); cursor.close(); } private void assertFirstLetterValues(Cursor cursor, String... expected) { String[] actual = cursor.getExtras() .getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); MoreAsserts.assertEquals(expected, actual); } private void assertFirstLetterCounts(Cursor cursor, int... expected) { int[] actual = cursor.getExtras() .getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); MoreAsserts.assertEquals(expected, actual); } public void testReadBooleanQueryParameter() { assertBooleanUriParameter("foo:bar", "bool", true, true); assertBooleanUriParameter("foo:bar", "bool", false, false); assertBooleanUriParameter("foo:bar?bool=0", "bool", true, false); assertBooleanUriParameter("foo:bar?bool=1", "bool", false, true); assertBooleanUriParameter("foo:bar?bool=false", "bool", true, false); assertBooleanUriParameter("foo:bar?bool=true", "bool", false, true); assertBooleanUriParameter("foo:bar?bool=FaLsE", "bool", true, false); assertBooleanUriParameter("foo:bar?bool=false&some=some", "bool", true, false); assertBooleanUriParameter("foo:bar?bool=1&some=some", "bool", false, true); assertBooleanUriParameter("foo:bar?some=bool", "bool", true, true); assertBooleanUriParameter("foo:bar?bool", "bool", true, true); } private void assertBooleanUriParameter(String uriString, String parameter, boolean defaultValue, boolean expectedValue) { assertEquals(expectedValue, ContactsProvider2.readBooleanQueryParameter( Uri.parse(uriString), parameter, defaultValue)); } public void testGetQueryParameter() { assertQueryParameter("foo:bar", "param", null); assertQueryParameter("foo:bar?param", "param", null); assertQueryParameter("foo:bar?param=", "param", ""); assertQueryParameter("foo:bar?param=val", "param", "val"); assertQueryParameter("foo:bar?param=val&some=some", "param", "val"); assertQueryParameter("foo:bar?some=some¶m=val", "param", "val"); assertQueryParameter("foo:bar?some=some¶m=val&else=else", "param", "val"); assertQueryParameter("foo:bar?param=john%40doe.com", "param", "john@doe.com"); assertQueryParameter("foo:bar?some_param=val", "param", null); assertQueryParameter("foo:bar?some_param=val1¶m=val2", "param", "val2"); assertQueryParameter("foo:bar?some_param=val1¶m=", "param", ""); assertQueryParameter("foo:bar?some_param=val1¶m", "param", null); assertQueryParameter("foo:bar?some_param=val1&another_param=val2¶m=val3", "param", "val3"); assertQueryParameter("foo:bar?some_param=val1¶m=val2&some_param=val3", "param", "val2"); assertQueryParameter("foo:bar?param=val1&some_param=val2", "param", "val1"); assertQueryParameter("foo:bar?p=val1&pp=val2", "p", "val1"); assertQueryParameter("foo:bar?pp=val1&p=val2", "p", "val2"); assertQueryParameter("foo:bar?ppp=val1&pp=val2&p=val3", "p", "val3"); assertQueryParameter("foo:bar?ppp=val&", "p", null); } public void testMissingAccountTypeParameter() { // Try querying for RawContacts only using ACCOUNT_NAME final Uri queryUri = RawContacts.CONTENT_URI.buildUpon().appendQueryParameter( RawContacts.ACCOUNT_NAME, "lolwut").build(); try { final Cursor cursor = mResolver.query(queryUri, null, null, null, null); fail("Able to query with incomplete account query parameters"); } catch (IllegalArgumentException e) { // Expected behavior. } } public void testInsertInconsistentAccountType() { // Try inserting RawContact with inconsistent Accounts final Account red = new Account("red", "red"); final Account blue = new Account("blue", "blue"); final ContentValues values = new ContentValues(); values.put(RawContacts.ACCOUNT_NAME, red.name); values.put(RawContacts.ACCOUNT_TYPE, red.type); final Uri insertUri = maybeAddAccountQueryParameters(RawContacts.CONTENT_URI, blue); try { mResolver.insert(insertUri, values); fail("Able to insert RawContact with inconsistent account details"); } catch (IllegalArgumentException e) { // Expected behavior. } } public void testProviderStatusNoContactsNoAccounts() throws Exception { assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS); } public void testProviderStatusOnlyLocalContacts() throws Exception { long rawContactId = createRawContact(); assertProviderStatus(ProviderStatus.STATUS_NORMAL); mResolver.delete( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), null, null); assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS); } public void testProviderStatusWithAccounts() throws Exception { assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS); mActor.setAccounts(new Account[]{ACCOUNT_1}); ((ContactsProvider2)getProvider()).onAccountsUpdated(new Account[]{ACCOUNT_1}); assertProviderStatus(ProviderStatus.STATUS_NORMAL); mActor.setAccounts(new Account[0]); ((ContactsProvider2)getProvider()).onAccountsUpdated(new Account[0]); assertProviderStatus(ProviderStatus.STATUS_NO_ACCOUNTS_NO_CONTACTS); } private void assertProviderStatus(int expectedProviderStatus) { Cursor cursor = mResolver.query(ProviderStatus.CONTENT_URI, new String[]{ProviderStatus.DATA1, ProviderStatus.STATUS}, null, null, null); assertTrue(cursor.moveToFirst()); assertEquals(0, cursor.getLong(0)); assertEquals(expectedProviderStatus, cursor.getInt(1)); cursor.close(); } public void testProperties() throws Exception { ContactsProvider2 provider = (ContactsProvider2)getProvider(); ContactsDatabaseHelper helper = (ContactsDatabaseHelper)provider.getDatabaseHelper(); assertNull(helper.getProperty("non-existent", null)); assertEquals("default", helper.getProperty("non-existent", "default")); helper.setProperty("existent1", "string1"); helper.setProperty("existent2", "string2"); assertEquals("string1", helper.getProperty("existent1", "default")); assertEquals("string2", helper.getProperty("existent2", "default")); helper.setProperty("existent1", null); assertEquals("default", helper.getProperty("existent1", "default")); } private class VCardTestUriCreator { private String mLookup1; private String mLookup2; public VCardTestUriCreator(String lookup1, String lookup2) { super(); mLookup1 = lookup1; mLookup2 = lookup2; } public Uri getUri1() { return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup1); } public Uri getUri2() { return Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, mLookup2); } public Uri getCombinedUri() { return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI, Uri.encode(mLookup1 + ":" + mLookup2)); } } private VCardTestUriCreator createVCardTestContacts() { final long rawContactId1 = createRawContact(mAccount, RawContacts.SOURCE_ID, "4:12"); insertStructuredName(rawContactId1, "John", "Doe"); final long rawContactId2 = createRawContact(mAccount, RawContacts.SOURCE_ID, "3:4%121"); insertStructuredName(rawContactId2, "Jane", "Doh"); final long contactId1 = queryContactId(rawContactId1); final long contactId2 = queryContactId(rawContactId2); final Uri contact1Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1); final Uri contact2Uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2); final String lookup1 = Uri.encode(Contacts.getLookupUri(mResolver, contact1Uri).getPathSegments().get(2)); final String lookup2 = Uri.encode(Contacts.getLookupUri(mResolver, contact2Uri).getPathSegments().get(2)); return new VCardTestUriCreator(lookup1, lookup2); } public void testQueryMultiVCard() { // No need to create any contacts here, because the query for multiple vcards // does not go into the database at all Uri uri = Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI, Uri.encode("123:456")); Cursor cursor = mResolver.query(uri, null, null, null, null); assertEquals(1, cursor.getCount()); assertTrue(cursor.moveToFirst()); assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE))); String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); // The resulting name contains date and time. Ensure that before and after are correct assertTrue(filename.startsWith("vcards_")); assertTrue(filename.endsWith(".vcf")); cursor.close(); } public void testQueryFileSingleVCard() { final VCardTestUriCreator contacts = createVCardTestContacts(); { Cursor cursor = mResolver.query(contacts.getUri1(), null, null, null, null); assertEquals(1, cursor.getCount()); assertTrue(cursor.moveToFirst()); assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE))); String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); assertEquals("John Doe.vcf", filename); cursor.close(); } { Cursor cursor = mResolver.query(contacts.getUri2(), null, null, null, null); assertEquals(1, cursor.getCount()); assertTrue(cursor.moveToFirst()); assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE))); String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); assertEquals("Jane Doh.vcf", filename); cursor.close(); } } public void testQueryFileProfileVCard() { createBasicProfileContact(new ContentValues()); Cursor cursor = mResolver.query(Profile.CONTENT_VCARD_URI, null, null, null, null); assertEquals(1, cursor.getCount()); assertTrue(cursor.moveToFirst()); assertTrue(cursor.isNull(cursor.getColumnIndex(OpenableColumns.SIZE))); String filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); assertEquals("Mia Prophyl.vcf", filename); cursor.close(); } public void testOpenAssetFileMultiVCard() throws IOException { final VCardTestUriCreator contacts = createVCardTestContacts(); final AssetFileDescriptor descriptor = mResolver.openAssetFileDescriptor(contacts.getCombinedUri(), "r"); final FileInputStream inputStream = descriptor.createInputStream(); String data = readToEnd(inputStream); inputStream.close(); descriptor.close(); // Ensure that the resulting VCard has both contacts assertTrue(data.contains("N:Doe;John;;;")); assertTrue(data.contains("N:Doh;Jane;;;")); } public void testOpenAssetFileSingleVCard() throws IOException { final VCardTestUriCreator contacts = createVCardTestContacts(); // Ensure that the right VCard is being created in each case { final AssetFileDescriptor descriptor = mResolver.openAssetFileDescriptor(contacts.getUri1(), "r"); final FileInputStream inputStream = descriptor.createInputStream(); final String data = readToEnd(inputStream); inputStream.close(); descriptor.close(); assertTrue(data.contains("N:Doe;John;;;")); assertFalse(data.contains("N:Doh;Jane;;;")); } { final AssetFileDescriptor descriptor = mResolver.openAssetFileDescriptor(contacts.getUri2(), "r"); final FileInputStream inputStream = descriptor.createInputStream(); final String data = readToEnd(inputStream); inputStream.close(); descriptor.close(); assertFalse(data.contains("N:Doe;John;;;")); assertTrue(data.contains("N:Doh;Jane;;;")); } } public void testAutoGroupMembership() { long g1 = createGroup(mAccount, "g1", "t1", 0, true /* autoAdd */, false /* favorite */); long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */); long g3 = createGroup(mAccountTwo, "g3", "t3", 0, true /* autoAdd */, false /* favorite */); long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, false/* favorite */); long r1 = createRawContact(mAccount); long r2 = createRawContact(mAccountTwo); long r3 = createRawContact(null); Cursor c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } c = queryGroupMemberships(mAccountTwo); try { assertTrue(c.moveToNext()); assertEquals(g3, c.getLong(0)); assertEquals(r2, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } } public void testNoAutoAddMembershipAfterGroupCreation() { long r1 = createRawContact(mAccount); long r2 = createRawContact(mAccount); long r3 = createRawContact(mAccount); long r4 = createRawContact(mAccountTwo); long r5 = createRawContact(mAccountTwo); long r6 = createRawContact(null); assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); long g1 = createGroup(mAccount, "g1", "t1", 0, true /* autoAdd */, false /* favorite */); long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */); long g3 = createGroup(mAccountTwo, "g3", "t3", 0, true /* autoAdd */, false/* favorite */); assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); } // create some starred and non-starred contacts, some associated with account, some not // favorites group created // the starred contacts should be added to group // favorites group removed // no change to starred status public void testFavoritesMembershipAfterGroupCreation() { long r1 = createRawContact(mAccount, RawContacts.STARRED, "1"); long r2 = createRawContact(mAccount); long r3 = createRawContact(mAccount, RawContacts.STARRED, "1"); long r4 = createRawContact(mAccountTwo, RawContacts.STARRED, "1"); long r5 = createRawContact(mAccountTwo); long r6 = createRawContact(null, RawContacts.STARRED, "1"); long r7 = createRawContact(null); assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */); long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false /* favorite */); long g3 = createGroup(mAccountTwo, "g3", "t3", 0, false /* autoAdd */, false/* favorite */); assertTrue(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertTrue(queryRawContactIsStarred(r3)); assertTrue(queryRawContactIsStarred(r4)); assertFalse(queryRawContactIsStarred(r5)); assertTrue(queryRawContactIsStarred(r6)); assertFalse(queryRawContactIsStarred(r7)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); Cursor c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r3, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } updateItem(RawContacts.CONTENT_URI, r6, RawContacts.ACCOUNT_NAME, mAccount.name, RawContacts.ACCOUNT_TYPE, mAccount.type); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r3, c.getLong(1)); assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r6, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } mResolver.delete(ContentUris.withAppendedId(Groups.CONTENT_URI, g1), null, null); assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); assertTrue(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertTrue(queryRawContactIsStarred(r3)); assertTrue(queryRawContactIsStarred(r4)); assertFalse(queryRawContactIsStarred(r5)); assertTrue(queryRawContactIsStarred(r6)); assertFalse(queryRawContactIsStarred(r7)); } public void testFavoritesGroupMembershipChangeAfterStarChange() { long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */); long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false/* favorite */); long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, true /* favorite */); long g5 = createGroup(mAccountTwo, "g5", "t5", 0, false /* autoAdd */, false/* favorite */); long r1 = createRawContact(mAccount, RawContacts.STARRED, "1"); long r2 = createRawContact(mAccount); long r3 = createRawContact(mAccountTwo); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); Cursor c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } // remove the star from r1 assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "0")); // Since no raw contacts are starred, there should be no group memberships. assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); // mark r1 as starred assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "1")); // Now that r1 is starred it should have a membership in the one groups from mAccount // that is marked as a favorite. // There should be no memberships in mAccountTwo since it has no starred raw contacts. assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } // remove the star from r1 assertEquals(1, updateItem(RawContacts.CONTENT_URI, r1, RawContacts.STARRED, "0")); // Since no raw contacts are starred, there should be no group memberships. assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, queryContactId(r1)); assertNotNull(contactUri); // mark r1 as starred via its contact lookup uri assertEquals(1, updateItem(contactUri, Contacts.STARRED, "1")); // Now that r1 is starred it should have a membership in the one groups from mAccount // that is marked as a favorite. // There should be no memberships in mAccountTwo since it has no starred raw contacts. assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } // remove the star from r1 updateItem(contactUri, Contacts.STARRED, "0"); // Since no raw contacts are starred, there should be no group memberships. assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); } public void testStarChangedAfterGroupMembershipChange() { long g1 = createGroup(mAccount, "g1", "t1", 0, false /* autoAdd */, true /* favorite */); long g2 = createGroup(mAccount, "g2", "t2", 0, false /* autoAdd */, false/* favorite */); long g4 = createGroup(mAccountTwo, "g4", "t4", 0, false /* autoAdd */, true /* favorite */); long g5 = createGroup(mAccountTwo, "g5", "t5", 0, false /* autoAdd */, false/* favorite */); long r1 = createRawContact(mAccount); long r2 = createRawContact(mAccount); long r3 = createRawContact(mAccountTwo); assertFalse(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertFalse(queryRawContactIsStarred(r3)); Cursor c; // add r1 to one favorites group // r1's star should automatically be set // r1 should automatically be added to the other favorites group Uri urir1g1 = insertGroupMembership(r1, g1); assertTrue(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertFalse(queryRawContactIsStarred(r3)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); c = queryGroupMemberships(mAccount); try { assertTrue(c.moveToNext()); assertEquals(g1, c.getLong(0)); assertEquals(r1, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } // remove r1 from one favorites group mResolver.delete(urir1g1, null, null); // r1's star should no longer be set assertFalse(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertFalse(queryRawContactIsStarred(r3)); // there should be no membership rows assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); // add r3 to the one favorites group for that account // r3's star should automatically be set Uri urir3g4 = insertGroupMembership(r3, g4); assertFalse(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertTrue(queryRawContactIsStarred(r3)); assertNoRowsAndClose(queryGroupMemberships(mAccount)); c = queryGroupMemberships(mAccountTwo); try { assertTrue(c.moveToNext()); assertEquals(g4, c.getLong(0)); assertEquals(r3, c.getLong(1)); assertFalse(c.moveToNext()); } finally { c.close(); } // remove r3 from the favorites group mResolver.delete(urir3g4, null, null); // r3's star should automatically be cleared assertFalse(queryRawContactIsStarred(r1)); assertFalse(queryRawContactIsStarred(r2)); assertFalse(queryRawContactIsStarred(r3)); assertNoRowsAndClose(queryGroupMemberships(mAccount)); assertNoRowsAndClose(queryGroupMemberships(mAccountTwo)); } public void testReadOnlyRawContact() { long rawContactId = createRawContact(); Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); storeValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "first"); storeValue(rawContactUri, RawContacts.RAW_CONTACT_IS_READ_ONLY, 1); storeValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "second"); assertStoredValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "first"); Uri syncAdapterUri = rawContactUri.buildUpon() .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "1") .build(); storeValue(syncAdapterUri, RawContacts.CUSTOM_RINGTONE, "third"); assertStoredValue(rawContactUri, RawContacts.CUSTOM_RINGTONE, "third"); } public void testReadOnlyDataRow() { long rawContactId = createRawContact(); Uri emailUri = insertEmail(rawContactId, "email"); Uri phoneUri = insertPhoneNumber(rawContactId, "555-1111"); storeValue(emailUri, Data.IS_READ_ONLY, "1"); storeValue(emailUri, Email.ADDRESS, "changed"); storeValue(phoneUri, Phone.NUMBER, "555-2222"); assertStoredValue(emailUri, Email.ADDRESS, "email"); assertStoredValue(phoneUri, Phone.NUMBER, "555-2222"); Uri syncAdapterUri = emailUri.buildUpon() .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "1") .build(); storeValue(syncAdapterUri, Email.ADDRESS, "changed"); assertStoredValue(emailUri, Email.ADDRESS, "changed"); } public void testContactWithReadOnlyRawContact() { long rawContactId1 = createRawContact(); Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1); storeValue(rawContactUri1, RawContacts.CUSTOM_RINGTONE, "first"); long rawContactId2 = createRawContact(); Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2); storeValue(rawContactUri2, RawContacts.CUSTOM_RINGTONE, "second"); storeValue(rawContactUri2, RawContacts.RAW_CONTACT_IS_READ_ONLY, 1); setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); long contactId = queryContactId(rawContactId1); Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); storeValue(contactUri, Contacts.CUSTOM_RINGTONE, "rt"); assertStoredValue(contactUri, Contacts.CUSTOM_RINGTONE, "rt"); assertStoredValue(rawContactUri1, RawContacts.CUSTOM_RINGTONE, "rt"); assertStoredValue(rawContactUri2, RawContacts.CUSTOM_RINGTONE, "second"); } public void testNameParsingQuery() { Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name") .appendQueryParameter(StructuredName.DISPLAY_NAME, "Mr. John Q. Doe Jr.").build(); Cursor cursor = mResolver.query(uri, null, null, null, null); ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, "Mr. John Q. Doe Jr."); values.put(StructuredName.PREFIX, "Mr."); values.put(StructuredName.GIVEN_NAME, "John"); values.put(StructuredName.MIDDLE_NAME, "Q."); values.put(StructuredName.FAMILY_NAME, "Doe"); values.put(StructuredName.SUFFIX, "Jr."); values.put(StructuredName.FULL_NAME_STYLE, FullNameStyle.WESTERN); assertTrue(cursor.moveToFirst()); assertCursorValues(cursor, values); cursor.close(); } public void testNameConcatenationQuery() { Uri uri = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name") .appendQueryParameter(StructuredName.PREFIX, "Mr") .appendQueryParameter(StructuredName.GIVEN_NAME, "John") .appendQueryParameter(StructuredName.MIDDLE_NAME, "Q.") .appendQueryParameter(StructuredName.FAMILY_NAME, "Doe") .appendQueryParameter(StructuredName.SUFFIX, "Jr.") .build(); Cursor cursor = mResolver.query(uri, null, null, null, null); ContentValues values = new ContentValues(); values.put(StructuredName.DISPLAY_NAME, "Mr John Q. Doe, Jr."); values.put(StructuredName.PREFIX, "Mr"); values.put(StructuredName.GIVEN_NAME, "John"); values.put(StructuredName.MIDDLE_NAME, "Q."); values.put(StructuredName.FAMILY_NAME, "Doe"); values.put(StructuredName.SUFFIX, "Jr."); values.put(StructuredName.FULL_NAME_STYLE, FullNameStyle.WESTERN); assertTrue(cursor.moveToFirst()); assertCursorValues(cursor, values); cursor.close(); } public void testBuildSingleRowResult() { checkBuildSingleRowResult( new String[] {"b"}, new String[] {"a", "b"}, new Integer[] {1, 2}, new Integer[] {2} ); checkBuildSingleRowResult( new String[] {"b", "a", "b"}, new String[] {"a", "b"}, new Integer[] {1, 2}, new Integer[] {2, 1, 2} ); checkBuildSingleRowResult( null, // all columns new String[] {"a", "b"}, new Integer[] {1, 2}, new Integer[] {1, 2} ); try { // Access non-existent column ContactsProvider2.buildSingleRowResult(new String[] {"a"}, new String[] {"b"}, new Object[] {1}); fail(); } catch (IllegalArgumentException expected) { } } private void checkBuildSingleRowResult(String[] projection, String[] availableColumns, Object[] data, Integer[] expectedValues) { final Cursor c = ContactsProvider2.buildSingleRowResult(projection, availableColumns, data); try { assertTrue(c.moveToFirst()); assertEquals(1, c.getCount()); assertEquals(expectedValues.length, c.getColumnCount()); for (int i = 0; i < expectedValues.length; i++) { assertEquals("column " + i, expectedValues[i], (Integer) c.getInt(i)); } } finally { c.close(); } } public void testDataUsageFeedbackAndDelete() { sMockClock.install(); final long startTime = sMockClock.currentTimeMillis(); final long rid1 = createRawContactWithName("contact", "a"); final long did1a = ContentUris.parseId(insertEmail(rid1, "email_1_a@email.com")); final long did1b = ContentUris.parseId(insertEmail(rid1, "email_1_b@email.com")); final long did1p = ContentUris.parseId(insertPhoneNumber(rid1, "555-555-5555")); final long rid2 = createRawContactWithName("contact", "b"); final long did2a = ContentUris.parseId(insertEmail(rid2, "email_2_a@email.com")); final long did2p = ContentUris.parseId(insertPhoneNumber(rid2, "555-555-5556")); // Aggregate 1 and 2 setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rid1, rid2); final long rid3 = createRawContactWithName("contact", "c"); final long did3a = ContentUris.parseId(insertEmail(rid3, "email_3@email.com")); final long did3p = ContentUris.parseId(insertPhoneNumber(rid3, "555-3333")); final long rid4 = createRawContactWithName("contact", "d"); final long did4p = ContentUris.parseId(insertPhoneNumber(rid4, "555-4444")); final long cid1 = queryContactId(rid1); final long cid3 = queryContactId(rid3); final long cid4 = queryContactId(rid4); // Make sure 1+2, 3 and 4 aren't aggregated MoreAsserts.assertNotEqual(cid1, cid3); MoreAsserts.assertNotEqual(cid1, cid4); MoreAsserts.assertNotEqual(cid3, cid4); // time = startTime // First, there's no frequent. (We use strequent here only because frequent is hidden // and may be removed someday.) assertRowCount(0, Contacts.CONTENT_STREQUENT_URI, null, null); // Test 1. touch data 1a updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a); // Now, there's a single frequent. (contact 1) assertRowCount(1, Contacts.CONTENT_STREQUENT_URI, null, null); // time = startTime + 1 sMockClock.advance(); // Test 2. touch data 1a, 2a and 3a updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a, did2a, did3a); // Now, contact 1 and 3 are in frequent. assertRowCount(2, Contacts.CONTENT_STREQUENT_URI, null, null); // time = startTime + 2 sMockClock.advance(); // Test 2. touch data 2p (call) updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did2p); // There're still two frequent. assertRowCount(2, Contacts.CONTENT_STREQUENT_URI, null, null); // time = startTime + 3 sMockClock.advance(); // Test 3. touch data 2p and 3p (short text) updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, did2p, did3p); // Let's check the tables. // Fist, check the data_usage_stat table, which has no public URI. assertStoredValuesDb("SELECT " + DataUsageStatColumns.DATA_ID + "," + DataUsageStatColumns.USAGE_TYPE_INT + "," + DataUsageStatColumns.TIMES_USED + "," + DataUsageStatColumns.LAST_TIME_USED + " FROM " + Tables.DATA_USAGE_STAT, null, cv(DataUsageStatColumns.DATA_ID, did1a, DataUsageStatColumns.USAGE_TYPE_INT, DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT, DataUsageStatColumns.TIMES_USED, 2, DataUsageStatColumns.LAST_TIME_USED, startTime + 1 ), cv(DataUsageStatColumns.DATA_ID, did2a, DataUsageStatColumns.USAGE_TYPE_INT, DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT, DataUsageStatColumns.TIMES_USED, 1, DataUsageStatColumns.LAST_TIME_USED, startTime + 1 ), cv(DataUsageStatColumns.DATA_ID, did3a, DataUsageStatColumns.USAGE_TYPE_INT, DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT, DataUsageStatColumns.TIMES_USED, 1, DataUsageStatColumns.LAST_TIME_USED, startTime + 1 ), cv(DataUsageStatColumns.DATA_ID, did2p, DataUsageStatColumns.USAGE_TYPE_INT, DataUsageStatColumns.USAGE_TYPE_INT_CALL, DataUsageStatColumns.TIMES_USED, 1, DataUsageStatColumns.LAST_TIME_USED, startTime + 2 ), cv(DataUsageStatColumns.DATA_ID, did2p, DataUsageStatColumns.USAGE_TYPE_INT, DataUsageStatColumns.USAGE_TYPE_INT_SHORT_TEXT, DataUsageStatColumns.TIMES_USED, 1, DataUsageStatColumns.LAST_TIME_USED, startTime + 3 ), cv(DataUsageStatColumns.DATA_ID, did3p, DataUsageStatColumns.USAGE_TYPE_INT, DataUsageStatColumns.USAGE_TYPE_INT_SHORT_TEXT, DataUsageStatColumns.TIMES_USED, 1, DataUsageStatColumns.LAST_TIME_USED, startTime + 3 ) ); // Next, check the raw_contacts table assertStoredValuesWithProjection(RawContacts.CONTENT_URI, cv(RawContacts._ID, rid1, RawContacts.TIMES_CONTACTED, 2, RawContacts.LAST_TIME_CONTACTED, startTime + 1 ), cv(RawContacts._ID, rid2, RawContacts.TIMES_CONTACTED, 3, RawContacts.LAST_TIME_CONTACTED, startTime + 3 ), cv(RawContacts._ID, rid3, RawContacts.TIMES_CONTACTED, 2, RawContacts.LAST_TIME_CONTACTED, startTime + 3 ), cv(RawContacts._ID, rid4, RawContacts.TIMES_CONTACTED, 0, RawContacts.LAST_TIME_CONTACTED, null // 4 wasn't touched. ) ); // Lastly, check the contacts table. // Note contact1.TIMES_CONTACTED = 4, even though raw_contact1.TIMES_CONTACTED + // raw_contact1.TIMES_CONTACTED = 5, because in test 2, data 1a and data 2a were touched // at once. assertStoredValuesWithProjection(Contacts.CONTENT_URI, cv(Contacts._ID, cid1, Contacts.TIMES_CONTACTED, 4, Contacts.LAST_TIME_CONTACTED, startTime + 3 ), cv(Contacts._ID, cid3, Contacts.TIMES_CONTACTED, 2, Contacts.LAST_TIME_CONTACTED, startTime + 3 ), cv(Contacts._ID, cid4, Contacts.TIMES_CONTACTED, 0, Contacts.LAST_TIME_CONTACTED, 0 // For contacts, the default is 0, not null. ) ); // Let's test the delete too. assertTrue(mResolver.delete(DataUsageFeedback.DELETE_USAGE_URI, null, null) > 0); // Now there's no frequent. assertRowCount(0, Contacts.CONTENT_STREQUENT_URI, null, null); // No rows in the stats table. assertStoredValuesDb("SELECT " + DataUsageStatColumns.DATA_ID + " FROM " + Tables.DATA_USAGE_STAT, null, new ContentValues[0]); // The following values should all be 0 or null. assertRowCount(0, Contacts.CONTENT_URI, Contacts.TIMES_CONTACTED + ">0", null); assertRowCount(0, Contacts.CONTENT_URI, Contacts.LAST_TIME_CONTACTED + ">0", null); assertRowCount(0, RawContacts.CONTENT_URI, RawContacts.TIMES_CONTACTED + ">0", null); assertRowCount(0, RawContacts.CONTENT_URI, RawContacts.LAST_TIME_CONTACTED + ">0", null); // Calling it when there's no usage stats will still return a positive value. assertTrue(mResolver.delete(DataUsageFeedback.DELETE_USAGE_URI, null, null) > 0); } private Cursor queryGroupMemberships(Account account) { Cursor c = mResolver.query(maybeAddAccountQueryParameters(Data.CONTENT_URI, account), new String[]{GroupMembership.GROUP_ROW_ID, GroupMembership.RAW_CONTACT_ID}, Data.MIMETYPE + "=?", new String[]{GroupMembership.CONTENT_ITEM_TYPE}, GroupMembership.GROUP_SOURCE_ID); return c; } private String readToEnd(FileInputStream inputStream) { try { System.out.println("DECLARED INPUT STREAM LENGTH: " + inputStream.available()); int ch; StringBuilder stringBuilder = new StringBuilder(); int index = 0; while (true) { ch = inputStream.read(); System.out.println("READ CHARACTER: " + index + " " + ch); if (ch == -1) { break; } stringBuilder.append((char)ch); index++; } return stringBuilder.toString(); } catch (IOException e) { return null; } } private void assertQueryParameter(String uriString, String parameter, String expectedValue) { assertEquals(expectedValue, ContactsProvider2.getQueryParameter( Uri.parse(uriString), parameter)); } private long createContact(ContentValues values, String firstName, String givenName, String phoneNumber, String email, int presenceStatus, int timesContacted, int starred, long groupId, int chatMode) { return createContact(values, firstName, givenName, phoneNumber, email, presenceStatus, timesContacted, starred, groupId, chatMode, false); } private long createContact(ContentValues values, String firstName, String givenName, String phoneNumber, String email, int presenceStatus, int timesContacted, int starred, long groupId, int chatMode, boolean isUserProfile) { return queryContactId(createRawContact(values, firstName, givenName, phoneNumber, email, presenceStatus, timesContacted, starred, groupId, chatMode, isUserProfile)); } private long createRawContact(ContentValues values, String firstName, String givenName, String phoneNumber, String email, int presenceStatus, int timesContacted, int starred, long groupId, int chatMode) { long rawContactId = createRawContact(values, phoneNumber, email, presenceStatus, timesContacted, starred, groupId, chatMode); insertStructuredName(rawContactId, firstName, givenName); return rawContactId; } private long createRawContact(ContentValues values, String firstName, String givenName, String phoneNumber, String email, int presenceStatus, int timesContacted, int starred, long groupId, int chatMode, boolean isUserProfile) { long rawContactId = createRawContact(values, phoneNumber, email, presenceStatus, timesContacted, starred, groupId, chatMode, isUserProfile); insertStructuredName(rawContactId, firstName, givenName); return rawContactId; } private long createRawContact(ContentValues values, String phoneNumber, String email, int presenceStatus, int timesContacted, int starred, long groupId, int chatMode) { return createRawContact(values, phoneNumber, email, presenceStatus, timesContacted, starred, groupId, chatMode, false); } private long createRawContact(ContentValues values, String phoneNumber, String email, int presenceStatus, int timesContacted, int starred, long groupId, int chatMode, boolean isUserProfile) { values.put(RawContacts.STARRED, starred); values.put(RawContacts.SEND_TO_VOICEMAIL, 1); values.put(RawContacts.CUSTOM_RINGTONE, "beethoven5"); values.put(RawContacts.TIMES_CONTACTED, timesContacted); Uri insertionUri = isUserProfile ? Profile.CONTENT_RAW_CONTACTS_URI : RawContacts.CONTENT_URI; Uri rawContactUri = mResolver.insert(insertionUri, values); long rawContactId = ContentUris.parseId(rawContactUri); Uri photoUri = insertPhoto(rawContactId); long photoId = ContentUris.parseId(photoUri); values.put(Contacts.PHOTO_ID, photoId); if (!TextUtils.isEmpty(phoneNumber)) { insertPhoneNumber(rawContactId, phoneNumber); } if (!TextUtils.isEmpty(email)) { insertEmail(rawContactId, email); } insertStatusUpdate(Im.PROTOCOL_GOOGLE_TALK, null, email, presenceStatus, "hacking", chatMode, isUserProfile); if (groupId != 0) { insertGroupMembership(rawContactId, groupId); } return rawContactId; } /** * Creates a raw contact with pre-set values under the user's profile. * @param profileValues Values to be used to create the entry (common values will be * automatically populated in createRawContact()). * @return the raw contact ID that was created. */ private long createBasicProfileContact(ContentValues profileValues) { long profileRawContactId = createRawContact(profileValues, "Mia", "Prophyl", "18005554411", "mia.prophyl@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA, true); profileValues.put(Contacts.DISPLAY_NAME, "Mia Prophyl"); return profileRawContactId; } /** * Creates a raw contact with pre-set values that is not under the user's profile. * @param nonProfileValues Values to be used to create the entry (common values will be * automatically populated in createRawContact()). * @return the raw contact ID that was created. */ private long createBasicNonProfileContact(ContentValues nonProfileValues) { long nonProfileRawContactId = createRawContact(nonProfileValues, "John", "Doe", "18004664411", "goog411@acme.com", StatusUpdates.INVISIBLE, 4, 1, 0, StatusUpdates.CAPABILITY_HAS_CAMERA, false); nonProfileValues.put(Contacts.DISPLAY_NAME, "John Doe"); return nonProfileRawContactId; } private void putDataValues(ContentValues values, long rawContactId) { values.put(Data.RAW_CONTACT_ID, rawContactId); values.put(Data.MIMETYPE, "testmimetype"); values.put(Data.RES_PACKAGE, "oldpackage"); values.put(Data.IS_PRIMARY, 1); values.put(Data.IS_SUPER_PRIMARY, 1); values.put(Data.DATA1, "one"); values.put(Data.DATA2, "two"); values.put(Data.DATA3, "three"); values.put(Data.DATA4, "four"); values.put(Data.DATA5, "five"); values.put(Data.DATA6, "six"); values.put(Data.DATA7, "seven"); values.put(Data.DATA8, "eight"); values.put(Data.DATA9, "nine"); values.put(Data.DATA10, "ten"); values.put(Data.DATA11, "eleven"); values.put(Data.DATA12, "twelve"); values.put(Data.DATA13, "thirteen"); values.put(Data.DATA14, "fourteen"); values.put(Data.DATA15, "fifteen"); values.put(Data.SYNC1, "sync1"); values.put(Data.SYNC2, "sync2"); values.put(Data.SYNC3, "sync3"); values.put(Data.SYNC4, "sync4"); } /** * @param data1 email address or phone number * @param usageType One of {@link DataUsageFeedback#USAGE_TYPE} * @param values ContentValues for this feedback. Useful for incrementing * {Contacts#TIMES_CONTACTED} in the ContentValue. Can be null. */ private void sendFeedback(String data1, String usageType, ContentValues values) { final long dataId = getStoredLongValue(Data.CONTENT_URI, Data.DATA1 + "=?", new String[] { data1 }, Data._ID); MoreAsserts.assertNotEqual(0, updateDataUsageFeedback(usageType, dataId)); if (values != null && values.containsKey(Contacts.TIMES_CONTACTED)) { values.put(Contacts.TIMES_CONTACTED, values.getAsInteger(Contacts.TIMES_CONTACTED) + 1); } } private void updateDataUsageFeedback(String usageType, Uri resultUri) { final long id = ContentUris.parseId(resultUri); final boolean successful = updateDataUsageFeedback(usageType, id) > 0; assertTrue(successful); } private int updateDataUsageFeedback(String usageType, long... ids) { final StringBuilder idList = new StringBuilder(); for (long id : ids) { if (idList.length() > 0) idList.append(","); idList.append(id); } return mResolver.update(DataUsageFeedback.FEEDBACK_URI.buildUpon() .appendPath(idList.toString()) .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType) .build(), new ContentValues(), null, null); } }