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