| /* |
| * Copyright (C) 2010 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.contacts.common.model; |
| |
| import android.content.ContentUris; |
| import android.net.Uri; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.CommonDataKinds.GroupMembership; |
| import android.provider.ContactsContract.CommonDataKinds.StructuredName; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Data; |
| import android.provider.ContactsContract.DisplayNameSources; |
| import android.provider.ContactsContract.RawContacts; |
| import android.provider.ContactsContract.StatusUpdates; |
| import android.test.LoaderTestCase; |
| import android.test.suitebuilder.annotation.LargeTest; |
| |
| import com.android.contacts.common.compat.CompatUtils; |
| import com.android.contacts.common.model.AccountTypeManager; |
| import com.android.contacts.common.model.account.AccountType; |
| import com.android.contacts.common.model.account.AccountWithDataSet; |
| import com.android.contacts.common.model.account.BaseAccountType; |
| import com.android.contacts.common.testing.InjectedServices; |
| import com.android.contacts.common.test.mocks.ContactsMockContext; |
| import com.android.contacts.common.test.mocks.MockContentProvider; |
| import com.android.contacts.common.test.mocks.MockContentProvider.Query; |
| import com.android.contacts.common.test.mocks.MockAccountTypeManager; |
| import com.android.contacts.common.util.Constants; |
| |
| import com.google.common.collect.Lists; |
| |
| import java.util.List; |
| |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| /** |
| * Runs ContactLoader tests for the the contact-detail and editor view. |
| */ |
| @LargeTest |
| public class ContactLoaderTest extends LoaderTestCase { |
| private static final long CONTACT_ID = 1; |
| private static final long RAW_CONTACT_ID = 11; |
| private static final long DATA_ID = 21; |
| private static final String LOOKUP_KEY = "aa%12%@!"; |
| |
| private ContactsMockContext mMockContext; |
| private MockContentProvider mContactsProvider; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mMockContext = new ContactsMockContext(getContext()); |
| mContactsProvider = mMockContext.getContactsProvider(); |
| |
| InjectedServices services = new InjectedServices(); |
| AccountType accountType = new BaseAccountType() { |
| @Override |
| public boolean areContactsWritable() { |
| return false; |
| } |
| }; |
| accountType.accountType = "mockAccountType"; |
| |
| AccountWithDataSet account = |
| new AccountWithDataSet("mockAccountName", "mockAccountType", null); |
| |
| AccountTypeManager.setInstanceForTest( |
| new MockAccountTypeManager( |
| new AccountType[]{accountType}, new AccountWithDataSet[]{account})); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| mMockContext = null; |
| mContactsProvider = null; |
| super.tearDown(); |
| } |
| |
| private Contact assertLoadContact(Uri uri) { |
| final ContactLoader loader = new ContactLoader(mMockContext, uri, true); |
| return getLoaderResultSynchronously(loader); |
| } |
| |
| public void testNullUri() { |
| Contact result = assertLoadContact(null); |
| assertTrue(result.isError()); |
| } |
| |
| public void testEmptyUri() { |
| Contact result = assertLoadContact(Uri.EMPTY); |
| assertTrue(result.isError()); |
| } |
| |
| public void testInvalidUri() { |
| Contact result = assertLoadContact(Uri.parse("content://wtf")); |
| assertTrue(result.isError()); |
| } |
| |
| public void testLoadContactWithContactIdUri() { |
| // Use content Uris that only contain the ID |
| final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID); |
| final Uri entityUri = Uri.withAppendedPath(baseUri, Contacts.Entity.CONTENT_DIRECTORY); |
| final Uri lookupUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY), |
| CONTACT_ID); |
| |
| ContactQueries queries = new ContactQueries(); |
| mContactsProvider.expectTypeQuery(baseUri, Contacts.CONTENT_ITEM_TYPE); |
| queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY); |
| |
| Contact contact = assertLoadContact(baseUri); |
| |
| assertEquals(CONTACT_ID, contact.getId()); |
| assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals(LOOKUP_KEY, contact.getLookupKey()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| assertEquals(1, contact.getStatuses().size()); |
| mContactsProvider.verify(); |
| } |
| |
| public void testLoadContactWithOldStyleUri() { |
| // Use content Uris that only contain the ID but use the format used in Donut |
| final Uri legacyUri = ContentUris.withAppendedId( |
| Uri.parse("content://contacts"), RAW_CONTACT_ID); |
| final Uri rawContactUri = ContentUris.withAppendedId( |
| RawContacts.CONTENT_URI, RAW_CONTACT_ID); |
| final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID); |
| final Uri lookupUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY), |
| CONTACT_ID); |
| final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY); |
| |
| ContactQueries queries = new ContactQueries(); |
| queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, CONTACT_ID, LOOKUP_KEY); |
| queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY); |
| |
| Contact contact = assertLoadContact(legacyUri); |
| |
| assertEquals(CONTACT_ID, contact.getId()); |
| assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals(LOOKUP_KEY, contact.getLookupKey()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| assertEquals(1, contact.getStatuses().size()); |
| if (CompatUtils.isMarshmallowCompatible()) { |
| assertEquals( |
| 1, contact.getRawContacts().get(0).getDataItems().get(0).getCarrierPresence()); |
| } |
| mContactsProvider.verify(); |
| } |
| |
| public void testLoadContactWithRawContactIdUri() { |
| // Use content Uris that only contain the ID but use the format used in Donut |
| final Uri rawContactUri = ContentUris.withAppendedId( |
| RawContacts.CONTENT_URI, RAW_CONTACT_ID); |
| final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID); |
| final Uri lookupUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY), |
| CONTACT_ID); |
| final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY); |
| |
| ContactQueries queries = new ContactQueries(); |
| mContactsProvider.expectTypeQuery(rawContactUri, RawContacts.CONTENT_ITEM_TYPE); |
| queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, CONTACT_ID, LOOKUP_KEY); |
| queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY); |
| |
| Contact contact = assertLoadContact(rawContactUri); |
| |
| assertEquals(CONTACT_ID, contact.getId()); |
| assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals(LOOKUP_KEY, contact.getLookupKey()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| assertEquals(1, contact.getStatuses().size()); |
| mContactsProvider.verify(); |
| } |
| |
| public void testLoadContactWithContactLookupUri() { |
| // Use lookup-style Uris that do not contain the Contact-ID |
| final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID); |
| final Uri lookupNoIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY); |
| final Uri lookupUri = ContentUris.withAppendedId(lookupNoIdUri, CONTACT_ID); |
| final Uri entityUri = Uri.withAppendedPath( |
| lookupNoIdUri, Contacts.Entity.CONTENT_DIRECTORY); |
| |
| ContactQueries queries = new ContactQueries(); |
| mContactsProvider.expectTypeQuery(lookupNoIdUri, Contacts.CONTENT_ITEM_TYPE); |
| queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY); |
| |
| Contact contact = assertLoadContact(lookupNoIdUri); |
| |
| assertEquals(CONTACT_ID, contact.getId()); |
| assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals(LOOKUP_KEY, contact.getLookupKey()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| assertEquals(1, contact.getStatuses().size()); |
| mContactsProvider.verify(); |
| } |
| |
| public void testLoadContactWithContactLookupAndIdUri() { |
| // Use lookup-style Uris that also contain the Contact-ID |
| final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID); |
| final Uri lookupUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY), |
| CONTACT_ID); |
| final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY); |
| |
| ContactQueries queries = new ContactQueries(); |
| mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE); |
| queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY); |
| |
| Contact contact = assertLoadContact(lookupUri); |
| |
| assertEquals(CONTACT_ID, contact.getId()); |
| assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals(LOOKUP_KEY, contact.getLookupKey()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| assertEquals(1, contact.getStatuses().size()); |
| mContactsProvider.verify(); |
| } |
| |
| public void testLoadContactWithContactLookupWithIncorrectIdUri() { |
| // Use lookup-style Uris that contain incorrect Contact-ID |
| // (we want to ensure that still the correct contact is chosen) |
| final long wrongContactId = 2; |
| final long wrongRawContactId = 12; |
| |
| final String wrongLookupKey = "ab%12%@!"; |
| final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID); |
| final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId); |
| final Uri lookupUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY), |
| CONTACT_ID); |
| final Uri lookupWithWrongIdUri = ContentUris.withAppendedId( |
| Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY), |
| wrongContactId); |
| final Uri entityUri = Uri.withAppendedPath(lookupWithWrongIdUri, |
| Contacts.Entity.CONTENT_DIRECTORY); |
| |
| ContactQueries queries = new ContactQueries(); |
| mContactsProvider.expectTypeQuery(lookupWithWrongIdUri, Contacts.CONTENT_ITEM_TYPE); |
| queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY); |
| |
| Contact contact = assertLoadContact(lookupWithWrongIdUri); |
| |
| assertEquals(CONTACT_ID, contact.getId()); |
| assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals(LOOKUP_KEY, contact.getLookupKey()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| assertEquals(1, contact.getStatuses().size()); |
| |
| mContactsProvider.verify(); |
| } |
| |
| public void testLoadContactReturnDirectoryContactWithoutDisplayName() throws JSONException { |
| // Use lookup-style Uri that contains encoded json object which encapsulates the |
| // directory contact. The test json object is: |
| // { |
| // display_name_source": 40, |
| // "vnd.android.cursor.item\/contact":{"email":{"data1":"test@google.com" }} |
| // } |
| JSONObject itemJson = new JSONObject(); |
| itemJson.put("email", new JSONObject().put("data1", "test@google.com")); |
| JSONObject json = new JSONObject(); |
| json.put(Contacts.NAME_RAW_CONTACT_ID, CONTACT_ID); |
| json.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME); |
| json.put(Contacts.CONTENT_ITEM_TYPE, itemJson); |
| |
| final Uri lookupUri = Contacts.CONTENT_LOOKUP_URI.buildUpon() |
| .encodedFragment(json.toString()) |
| .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, "1") |
| .appendPath(Constants.LOOKUP_URI_ENCODED).build(); |
| |
| mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE); |
| Contact contact = assertLoadContact(lookupUri); |
| |
| assertEquals(-1, contact.getId()); |
| assertEquals(-1, contact.getNameRawContactId()); |
| assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource()); |
| assertEquals("", contact.getDisplayName()); |
| assertEquals(lookupUri, contact.getLookupUri()); |
| assertEquals(1, contact.getRawContacts().size()); |
| mContactsProvider.verify(); |
| } |
| |
| class ContactQueries { |
| public void fetchAllData( |
| Uri baseUri, long contactId, long rawContactId, long dataId, String encodedLookup) { |
| final String[] COLUMNS_INTERNAL = new String[] { |
| Contacts.NAME_RAW_CONTACT_ID, Contacts.DISPLAY_NAME_SOURCE, |
| Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME, |
| Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.PHONETIC_NAME, |
| Contacts.PHOTO_ID, Contacts.STARRED, Contacts.CONTACT_PRESENCE, |
| Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP, |
| Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL, |
| |
| Contacts.Entity.CONTACT_ID, |
| Contacts.Entity.RAW_CONTACT_ID, |
| |
| RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, |
| RawContacts.DATA_SET, |
| RawContacts.DIRTY, RawContacts.VERSION, RawContacts.SOURCE_ID, |
| RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4, |
| RawContacts.DELETED, |
| |
| Contacts.Entity.DATA_ID, |
| |
| 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.DATA_VERSION, Data.IS_PRIMARY, |
| Data.IS_SUPER_PRIMARY, Data.MIMETYPE, |
| |
| GroupMembership.GROUP_SOURCE_ID, |
| |
| Data.PRESENCE, Data.CHAT_CAPABILITY, |
| Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON, |
| Data.STATUS_LABEL, Data.STATUS_TIMESTAMP, |
| |
| Contacts.PHOTO_URI, |
| |
| Contacts.SEND_TO_VOICEMAIL, |
| Contacts.CUSTOM_RINGTONE, |
| Contacts.IS_USER_PROFILE, |
| |
| Data.TIMES_USED, |
| Data.LAST_TIME_USED |
| }; |
| |
| List<String> projectionList = Lists.newArrayList(COLUMNS_INTERNAL); |
| if (CompatUtils.isMarshmallowCompatible()) { |
| projectionList.add(Data.CARRIER_PRESENCE); |
| } |
| final String[] COLUMNS = projectionList.toArray(new String[projectionList.size()]); |
| |
| final Object[] ROWS_INTERNAL = new Object[] { |
| rawContactId, 40, |
| "aa%12%@!", "John Doe", "Doe, John", "jdo", |
| 0, 0, StatusUpdates.AVAILABLE, |
| "Having lunch", 0, |
| "mockPkg1", 10, |
| |
| contactId, |
| rawContactId, |
| |
| "mockAccountName", "mockAccountType", null, |
| 0, 1, 0, |
| "sync1", "sync2", "sync3", "sync4", |
| 0, |
| |
| dataId, |
| |
| "dat1", "dat2", "dat3", "dat4", "dat5", |
| "dat6", "dat7", "dat8", "dat9", "dat10", |
| "dat11", "dat12", "dat13", "dat14", "dat15", |
| "syn1", "syn2", "syn3", "syn4", |
| |
| 0, 0, |
| 0, StructuredName.CONTENT_ITEM_TYPE, |
| |
| "groupId", |
| |
| StatusUpdates.INVISIBLE, null, |
| "Having dinner", "mockPkg3", 0, |
| 20, 0, |
| |
| "content:some.photo.uri", |
| |
| 0, |
| null, |
| 0, |
| |
| 0, |
| 0 |
| }; |
| |
| List<Object> rowsList = Lists.newArrayList(ROWS_INTERNAL); |
| if (CompatUtils.isMarshmallowCompatible()) { |
| rowsList.add(Data.CARRIER_PRESENCE_VT_CAPABLE); |
| } |
| final Object[] ROWS = rowsList.toArray(new Object[rowsList.size()]); |
| |
| mContactsProvider.expectQuery(baseUri) |
| .withProjection(COLUMNS) |
| .withSortOrder(Contacts.Entity.RAW_CONTACT_ID) |
| .returnRow(ROWS); |
| } |
| |
| void fetchLookupAndId(final Uri sourceUri, final long expectedContactId, |
| final String expectedEncodedLookup) { |
| mContactsProvider.expectQuery(sourceUri) |
| .withProjection(Contacts.LOOKUP_KEY, Contacts._ID) |
| .returnRow(expectedEncodedLookup, expectedContactId); |
| } |
| |
| void fetchContactIdAndLookupFromRawContactUri(final Uri rawContactUri, |
| final long expectedContactId, final String expectedEncodedLookup) { |
| // TODO: use a lighter query by joining rawcontacts with contacts in provider |
| // (See ContactContracts.java) |
| final Uri dataUri = Uri.withAppendedPath(rawContactUri, |
| RawContacts.Data.CONTENT_DIRECTORY); |
| mContactsProvider.expectQuery(dataUri) |
| .withProjection(RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY) |
| .returnRow(expectedContactId, expectedEncodedLookup); |
| } |
| } |
| } |