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