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