Home | History | Annotate | Download | only in model
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.common.model;
     18 
     19 import android.content.ContentUris;
     20 import android.net.Uri;
     21 import android.provider.ContactsContract;
     22 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     23 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     24 import android.provider.ContactsContract.Contacts;
     25 import android.provider.ContactsContract.Data;
     26 import android.provider.ContactsContract.DisplayNameSources;
     27 import android.provider.ContactsContract.RawContacts;
     28 import android.provider.ContactsContract.StatusUpdates;
     29 import android.test.LoaderTestCase;
     30 import android.test.suitebuilder.annotation.LargeTest;
     31 
     32 import com.android.contacts.common.compat.CompatUtils;
     33 import com.android.contacts.common.model.AccountTypeManager;
     34 import com.android.contacts.common.model.account.AccountType;
     35 import com.android.contacts.common.model.account.AccountWithDataSet;
     36 import com.android.contacts.common.model.account.BaseAccountType;
     37 import com.android.contacts.common.testing.InjectedServices;
     38 import com.android.contacts.common.test.mocks.ContactsMockContext;
     39 import com.android.contacts.common.test.mocks.MockContentProvider;
     40 import com.android.contacts.common.test.mocks.MockContentProvider.Query;
     41 import com.android.contacts.common.test.mocks.MockAccountTypeManager;
     42 import com.android.contacts.common.util.Constants;
     43 
     44 import com.google.common.collect.Lists;
     45 
     46 import java.util.List;
     47 
     48 import org.json.JSONException;
     49 import org.json.JSONObject;
     50 
     51 /**
     52  * Runs ContactLoader tests for the the contact-detail and editor view.
     53  */
     54 @LargeTest
     55 public class ContactLoaderTest extends LoaderTestCase {
     56     private static final long CONTACT_ID = 1;
     57     private static final long RAW_CONTACT_ID = 11;
     58     private static final long DATA_ID = 21;
     59     private static final String LOOKUP_KEY = "aa%12%@!";
     60 
     61     private ContactsMockContext mMockContext;
     62     private MockContentProvider mContactsProvider;
     63 
     64     @Override
     65     protected void setUp() throws Exception {
     66         super.setUp();
     67         mMockContext = new ContactsMockContext(getContext());
     68         mContactsProvider = mMockContext.getContactsProvider();
     69 
     70         InjectedServices services = new InjectedServices();
     71         AccountType accountType = new BaseAccountType() {
     72             @Override
     73             public boolean areContactsWritable() {
     74                 return false;
     75             }
     76         };
     77         accountType.accountType = "mockAccountType";
     78 
     79         AccountWithDataSet account =
     80                 new AccountWithDataSet("mockAccountName", "mockAccountType", null);
     81 
     82         AccountTypeManager.setInstanceForTest(
     83                 new MockAccountTypeManager(
     84                         new AccountType[]{accountType}, new AccountWithDataSet[]{account}));
     85     }
     86 
     87     @Override
     88     protected void tearDown() throws Exception {
     89         mMockContext = null;
     90         mContactsProvider = null;
     91         super.tearDown();
     92     }
     93 
     94     private Contact assertLoadContact(Uri uri) {
     95         final ContactLoader loader = new ContactLoader(mMockContext, uri, true);
     96         return getLoaderResultSynchronously(loader);
     97     }
     98 
     99     public void testNullUri() {
    100         Contact result = assertLoadContact(null);
    101         assertTrue(result.isError());
    102     }
    103 
    104     public void testEmptyUri() {
    105         Contact result = assertLoadContact(Uri.EMPTY);
    106         assertTrue(result.isError());
    107     }
    108 
    109     public void testInvalidUri() {
    110         Contact result = assertLoadContact(Uri.parse("content://wtf"));
    111         assertTrue(result.isError());
    112     }
    113 
    114     public void testLoadContactWithContactIdUri() {
    115         // Use content Uris that only contain the ID
    116         final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
    117         final Uri entityUri = Uri.withAppendedPath(baseUri, Contacts.Entity.CONTENT_DIRECTORY);
    118         final Uri lookupUri = ContentUris.withAppendedId(
    119                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
    120                 CONTACT_ID);
    121 
    122         ContactQueries queries = new ContactQueries();
    123         mContactsProvider.expectTypeQuery(baseUri, Contacts.CONTENT_ITEM_TYPE);
    124         queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
    125 
    126         Contact contact = assertLoadContact(baseUri);
    127 
    128         assertEquals(CONTACT_ID, contact.getId());
    129         assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
    130         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    131         assertEquals(LOOKUP_KEY, contact.getLookupKey());
    132         assertEquals(lookupUri, contact.getLookupUri());
    133         assertEquals(1, contact.getRawContacts().size());
    134         assertEquals(1, contact.getStatuses().size());
    135         mContactsProvider.verify();
    136     }
    137 
    138     public void testLoadContactWithOldStyleUri() {
    139         // Use content Uris that only contain the ID but use the format used in Donut
    140         final Uri legacyUri = ContentUris.withAppendedId(
    141                 Uri.parse("content://contacts"), RAW_CONTACT_ID);
    142         final Uri rawContactUri = ContentUris.withAppendedId(
    143                 RawContacts.CONTENT_URI, RAW_CONTACT_ID);
    144         final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
    145         final Uri lookupUri = ContentUris.withAppendedId(
    146                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
    147                 CONTACT_ID);
    148         final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
    149 
    150         ContactQueries queries = new ContactQueries();
    151         queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, CONTACT_ID, LOOKUP_KEY);
    152         queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
    153 
    154         Contact contact = assertLoadContact(legacyUri);
    155 
    156         assertEquals(CONTACT_ID, contact.getId());
    157         assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
    158         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    159         assertEquals(LOOKUP_KEY, contact.getLookupKey());
    160         assertEquals(lookupUri, contact.getLookupUri());
    161         assertEquals(1, contact.getRawContacts().size());
    162         assertEquals(1, contact.getStatuses().size());
    163         if (CompatUtils.isMarshmallowCompatible()) {
    164             assertEquals(
    165                     1, contact.getRawContacts().get(0).getDataItems().get(0).getCarrierPresence());
    166         }
    167         mContactsProvider.verify();
    168     }
    169 
    170     public void testLoadContactWithRawContactIdUri() {
    171         // Use content Uris that only contain the ID but use the format used in Donut
    172         final Uri rawContactUri = ContentUris.withAppendedId(
    173                 RawContacts.CONTENT_URI, RAW_CONTACT_ID);
    174         final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
    175         final Uri lookupUri = ContentUris.withAppendedId(
    176                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
    177                 CONTACT_ID);
    178         final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
    179 
    180         ContactQueries queries = new ContactQueries();
    181         mContactsProvider.expectTypeQuery(rawContactUri, RawContacts.CONTENT_ITEM_TYPE);
    182         queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, CONTACT_ID, LOOKUP_KEY);
    183         queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
    184 
    185         Contact contact = assertLoadContact(rawContactUri);
    186 
    187         assertEquals(CONTACT_ID, contact.getId());
    188         assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
    189         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    190         assertEquals(LOOKUP_KEY, contact.getLookupKey());
    191         assertEquals(lookupUri, contact.getLookupUri());
    192         assertEquals(1, contact.getRawContacts().size());
    193         assertEquals(1, contact.getStatuses().size());
    194         mContactsProvider.verify();
    195     }
    196 
    197     public void testLoadContactWithContactLookupUri() {
    198         // Use lookup-style Uris that do not contain the Contact-ID
    199         final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
    200         final Uri lookupNoIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY);
    201         final Uri lookupUri = ContentUris.withAppendedId(lookupNoIdUri, CONTACT_ID);
    202         final Uri entityUri = Uri.withAppendedPath(
    203                 lookupNoIdUri, Contacts.Entity.CONTENT_DIRECTORY);
    204 
    205         ContactQueries queries = new ContactQueries();
    206         mContactsProvider.expectTypeQuery(lookupNoIdUri, Contacts.CONTENT_ITEM_TYPE);
    207         queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
    208 
    209         Contact contact = assertLoadContact(lookupNoIdUri);
    210 
    211         assertEquals(CONTACT_ID, contact.getId());
    212         assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
    213         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    214         assertEquals(LOOKUP_KEY, contact.getLookupKey());
    215         assertEquals(lookupUri, contact.getLookupUri());
    216         assertEquals(1, contact.getRawContacts().size());
    217         assertEquals(1, contact.getStatuses().size());
    218         mContactsProvider.verify();
    219     }
    220 
    221     public void testLoadContactWithContactLookupAndIdUri() {
    222         // Use lookup-style Uris that also contain the Contact-ID
    223         final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
    224         final Uri lookupUri = ContentUris.withAppendedId(
    225                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
    226                 CONTACT_ID);
    227         final Uri entityUri = Uri.withAppendedPath(lookupUri, Contacts.Entity.CONTENT_DIRECTORY);
    228 
    229         ContactQueries queries = new ContactQueries();
    230         mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE);
    231         queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
    232 
    233         Contact contact = assertLoadContact(lookupUri);
    234 
    235         assertEquals(CONTACT_ID, contact.getId());
    236         assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
    237         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    238         assertEquals(LOOKUP_KEY, contact.getLookupKey());
    239         assertEquals(lookupUri, contact.getLookupUri());
    240         assertEquals(1, contact.getRawContacts().size());
    241         assertEquals(1, contact.getStatuses().size());
    242         mContactsProvider.verify();
    243     }
    244 
    245     public void testLoadContactWithContactLookupWithIncorrectIdUri() {
    246         // Use lookup-style Uris that contain incorrect Contact-ID
    247         // (we want to ensure that still the correct contact is chosen)
    248         final long wrongContactId = 2;
    249         final long wrongRawContactId = 12;
    250 
    251         final String wrongLookupKey = "ab%12%@!";
    252         final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, CONTACT_ID);
    253         final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId);
    254         final Uri lookupUri = ContentUris.withAppendedId(
    255                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
    256                 CONTACT_ID);
    257         final Uri lookupWithWrongIdUri = ContentUris.withAppendedId(
    258                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, LOOKUP_KEY),
    259                 wrongContactId);
    260         final Uri entityUri = Uri.withAppendedPath(lookupWithWrongIdUri,
    261                 Contacts.Entity.CONTENT_DIRECTORY);
    262 
    263         ContactQueries queries = new ContactQueries();
    264         mContactsProvider.expectTypeQuery(lookupWithWrongIdUri, Contacts.CONTENT_ITEM_TYPE);
    265         queries.fetchAllData(entityUri, CONTACT_ID, RAW_CONTACT_ID, DATA_ID, LOOKUP_KEY);
    266 
    267         Contact contact = assertLoadContact(lookupWithWrongIdUri);
    268 
    269         assertEquals(CONTACT_ID, contact.getId());
    270         assertEquals(RAW_CONTACT_ID, contact.getNameRawContactId());
    271         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    272         assertEquals(LOOKUP_KEY, contact.getLookupKey());
    273         assertEquals(lookupUri, contact.getLookupUri());
    274         assertEquals(1, contact.getRawContacts().size());
    275         assertEquals(1, contact.getStatuses().size());
    276 
    277         mContactsProvider.verify();
    278     }
    279 
    280     public void testLoadContactReturnDirectoryContactWithoutDisplayName() throws JSONException {
    281         // Use lookup-style Uri that contains encoded json object which encapsulates the
    282         // directory contact. The test json object is:
    283         // {
    284         //   display_name_source": 40,
    285         //   "vnd.android.cursor.item\/contact":{"email":{"data1":"test (at) google.com" }}
    286         // }
    287         JSONObject itemJson = new JSONObject();
    288         itemJson.put("email", new JSONObject().put("data1", "test (at) google.com"));
    289         JSONObject json = new JSONObject();
    290         json.put(Contacts.NAME_RAW_CONTACT_ID, CONTACT_ID);
    291         json.put(Contacts.DISPLAY_NAME_SOURCE, DisplayNameSources.STRUCTURED_NAME);
    292         json.put(Contacts.CONTENT_ITEM_TYPE, itemJson);
    293 
    294         final Uri lookupUri = Contacts.CONTENT_LOOKUP_URI.buildUpon()
    295                 .encodedFragment(json.toString())
    296                 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, "1")
    297                 .appendPath(Constants.LOOKUP_URI_ENCODED).build();
    298 
    299         mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE);
    300         Contact contact = assertLoadContact(lookupUri);
    301 
    302         assertEquals(-1, contact.getId());
    303         assertEquals(-1, contact.getNameRawContactId());
    304         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
    305         assertEquals("", contact.getDisplayName());
    306         assertEquals(lookupUri, contact.getLookupUri());
    307         assertEquals(1, contact.getRawContacts().size());
    308         mContactsProvider.verify();
    309     }
    310 
    311     class ContactQueries {
    312         public void fetchAllData(
    313                 Uri baseUri, long contactId, long rawContactId, long dataId, String encodedLookup) {
    314             final String[] COLUMNS_INTERNAL = new String[] {
    315                     Contacts.NAME_RAW_CONTACT_ID, Contacts.DISPLAY_NAME_SOURCE,
    316                     Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME,
    317                     Contacts.DISPLAY_NAME_ALTERNATIVE, Contacts.PHONETIC_NAME,
    318                     Contacts.PHOTO_ID, Contacts.STARRED, Contacts.CONTACT_PRESENCE,
    319                     Contacts.CONTACT_STATUS, Contacts.CONTACT_STATUS_TIMESTAMP,
    320                     Contacts.CONTACT_STATUS_RES_PACKAGE, Contacts.CONTACT_STATUS_LABEL,
    321 
    322                     Contacts.Entity.CONTACT_ID,
    323                     Contacts.Entity.RAW_CONTACT_ID,
    324 
    325                     RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
    326                     RawContacts.DATA_SET,
    327                     RawContacts.DIRTY, RawContacts.VERSION, RawContacts.SOURCE_ID,
    328                     RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4,
    329                     RawContacts.DELETED,
    330 
    331                     Contacts.Entity.DATA_ID,
    332 
    333                     Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5,
    334                     Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10,
    335                     Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15,
    336                     Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4,
    337                     Data.DATA_VERSION, Data.IS_PRIMARY,
    338                     Data.IS_SUPER_PRIMARY, Data.MIMETYPE,
    339 
    340                     GroupMembership.GROUP_SOURCE_ID,
    341 
    342                     Data.PRESENCE, Data.CHAT_CAPABILITY,
    343                     Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON,
    344                     Data.STATUS_LABEL, Data.STATUS_TIMESTAMP,
    345 
    346                     Contacts.PHOTO_URI,
    347 
    348                     Contacts.SEND_TO_VOICEMAIL,
    349                     Contacts.CUSTOM_RINGTONE,
    350                     Contacts.IS_USER_PROFILE,
    351 
    352                     Data.TIMES_USED,
    353                     Data.LAST_TIME_USED
    354             };
    355 
    356             List<String> projectionList = Lists.newArrayList(COLUMNS_INTERNAL);
    357             if (CompatUtils.isMarshmallowCompatible()) {
    358                 projectionList.add(Data.CARRIER_PRESENCE);
    359             }
    360             final String[] COLUMNS = projectionList.toArray(new String[projectionList.size()]);
    361 
    362             final Object[] ROWS_INTERNAL = new Object[] {
    363                     rawContactId, 40,
    364                     "aa%12%@!", "John Doe", "Doe, John", "jdo",
    365                     0, 0, StatusUpdates.AVAILABLE,
    366                     "Having lunch", 0,
    367                     "mockPkg1", 10,
    368 
    369                     contactId,
    370                     rawContactId,
    371 
    372                     "mockAccountName", "mockAccountType", null,
    373                     0, 1, 0,
    374                     "sync1", "sync2", "sync3", "sync4",
    375                     0,
    376 
    377                     dataId,
    378 
    379                     "dat1", "dat2", "dat3", "dat4", "dat5",
    380                     "dat6", "dat7", "dat8", "dat9", "dat10",
    381                     "dat11", "dat12", "dat13", "dat14", "dat15",
    382                     "syn1", "syn2", "syn3", "syn4",
    383 
    384                     0, 0,
    385                     0, StructuredName.CONTENT_ITEM_TYPE,
    386 
    387                     "groupId",
    388 
    389                     StatusUpdates.INVISIBLE, null,
    390                     "Having dinner", "mockPkg3", 0,
    391                     20, 0,
    392 
    393                     "content:some.photo.uri",
    394 
    395                     0,
    396                     null,
    397                     0,
    398 
    399                     0,
    400                     0
    401             };
    402 
    403             List<Object> rowsList = Lists.newArrayList(ROWS_INTERNAL);
    404             if (CompatUtils.isMarshmallowCompatible()) {
    405                 rowsList.add(Data.CARRIER_PRESENCE_VT_CAPABLE);
    406             }
    407             final Object[] ROWS = rowsList.toArray(new Object[rowsList.size()]);
    408 
    409             mContactsProvider.expectQuery(baseUri)
    410                     .withProjection(COLUMNS)
    411                     .withSortOrder(Contacts.Entity.RAW_CONTACT_ID)
    412                     .returnRow(ROWS);
    413         }
    414 
    415         void fetchLookupAndId(final Uri sourceUri, final long expectedContactId,
    416                 final String expectedEncodedLookup) {
    417             mContactsProvider.expectQuery(sourceUri)
    418                     .withProjection(Contacts.LOOKUP_KEY, Contacts._ID)
    419                     .returnRow(expectedEncodedLookup, expectedContactId);
    420         }
    421 
    422         void fetchContactIdAndLookupFromRawContactUri(final Uri rawContactUri,
    423                 final long expectedContactId, final String expectedEncodedLookup) {
    424             // TODO: use a lighter query by joining rawcontacts with contacts in provider
    425             // (See ContactContracts.java)
    426             final Uri dataUri = Uri.withAppendedPath(rawContactUri,
    427                     RawContacts.Data.CONTENT_DIRECTORY);
    428             mContactsProvider.expectQuery(dataUri)
    429                     .withProjection(RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY)
    430                     .returnRow(expectedContactId, expectedEncodedLookup);
    431         }
    432     }
    433 }
    434