1 /* 2 * Copyright (C) 2013 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 android.provider.cts.contacts; 18 19 20 import android.content.ContentProviderClient; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.net.Uri; 25 import android.provider.ContactsContract; 26 import android.provider.ContactsContract.CommonDataKinds.Email; 27 import android.provider.ContactsContract.CommonDataKinds.Phone; 28 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 29 import android.provider.ContactsContract.Contacts; 30 import android.provider.ContactsContract.Data; 31 import android.provider.ContactsContract.DataUsageFeedback; 32 import android.provider.ContactsContract.RawContacts; 33 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact; 34 import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact; 35 import android.test.InstrumentationTestCase; 36 37 /** 38 * CTS tests for {@link android.provider.ContactsContract.Contacts#CONTENT_FREQUENT_URI}, 39 * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} and 40 * {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI} apis. 41 */ 42 public class ContactsContract_FrequentsStrequentsTest extends InstrumentationTestCase { 43 private ContentResolver mResolver; 44 private ContactsContract_TestDataBuilder mBuilder; 45 46 private static final String[] STREQUENT_PROJECTION = new String[]{ 47 Contacts._ID, 48 Contacts.HAS_PHONE_NUMBER, 49 Contacts.NAME_RAW_CONTACT_ID, 50 Contacts.IS_USER_PROFILE, 51 Contacts.CUSTOM_RINGTONE, 52 Contacts.DISPLAY_NAME, 53 Contacts.DISPLAY_NAME_ALTERNATIVE, 54 Contacts.DISPLAY_NAME_SOURCE, 55 Contacts.IN_DEFAULT_DIRECTORY, 56 Contacts.IN_VISIBLE_GROUP, 57 Contacts.LAST_TIME_CONTACTED, 58 Contacts.LOOKUP_KEY, 59 Contacts.PHONETIC_NAME, 60 Contacts.PHONETIC_NAME_STYLE, 61 Contacts.PHOTO_ID, 62 Contacts.PHOTO_FILE_ID, 63 Contacts.PHOTO_URI, 64 Contacts.PHOTO_THUMBNAIL_URI, 65 Contacts.SEND_TO_VOICEMAIL, 66 Contacts.SORT_KEY_ALTERNATIVE, 67 Contacts.SORT_KEY_PRIMARY, 68 Contacts.STARRED, 69 Contacts.PINNED, 70 Contacts.TIMES_CONTACTED, 71 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP, 72 Contacts.CONTACT_PRESENCE, 73 Contacts.CONTACT_CHAT_CAPABILITY, 74 Contacts.CONTACT_STATUS, 75 Contacts.CONTACT_STATUS_TIMESTAMP, 76 Contacts.CONTACT_STATUS_RES_PACKAGE, 77 Contacts.CONTACT_STATUS_LABEL, 78 Contacts.CONTACT_STATUS_ICON, 79 Data.TIMES_USED, 80 Data.LAST_TIME_USED, 81 }; 82 83 private static final String[] STREQUENT_PHONE_ONLY_PROJECTION = new String[]{ 84 Data._ID, 85 Contacts.HAS_PHONE_NUMBER, 86 Contacts.NAME_RAW_CONTACT_ID, 87 Contacts.IS_USER_PROFILE, 88 Contacts.CUSTOM_RINGTONE, 89 Contacts.DISPLAY_NAME, 90 Contacts.DISPLAY_NAME_ALTERNATIVE, 91 Contacts.DISPLAY_NAME_SOURCE, 92 Contacts.IN_DEFAULT_DIRECTORY, 93 Contacts.IN_VISIBLE_GROUP, 94 Contacts.LAST_TIME_CONTACTED, 95 Contacts.LOOKUP_KEY, 96 Contacts.PHONETIC_NAME, 97 Contacts.PHONETIC_NAME_STYLE, 98 Contacts.PHOTO_ID, 99 Contacts.PHOTO_FILE_ID, 100 Contacts.PHOTO_URI, 101 Contacts.PHOTO_THUMBNAIL_URI, 102 Contacts.SEND_TO_VOICEMAIL, 103 Contacts.SORT_KEY_ALTERNATIVE, 104 Contacts.SORT_KEY_PRIMARY, 105 Contacts.STARRED, 106 Contacts.PINNED, 107 Contacts.TIMES_CONTACTED, 108 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP, 109 Contacts.CONTACT_PRESENCE, 110 Contacts.CONTACT_CHAT_CAPABILITY, 111 Contacts.CONTACT_STATUS, 112 Contacts.CONTACT_STATUS_TIMESTAMP, 113 Contacts.CONTACT_STATUS_RES_PACKAGE, 114 Contacts.CONTACT_STATUS_LABEL, 115 Contacts.CONTACT_STATUS_ICON, 116 Data.TIMES_USED, 117 Data.LAST_TIME_USED, 118 Phone.NUMBER, 119 Phone.TYPE, 120 Phone.LABEL, 121 Phone.IS_SUPER_PRIMARY, 122 Phone.CONTACT_ID, 123 }; 124 125 public static ContentValues[] sContentValues = new ContentValues[3]; 126 static { 127 ContentValues cv1 = new ContentValues(); 128 cv1.put(Contacts.DISPLAY_NAME, "Hot Tamale"); 129 sContentValues[0] = cv1; 130 131 ContentValues cv2 = new ContentValues(); 132 cv2.put(Contacts.DISPLAY_NAME, "Cold Tamago"); 133 sContentValues[1] = cv2; 134 135 ContentValues cv3 = new ContentValues(); 136 cv3.put(Contacts.DISPLAY_NAME, "John Doe"); 137 sContentValues[2] = cv3; 138 } 139 140 private long[] mDataIds = new long[3]; 141 142 @Override 143 protected void setUp() throws Exception { 144 super.setUp(); 145 mResolver = getInstrumentation().getTargetContext().getContentResolver(); 146 ContentProviderClient provider = 147 mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY); 148 mBuilder = new ContactsContract_TestDataBuilder(provider); 149 } 150 151 @Override 152 protected void tearDown() throws Exception { 153 super.tearDown(); 154 mBuilder.cleanup(); 155 } 156 157 /** 158 * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns 159 * no contacts if there are no starred or frequent contacts in the user's contacts. 160 */ 161 public void testStrequents_noStarredOrFrequents() throws Exception { 162 long[] ids = setupTestData(); 163 assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids, false); 164 } 165 166 /** 167 * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns 168 * starred contacts in the correct order if there are only starred contacts in the user's 169 * contacts. 170 */ 171 public void testStrequents_starredOnlyInCorrectOrder() throws Exception { 172 long[] ids = setupTestData(); 173 174 // Star/favorite the first and third contact. 175 starContact(ids[0]); 176 starContact(ids[1]); 177 178 // Only the starred contacts should be returned, ordered alphabetically by name 179 assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids, 180 false, sContentValues[1], sContentValues[0]); 181 } 182 183 /** 184 * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns 185 * frequent contacts in the correct order if there are only frequent contacts in the user's 186 * contacts. 187 */ 188 public void testStrequents_frequentsOnlyInCorrectOrder() throws Exception { 189 long[] ids = setupTestData(); 190 191 // Contact the first contact once. 192 markDataAsUsed(mDataIds[0], 1); 193 194 // Contact the second contact thrice. 195 markDataAsUsed(mDataIds[1], 3); 196 197 // Contact the third contact twice. 198 markDataAsUsed(mDataIds[2], 2); 199 200 // The strequents uri should now return contact 2, 3, 1 in order due to ranking by 201 // data usage. 202 assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids, 203 false, sContentValues[1], sContentValues[2], sContentValues[0]); 204 } 205 206 /** 207 * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_URI} returns 208 * first starred, then frequent contacts in their respective correct orders if there are both 209 * starred and frequent contacts in the user's contacts. 210 */ 211 public void testStrequents_starredAndFrequentsInCorrectOrder() throws Exception { 212 long[] ids = setupTestData(); 213 214 // Contact the first contact once. 215 markDataAsUsed(mDataIds[0], 1); 216 217 // Contact the second contact thrice. 218 markDataAsUsed(mDataIds[1], 3); 219 220 // Contact the third contact twice, and mark it as used 221 markDataAsUsed(mDataIds[2], 2); 222 starContact(ids[2]); 223 224 // The strequents uri should now return contact 3, 2, 1 in order. Contact 3 is ranked first 225 // because it is starred, followed by contacts 2 and 1 due to their data usage ranking. 226 // Note that contact 3 is only returned once (as a starred contact) even though it is also 227 // a frequently contacted contact. 228 assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_STREQUENT_URI, ids, 229 false, sContentValues[2], sContentValues[1], sContentValues[0]); 230 } 231 232 /** 233 * Tests that {@link android.provider.ContactsContract.Contacts#CONTENT_STREQUENT_FILTER_URI} 234 * correctly filters the returned contacts with the given user input. 235 */ 236 public void testStrequents_withFilter() throws Exception { 237 long[] ids = setupTestData(); 238 239 //Star all 3 contacts 240 starContact(ids[0]); 241 starContact(ids[1]); 242 starContact(ids[2]); 243 244 // Construct a uri that filters for the query string "ta". 245 Uri uri = Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon().appendEncodedPath("ta").build(); 246 247 // Only contact 1 and 2 should be returned (sorted in alphabetical order) due to the 248 // filtered query. 249 assertCursorStoredValuesWithContactsFilter(uri, ids, false, sContentValues[1], sContentValues[0]); 250 } 251 252 public void testStrequents_projection() throws Exception { 253 long[] ids = setupTestData(); 254 255 // Start contact 0 and mark contact 2 as frequent 256 starContact(ids[0]); 257 markDataAsUsed(mDataIds[2], 1); 258 259 DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_STREQUENT_URI, 260 STREQUENT_PROJECTION, 261 new long[]{ids[0], ids[2]} 262 ); 263 264 // Strequent filter. 265 DatabaseAsserts.checkProjection(mResolver, 266 Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon() 267 .appendEncodedPath("Hot Tamale").build(), 268 STREQUENT_PROJECTION, 269 new long[]{ids[0]} 270 ); 271 } 272 273 public void testStrequents_phoneOnly() throws Exception { 274 long[] ids = setupTestData(); 275 276 // Star all 3 contacts 277 starContact(ids[0]); 278 starContact(ids[1]); 279 starContact(ids[2]); 280 281 // Construct a uri for phone only favorites. 282 Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon(). 283 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build(); 284 285 // Only the contacts with phone numbers are returned, in alphabetical order. Filtering 286 // is done with data ids instead of contact ids since each row contains a single data item. 287 assertCursorStoredValuesWithContactsFilter(uri, mDataIds, false, 288 sContentValues[0], sContentValues[2]); 289 } 290 291 public void testStrequents_phoneOnlyFrequentsOrder() throws Exception { 292 long[] ids = setupTestData(); 293 294 // Contact the first contact once. 295 markDataAsUsed(mDataIds[0], 1); 296 297 // Contact the second contact twice. 298 markDataAsUsed(mDataIds[1], 2); 299 300 // Contact the third contact thrice. 301 markDataAsUsed(mDataIds[2], 3); 302 303 // Construct a uri for phone only favorites. 304 Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon(). 305 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build(); 306 307 // Only the contacts with phone numbers are returned, in frequency ranking order. 308 assertCursorStoredValuesWithContactsFilter(uri, mDataIds, false, 309 sContentValues[2], sContentValues[0]); 310 } 311 312 public void testStrequents_phoneOnly_projection() throws Exception { 313 long[] ids = setupTestData(); 314 315 // Start contact 0 and mark contact 2 as frequent 316 starContact(ids[0]); 317 markDataAsUsed(mDataIds[2], 1); 318 319 // Construct a uri for phone only favorites. 320 Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon(). 321 appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build(); 322 323 DatabaseAsserts.checkProjection(mResolver, uri, 324 STREQUENT_PHONE_ONLY_PROJECTION, 325 new long[]{mDataIds[0], mDataIds[2]} // Note _id from phone_only is data._id 326 ); 327 } 328 329 public void testFrequents_noFrequentsReturnsEmptyCursor() throws Exception { 330 long[] ids = setupTestData(); 331 assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_FREQUENT_URI, ids, false); 332 } 333 334 public void testFrequents_CorrectOrder() throws Exception { 335 long[] ids = setupTestData(); 336 337 // Contact the first contact once. 338 markDataAsUsed(mDataIds[0], 1); 339 340 // Contact the second contact thrice. 341 markDataAsUsed(mDataIds[1], 3); 342 343 // Contact the third contact twice. 344 markDataAsUsed(mDataIds[2], 2); 345 346 // The frequents uri should now return contact 2, 3, 1 in order due to ranking by 347 // data usage. 348 assertCursorStoredValuesWithContactsFilter(Contacts.CONTENT_FREQUENT_URI, ids, 349 true /* inOrder */, sContentValues[1], sContentValues[2], sContentValues[0]); 350 } 351 352 public void testFrequent_projection() throws Exception { 353 long[] ids = setupTestData(); 354 355 markDataAsUsed(mDataIds[0], 10); 356 357 DatabaseAsserts.checkProjection(mResolver, Contacts.CONTENT_FREQUENT_URI, 358 STREQUENT_PROJECTION, 359 new long[]{ids[0]} 360 ); 361 } 362 363 /** 364 * Given a uri, performs a query on the contacts provider for that uri and asserts that the 365 * cursor returned from the query matches the expected results. 366 * 367 * @param uri Uri to perform the query for 368 * @param contactsId Array of contact IDs that serves as an additional filter on the result 369 * set. This is needed to limit the output to temporary test contacts that were created for 370 * purposes of the test, so that the tests do not fail on devices with existing contacts on 371 * them 372 * @param inOrder Whether or not the returned rows in the cursor should correspond to the 373 * order of the provided ContentValues 374 * @param expected An array of ContentValues corresponding to the expected output of the query 375 */ 376 private void assertCursorStoredValuesWithContactsFilter(Uri uri, long[] contactsId, 377 boolean inOrder, ContentValues... expected) { 378 // We need this helper function to add a filter for specific contacts because 379 // otherwise tests will fail if performed on a device with existing contacts data 380 StringBuilder sb = new StringBuilder(); 381 sb.append(Contacts._ID + " in "); 382 sb.append("("); 383 for (int i = 0; i < contactsId.length; i++) { 384 if (i != 0) sb.append(","); 385 sb.append(contactsId[i]); 386 } 387 sb.append(")"); 388 DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, uri, null, sb.toString(), 389 null, null, inOrder, expected); 390 } 391 392 /** 393 * Given a contact id, update the contact corresponding to that contactId so that it now shows 394 * up in the user's favorites/starred contacts. 395 * 396 * @param contactId Contact ID corresponding to the contact to star 397 */ 398 private void starContact(long contactId) { 399 ContentValues values = new ContentValues(); 400 values.put(Contacts.STARRED, 1); 401 mResolver.update(ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId), values, 402 null, null); 403 } 404 405 /** 406 * Given a data id, increment the data usage stats by a given number of usages to simulate 407 * the user making a call to the given data item. 408 * 409 * @param dataId Id of the data item to increment data usage stats for 410 * @param numTimes The number of times to increase the data usage stats by 411 */ 412 private void markDataAsUsed(long dataId, int numTimes) { 413 Uri uri = ContactsContract.DataUsageFeedback.FEEDBACK_URI.buildUpon(). 414 appendPath(String.valueOf(dataId)). 415 appendQueryParameter(DataUsageFeedback.USAGE_TYPE, 416 DataUsageFeedback.USAGE_TYPE_CALL).build(); 417 for (int i = 1; i <= numTimes; i++) { 418 mResolver.update(uri, new ContentValues(), null, null); 419 } 420 } 421 422 /** 423 * Setup the contacts database with temporary contacts used for testing. These contacts will 424 * be removed during teardown. 425 * 426 * @return An array of long values corresponding to the ids of the created contacts 427 * 428 * @throws Exception 429 */ 430 private long[] setupTestData() throws Exception { 431 TestRawContact rawContact = mBuilder.newRawContact() 432 .with(RawContacts.ACCOUNT_TYPE, "test_account") 433 .with(RawContacts.ACCOUNT_NAME, "test_name") 434 .insert(); 435 rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE) 436 .with(StructuredName.DISPLAY_NAME, "Hot Tamale") 437 .insert(); 438 mDataIds[0] = rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE) 439 .with(Phone.DATA, "510-123-5769") 440 .with(Email.TYPE, Phone.TYPE_HOME) 441 .insert().load().getId(); 442 rawContact.load(); 443 TestContact contact = rawContact.getContact().load(); 444 445 TestRawContact rawContact2 = mBuilder.newRawContact() 446 .with(RawContacts.ACCOUNT_TYPE, "test_account") 447 .with(RawContacts.ACCOUNT_NAME, "test_name") 448 .insert(); 449 rawContact2.newDataRow(StructuredName.CONTENT_ITEM_TYPE) 450 .with(StructuredName.DISPLAY_NAME, "Cold Tamago") 451 .insert(); 452 mDataIds[1] = rawContact2.newDataRow(Email.CONTENT_ITEM_TYPE) 453 .with(Email.DATA, "eggs (at) farmers.org") 454 .with(Email.TYPE, Email.TYPE_HOME) 455 .insert().load().getId(); 456 rawContact2.load(); 457 TestContact contact2 = rawContact2.getContact().load(); 458 459 TestRawContact rawContact3 = mBuilder.newRawContact() 460 .with(RawContacts.ACCOUNT_TYPE, "test_account") 461 .with(RawContacts.ACCOUNT_NAME, "test_name") 462 .insert(); 463 rawContact3.newDataRow(StructuredName.CONTENT_ITEM_TYPE) 464 .with(StructuredName.DISPLAY_NAME, "John Doe") 465 .insert(); 466 mDataIds[2] = rawContact3.newDataRow(Phone.CONTENT_ITEM_TYPE) 467 .with(Phone.DATA, "518-354-1111") 468 .with(Phone.TYPE, Phone.TYPE_HOME) 469 .insert().load().getId(); 470 rawContact3.load(); 471 TestContact contact3 = rawContact3.getContact().load(); 472 473 return new long[] {contact.getId(), contact2.getId(), contact3.getId()}; 474 } 475 } 476 477