1/* 2 * Copyright (C) 2009 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 17package com.android.providers.contacts; 18 19import com.android.internal.telephony.CallerInfo; 20import com.android.internal.telephony.PhoneConstants; 21import com.android.providers.contacts.testutil.CommonDatabaseUtils; 22 23import android.content.ComponentName; 24import android.content.ContentProvider; 25import android.content.ContentUris; 26import android.content.ContentValues; 27import android.content.Context; 28import android.content.ContextWrapper; 29import android.content.Intent; 30import android.content.pm.PackageManager; 31import android.database.Cursor; 32import android.database.MatrixCursor; 33import android.net.Uri; 34import android.provider.CallLog; 35import android.provider.CallLog.Calls; 36import android.provider.ContactsContract; 37import android.provider.ContactsContract.CommonDataKinds.Phone; 38import android.provider.VoicemailContract.Voicemails; 39import android.telecom.PhoneAccountHandle; 40import android.test.suitebuilder.annotation.MediumTest; 41 42import java.util.Arrays; 43import java.util.List; 44 45/** 46 * Unit tests for {@link CallLogProvider}. 47 * 48 * Run the test like this: 49 * <code> 50 * adb shell am instrument -e class com.android.providers.contacts.CallLogProviderTest -w \ 51 * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner 52 * </code> 53 */ 54@MediumTest 55public class CallLogProviderTest extends BaseContactsProvider2Test { 56 /** Fields specific to voicemail provider that should not be exposed by call_log*/ 57 private static final String[] VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS = new String[] { 58 Voicemails._DATA, 59 Voicemails.HAS_CONTENT, 60 Voicemails.MIME_TYPE, 61 Voicemails.SOURCE_PACKAGE, 62 Voicemails.SOURCE_DATA, 63 Voicemails.STATE, 64 Voicemails.DIRTY, 65 Voicemails.DELETED}; 66 /** Total number of columns exposed by call_log provider. */ 67 private static final int NUM_CALLLOG_FIELDS = 26; 68 69 private CallLogProvider mCallLogProvider; 70 71 @Override 72 protected Class<? extends ContentProvider> getProviderClass() { 73 return SynchronousContactsProvider2.class; 74 } 75 76 @Override 77 protected String getAuthority() { 78 return ContactsContract.AUTHORITY; 79 } 80 81 @Override 82 protected void setUp() throws Exception { 83 super.setUp(); 84 mCallLogProvider = (CallLogProvider) addProvider(TestCallLogProvider.class, 85 CallLog.AUTHORITY); 86 } 87 88 @Override 89 protected void tearDown() throws Exception { 90 setUpWithVoicemailPermissions(); 91 mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null); 92 super.tearDown(); 93 } 94 95 public void testInsert_RegularCallRecord() { 96 ContentValues values = getDefaultCallValues(); 97 Uri uri = mResolver.insert(Calls.CONTENT_URI, values); 98 values.put(Calls.COUNTRY_ISO, "us"); 99 assertStoredValues(uri, values); 100 assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri)); 101 } 102 103 private void setUpWithVoicemailPermissions() { 104 mActor.addPermissions(ADD_VOICEMAIL_PERMISSION); 105 mActor.addPermissions(READ_VOICEMAIL_PERMISSION); 106 mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION); 107 } 108 109 public void testInsert_VoicemailCallRecord() { 110 setUpWithVoicemailPermissions(); 111 final ContentValues values = getDefaultCallValues(); 112 values.put(Calls.TYPE, Calls.VOICEMAIL_TYPE); 113 values.put(Calls.VOICEMAIL_URI, "content://foo/voicemail/2"); 114 115 // Should fail with the base content uri without the voicemail param. 116 EvenMoreAsserts.assertThrows(IllegalArgumentException.class, new Runnable() { 117 @Override 118 public void run() { 119 mResolver.insert(Calls.CONTENT_URI, values); 120 } 121 }); 122 123 // Now grant voicemail permission - should succeed. 124 Uri uri = mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, values); 125 assertStoredValues(uri, values); 126 assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri)); 127 } 128 129 public void testUpdate() { 130 Uri uri = insertCallRecord(); 131 ContentValues values = new ContentValues(); 132 values.put(Calls.TYPE, Calls.OUTGOING_TYPE); 133 values.put(Calls.NUMBER, "1-800-263-7643"); 134 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 135 values.put(Calls.DATE, 2000); 136 values.put(Calls.DURATION, 40); 137 values.put(Calls.CACHED_NAME, "1-800-GOOG-411"); 138 values.put(Calls.CACHED_NUMBER_TYPE, Phone.TYPE_CUSTOM); 139 values.put(Calls.CACHED_NUMBER_LABEL, "Directory"); 140 141 int count = mResolver.update(uri, values, null, null); 142 assertEquals(1, count); 143 assertStoredValues(uri, values); 144 } 145 146 public void testDelete() { 147 Uri uri = insertCallRecord(); 148 try { 149 mResolver.delete(uri, null, null); 150 fail(); 151 } catch (UnsupportedOperationException ex) { 152 // Expected 153 } 154 155 int count = mResolver.delete(Calls.CONTENT_URI, Calls._ID + "=" 156 + ContentUris.parseId(uri), null); 157 assertEquals(1, count); 158 assertEquals(0, getCount(uri, null, null)); 159 } 160 161 public void testCallLogFilter() { 162 ContentValues values = getDefaultCallValues(); 163 mResolver.insert(Calls.CONTENT_URI, values); 164 165 Uri filterUri = Uri.withAppendedPath(Calls.CONTENT_FILTER_URI, "1-800-4664-411"); 166 Cursor c = mResolver.query(filterUri, null, null, null, null); 167 assertEquals(1, c.getCount()); 168 c.moveToFirst(); 169 assertCursorValues(c, values); 170 c.close(); 171 172 filterUri = Uri.withAppendedPath(Calls.CONTENT_FILTER_URI, "1-888-4664-411"); 173 c = mResolver.query(filterUri, null, null, null, null); 174 assertEquals(0, c.getCount()); 175 c.close(); 176 } 177 178 public void testAddCall() { 179 CallerInfo ci = new CallerInfo(); 180 ci.name = "1-800-GOOG-411"; 181 ci.numberType = Phone.TYPE_CUSTOM; 182 ci.numberLabel = "Directory"; 183 final ComponentName sComponentName = new ComponentName( 184 "com.android.server.telecom", 185 "TelecomServiceImpl"); 186 PhoneAccountHandle subscription = new PhoneAccountHandle( 187 sComponentName, "sub0"); 188 189 Uri uri = Calls.addCall(ci, getMockContext(), "1-800-263-7643", 190 PhoneConstants.PRESENTATION_ALLOWED, Calls.OUTGOING_TYPE, 0, subscription, 2000, 191 40, null); 192 193 ContentValues values = new ContentValues(); 194 values.put(Calls.TYPE, Calls.OUTGOING_TYPE); 195 values.put(Calls.FEATURES, 0); 196 values.put(Calls.NUMBER, "1-800-263-7643"); 197 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 198 values.put(Calls.DATE, 2000); 199 values.put(Calls.DURATION, 40); 200 // Cached values should not be updated immediately by the framework when inserting the call. 201 values.put(Calls.CACHED_NAME, (String) null); 202 values.put(Calls.CACHED_NUMBER_TYPE, (String) null); 203 values.put(Calls.CACHED_NUMBER_LABEL, (String) null); 204 values.put(Calls.COUNTRY_ISO, "us"); 205 values.put(Calls.GEOCODED_LOCATION, "usa"); 206 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, 207 "com.android.server.telecom/TelecomServiceImpl"); 208 values.put(Calls.PHONE_ACCOUNT_ID, "sub0"); 209 // Casting null to Long as there are many forms of "put" which have nullable second 210 // parameters and the compiler needs a hint as to which form is correct. 211 values.put(Calls.DATA_USAGE, (Long) null); 212 assertStoredValues(uri, values); 213 } 214 215 // Test to check that the calls and voicemail uris returns expected results. 216 public void testDifferentContentUris() { 217 setUpWithVoicemailPermissions(); 218 // Insert one voicemaail and two regular call record. 219 insertVoicemailRecord(); 220 insertCallRecord(); 221 insertCallRecord(); 222 223 // With the default uri, only 2 call entries should be returned. 224 // With the voicemail uri all 3 should be returned. 225 assertEquals(2, getCount(Calls.CONTENT_URI, null, null)); 226 assertEquals(3, getCount(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null)); 227 } 228 229 public void testLimitParamReturnsCorrectLimit() { 230 for (int i=0; i<10; i++) { 231 insertCallRecord(); 232 } 233 Uri uri = Calls.CONTENT_URI.buildUpon() 234 .appendQueryParameter(Calls.LIMIT_PARAM_KEY, "4") 235 .build(); 236 assertEquals(4, getCount(uri, null, null)); 237 } 238 239 public void testLimitAndOffsetParamReturnsCorrectEntries() { 240 for (int i=0; i<10; i++) { 241 mResolver.insert(Calls.CONTENT_URI, getDefaultValues(Calls.INCOMING_TYPE)); 242 } 243 for (int i=0; i<10; i++) { 244 mResolver.insert(Calls.CONTENT_URI, getDefaultValues(Calls.MISSED_TYPE)); 245 } 246 // Limit 4 records. Discard first 8. 247 Uri uri = Calls.CONTENT_URI.buildUpon() 248 .appendQueryParameter(Calls.LIMIT_PARAM_KEY, "4") 249 .appendQueryParameter(Calls.OFFSET_PARAM_KEY, "8") 250 .build(); 251 String[] projection = new String[] {Calls._ID, Calls.TYPE}; 252 Cursor c = mResolver.query(uri, projection, null, null, null); 253 try { 254 // First two should be incoming, next two should be missed. 255 for (int i = 0; i < 2; i++) { 256 c.moveToNext(); 257 assertEquals(Calls.INCOMING_TYPE, c.getInt(1)); 258 } 259 for (int i = 0; i < 2; i++) { 260 c.moveToNext(); 261 assertEquals(Calls.MISSED_TYPE, c.getInt(1)); 262 } 263 } finally { 264 c.close(); 265 } 266 } 267 268 public void testUriWithBadLimitParamThrowsException() { 269 assertParamThrowsIllegalArgumentException(Calls.LIMIT_PARAM_KEY, "notvalid"); 270 } 271 272 public void testUriWithBadOffsetParamThrowsException() { 273 assertParamThrowsIllegalArgumentException(Calls.OFFSET_PARAM_KEY, "notvalid"); 274 } 275 276 private void assertParamThrowsIllegalArgumentException(String key, String value) { 277 Uri uri = Calls.CONTENT_URI.buildUpon() 278 .appendQueryParameter(key, value) 279 .build(); 280 try { 281 mResolver.query(uri, null, null, null, null); 282 fail(); 283 } catch (IllegalArgumentException e) { 284 assertTrue("Error does not contain value in question.", 285 e.toString().contains(value)); 286 } 287 } 288 289 // Test to check that none of the voicemail provider specific fields are 290 // insertable through call_log provider. 291 public void testCannotAccessVoicemailSpecificFields_Insert() { 292 for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) { 293 final ContentValues values = getDefaultCallValues(); 294 values.put(voicemailColumn, "foo"); 295 EvenMoreAsserts.assertThrows("Column: " + voicemailColumn, 296 IllegalArgumentException.class, new Runnable() { 297 @Override 298 public void run() { 299 mResolver.insert(Calls.CONTENT_URI, values); 300 } 301 }); 302 } 303 } 304 305 // Test to check that none of the voicemail provider specific fields are 306 // exposed through call_log provider query. 307 public void testCannotAccessVoicemailSpecificFields_Query() { 308 // Query. 309 Cursor cursor = mResolver.query(Calls.CONTENT_URI, null, null, null, null); 310 List<String> columnNames = Arrays.asList(cursor.getColumnNames()); 311 assertEquals(NUM_CALLLOG_FIELDS, columnNames.size()); 312 // None of the voicemail provider specific columns should be present. 313 for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) { 314 assertFalse("Unexpected column: '" + voicemailColumn + "' returned.", 315 columnNames.contains(voicemailColumn)); 316 } 317 } 318 319 // Test to check that none of the voicemail provider specific fields are 320 // updatable through call_log provider. 321 public void testCannotAccessVoicemailSpecificFields_Update() { 322 for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) { 323 final Uri insertedUri = insertCallRecord(); 324 final ContentValues values = new ContentValues(); 325 values.put(voicemailColumn, "foo"); 326 EvenMoreAsserts.assertThrows("Column: " + voicemailColumn, 327 IllegalArgumentException.class, new Runnable() { 328 @Override 329 public void run() { 330 mResolver.update(insertedUri, values, null, null); 331 } 332 }); 333 } 334 } 335 336 public void testVoicemailPermissions_Insert() { 337 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 338 @Override 339 public void run() { 340 mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues()); 341 } 342 }); 343 // Should now succeed with permissions granted. 344 setUpWithVoicemailPermissions(); 345 mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues()); 346 } 347 348 public void testVoicemailPermissions_Update() { 349 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 350 @Override 351 public void run() { 352 mResolver.update(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues(), 353 null, null); 354 } 355 }); 356 357 // Should succeed with manage permission granted 358 mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION); 359 mResolver.update(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultCallValues(), null, null); 360 mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION); 361 362 // Should also succeed with full permissions granted. 363 setUpWithVoicemailPermissions(); 364 mResolver.update(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultCallValues(), null, null); 365 } 366 367 public void testVoicemailPermissions_Query() { 368 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 369 @Override 370 public void run() { 371 mResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null, null, null); 372 } 373 }); 374 375 // Should succeed with read_all permission granted 376 mActor.addPermissions(READ_VOICEMAIL_PERMISSION); 377 mResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null, null, null); 378 mActor.removePermissions(READ_VOICEMAIL_PERMISSION); 379 380 // Should also succeed with full permissions granted. 381 setUpWithVoicemailPermissions(); 382 mResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null, null, null); 383 } 384 385 public void testVoicemailPermissions_Delete() { 386 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 387 @Override 388 public void run() { 389 mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null); 390 } 391 }); 392 393 // Should succeed with manage permission granted 394 mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION); 395 mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null); 396 mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION); 397 398 // Should now succeed with permissions granted. 399 setUpWithVoicemailPermissions(); 400 mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null); 401 } 402 403 public void testCopyEntriesFromCursor_ReturnsMostRecentEntryTimestamp() { 404 assertEquals(10, mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor())); 405 } 406 407 public void testCopyEntriesFromCursor_AllEntriesSyncedWithoutDuplicatesPresent() { 408 assertStoredValues(Calls.CONTENT_URI); 409 mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor()); 410 assertStoredValues(Calls.CONTENT_URI, 411 getTestCallLogValues(2), 412 getTestCallLogValues(1), 413 getTestCallLogValues(0)); 414 } 415 416 public void testCopyEntriesFromCursor_DuplicatesIgnoredCorrectly() { 417 mResolver.insert(Calls.CONTENT_URI, getTestCallLogValues(1)); 418 assertStoredValues(Calls.CONTENT_URI, getTestCallLogValues(1)); 419 mCallLogProvider.copyEntriesFromCursor(getTestCallLogCursor()); 420 assertStoredValues(Calls.CONTENT_URI, 421 getTestCallLogValues(2), 422 getTestCallLogValues(1), 423 getTestCallLogValues(0)); 424 } 425 426 private ContentValues getDefaultValues(int callType) { 427 ContentValues values = new ContentValues(); 428 values.put(Calls.TYPE, callType); 429 values.put(Calls.NUMBER, "1-800-4664-411"); 430 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 431 values.put(Calls.DATE, 1000); 432 values.put(Calls.DURATION, 30); 433 values.put(Calls.NEW, 1); 434 return values; 435 } 436 437 private ContentValues getDefaultCallValues() { 438 return getDefaultValues(Calls.INCOMING_TYPE); 439 } 440 441 private ContentValues getDefaultVoicemailValues() { 442 return getDefaultValues(Calls.VOICEMAIL_TYPE); 443 } 444 445 private Uri insertCallRecord() { 446 return mResolver.insert(Calls.CONTENT_URI, getDefaultCallValues()); 447 } 448 449 private Uri insertVoicemailRecord() { 450 return mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, getDefaultVoicemailValues()); 451 } 452 453 public static class TestCallLogProvider extends CallLogProvider { 454 private static ContactsDatabaseHelper mDbHelper; 455 456 @Override 457 protected ContactsDatabaseHelper getDatabaseHelper(final Context context) { 458 if (mDbHelper == null) { 459 mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context); 460 } 461 return mDbHelper; 462 } 463 464 @Override 465 protected CallLogInsertionHelper createCallLogInsertionHelper(Context context) { 466 return new CallLogInsertionHelper() { 467 @Override 468 public String getGeocodedLocationFor(String number, String countryIso) { 469 return "usa"; 470 } 471 472 @Override 473 public void addComputedValues(ContentValues values) { 474 values.put(Calls.COUNTRY_ISO, "us"); 475 values.put(Calls.GEOCODED_LOCATION, "usa"); 476 } 477 }; 478 } 479 480 @Override 481 protected Context context() { 482 return new ContextWrapper(super.context()) { 483 @Override 484 public PackageManager getPackageManager() { 485 return super.getPackageManager(); 486 } 487 488 @Override 489 public void sendBroadcast(Intent intent, String receiverPermission) { 490 // Do nothing for now. 491 } 492 }; 493 } 494 } 495 496 private Cursor getTestCallLogCursor() { 497 final MatrixCursor cursor = new MatrixCursor(CallLogProvider.CALL_LOG_SYNC_PROJECTION); 498 for (int i = 2; i >= 0; i--) { 499 cursor.addRow(CommonDatabaseUtils.getArrayFromContentValues(getTestCallLogValues(i), 500 CallLogProvider.CALL_LOG_SYNC_PROJECTION)); 501 } 502 return cursor; 503 } 504 505 /** 506 * Returns a predefined {@link ContentValues} object based on the provided index. 507 */ 508 private ContentValues getTestCallLogValues(int i) { 509 ContentValues values = new ContentValues(); 510 switch (i) { 511 case 0: 512 values.put(Calls.NUMBER, "123456"); 513 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 514 values.put(Calls.TYPE, Calls.MISSED_TYPE); 515 values.put(Calls.FEATURES, 0); 516 values.put(Calls.DATE, 10); 517 values.put(Calls.DURATION, 100); 518 values.put(Calls.DATA_USAGE, 1000); 519 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null); 520 values.put(Calls.PHONE_ACCOUNT_ID, (Long) null); 521 break; 522 case 1: 523 values.put(Calls.NUMBER, "654321"); 524 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 525 values.put(Calls.TYPE, Calls.INCOMING_TYPE); 526 values.put(Calls.FEATURES, 0); 527 values.put(Calls.DATE, 5); 528 values.put(Calls.DURATION, 200); 529 values.put(Calls.DATA_USAGE, 0); 530 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null); 531 values.put(Calls.PHONE_ACCOUNT_ID, (Long) null); 532 break; 533 case 2: 534 values.put(Calls.NUMBER, "123456"); 535 values.put(Calls.NUMBER_PRESENTATION, Calls.PRESENTATION_ALLOWED); 536 values.put(Calls.TYPE, Calls.OUTGOING_TYPE); 537 values.put(Calls.FEATURES, Calls.FEATURES_VIDEO); 538 values.put(Calls.DATE, 1); 539 values.put(Calls.DURATION, 50); 540 values.put(Calls.DATA_USAGE, 2000); 541 values.put(Calls.PHONE_ACCOUNT_COMPONENT_NAME, (String) null); 542 values.put(Calls.PHONE_ACCOUNT_ID, (Long) null); 543 break; 544 } 545 return values; 546 } 547} 548