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