1 /* 2 * Copyright (C) 2011 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.providers.contacts; 18 19 import com.android.common.io.MoreCloseables; 20 21 import android.content.ContentUris; 22 import android.content.ContentValues; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.ParcelFileDescriptor; 26 import android.provider.CallLog.Calls; 27 import android.provider.VoicemailContract; 28 import android.provider.VoicemailContract.Status; 29 import android.provider.VoicemailContract.Voicemails; 30 import android.test.MoreAsserts; 31 32 import java.io.FileNotFoundException; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.util.Arrays; 37 import java.util.List; 38 39 /** 40 * Unit tests for {@link VoicemailContentProvider}. 41 * 42 * Run the test like this: 43 * <code> 44 * runtest -c com.android.providers.contacts.VoicemailProviderTest contactsprov 45 * </code> 46 */ 47 // TODO: Test that calltype and voicemail_uri are auto populated by the provider. 48 public class VoicemailProviderTest extends BaseVoicemailProviderTest { 49 /** Fields specific to call_log provider that should not be exposed by voicemail provider. */ 50 private static final String[] CALLLOG_PROVIDER_SPECIFIC_COLUMNS = { 51 Calls.CACHED_NAME, 52 Calls.CACHED_NUMBER_LABEL, 53 Calls.CACHED_NUMBER_TYPE, 54 Calls.TYPE, 55 Calls.VOICEMAIL_URI, 56 Calls.COUNTRY_ISO 57 }; 58 /** Total number of columns exposed by voicemail provider. */ 59 private static final int NUM_VOICEMAIL_FIELDS = 13; 60 61 @Override setUp()62 protected void setUp() throws Exception { 63 super.setUp(); 64 setUpForOwnPermission(); 65 } 66 67 /** Returns the appropriate /voicemail URI. */ voicemailUri()68 private Uri voicemailUri() { 69 return mUseSourceUri ? 70 Voicemails.buildSourceUri(mActor.packageName) : Voicemails.CONTENT_URI; 71 } 72 73 /** Returns the appropriate /status URI. */ statusUri()74 private Uri statusUri() { 75 return mUseSourceUri ? 76 Status.buildSourceUri(mActor.packageName) : Status.CONTENT_URI; 77 } 78 testInsert()79 public void testInsert() throws Exception { 80 Uri uri = mResolver.insert(voicemailUri(), getTestVoicemailValues()); 81 // We create on purpose a new set of ContentValues here, because the code above modifies 82 // the copy it gets. 83 assertStoredValues(uri, getTestVoicemailValues()); 84 assertSelection(uri, getTestVoicemailValues(), Voicemails._ID, ContentUris.parseId(uri)); 85 assertEquals(1, countFilesInTestDirectory()); 86 } 87 88 // Test to ensure that media content can be written and read back. testFileContent()89 public void testFileContent() throws Exception { 90 Uri uri = insertVoicemail(); 91 OutputStream out = mResolver.openOutputStream(uri); 92 byte[] outBuffer = {0x1, 0x2, 0x3, 0x4}; 93 out.write(outBuffer); 94 out.flush(); 95 out.close(); 96 InputStream in = mResolver.openInputStream(uri); 97 byte[] inBuffer = new byte[4]; 98 int numBytesRead = in.read(inBuffer); 99 assertEquals(numBytesRead, outBuffer.length); 100 MoreAsserts.assertEquals(outBuffer, inBuffer); 101 // No more data should be left. 102 assertEquals(-1, in.read(inBuffer)); 103 in.close(); 104 } 105 testUpdate()106 public void testUpdate() { 107 Uri uri = insertVoicemail(); 108 ContentValues values = new ContentValues(); 109 values.put(Voicemails.NUMBER, "1-800-263-7643"); 110 values.put(Voicemails.DATE, 2000); 111 values.put(Voicemails.DURATION, 40); 112 values.put(Voicemails.STATE, 2); 113 values.put(Voicemails.HAS_CONTENT, 1); 114 values.put(Voicemails.SOURCE_DATA, "foo"); 115 int count = mResolver.update(uri, values, null, null); 116 assertEquals(1, count); 117 assertStoredValues(uri, values); 118 } 119 testDelete()120 public void testDelete() { 121 Uri uri = insertVoicemail(); 122 int count = mResolver.delete(voicemailUri(), Voicemails._ID + "=" 123 + ContentUris.parseId(uri), null); 124 assertEquals(1, count); 125 assertEquals(0, getCount(uri, null, null)); 126 } 127 testGetType_ItemUri()128 public void testGetType_ItemUri() throws Exception { 129 // Random item uri. 130 assertEquals(Voicemails.ITEM_TYPE, 131 mResolver.getType(ContentUris.withAppendedId(Voicemails.CONTENT_URI, 100))); 132 // Item uri of an inserted voicemail. 133 ContentValues values = getTestVoicemailValues(); 134 values.put(Voicemails.MIME_TYPE, "foo/bar"); 135 Uri uri = mResolver.insert(voicemailUri(), values); 136 assertEquals(Voicemails.ITEM_TYPE, mResolver.getType(uri)); 137 } 138 testGetType_DirUri()139 public void testGetType_DirUri() throws Exception { 140 assertEquals(Voicemails.DIR_TYPE, mResolver.getType(Voicemails.CONTENT_URI)); 141 assertEquals(Voicemails.DIR_TYPE, mResolver.getType(Voicemails.buildSourceUri("foo"))); 142 } 143 144 // Test to ensure that without full permission it is not possible to use the base uri (i.e. with 145 // no package URI specified). testMustUsePackageUriWithoutFullPermission()146 public void testMustUsePackageUriWithoutFullPermission() { 147 setUpForOwnPermission(); 148 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 149 @Override 150 public void run() { 151 mResolver.insert(Voicemails.CONTENT_URI, getTestVoicemailValues()); 152 } 153 }); 154 155 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 156 @Override 157 public void run() { 158 mResolver.update(Voicemails.CONTENT_URI, getTestVoicemailValues(), null, null); 159 } 160 }); 161 162 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 163 @Override 164 public void run() { 165 mResolver.query(Voicemails.CONTENT_URI, null, null, null, null); 166 } 167 }); 168 169 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 170 @Override 171 public void run() { 172 mResolver.delete(Voicemails.CONTENT_URI, null, null); 173 } 174 }); 175 } 176 testPermissions_InsertAndQuery()177 public void testPermissions_InsertAndQuery() { 178 setUpForFullPermission(); 179 // Insert two records - one each with own and another package. 180 insertVoicemail(); 181 insertVoicemailForSourcePackage("another-package"); 182 assertEquals(2, getCount(voicemailUri(), null, null)); 183 184 // Now give away full permission and check that only 1 message is accessible. 185 setUpForOwnPermission(); 186 assertEquals(1, getCount(voicemailUri(), null, null)); 187 188 // Once again try to insert message for another package. This time 189 // it should fail. 190 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 191 @Override 192 public void run() { 193 insertVoicemailForSourcePackage("another-package"); 194 } 195 }); 196 } 197 testPermissions_UpdateAndDelete()198 public void testPermissions_UpdateAndDelete() { 199 setUpForFullPermission(); 200 // Insert two records - one each with own and another package. 201 final Uri ownVoicemail = insertVoicemail(); 202 final Uri anotherVoicemail = insertVoicemailForSourcePackage("another-package"); 203 assertEquals(2, getCount(voicemailUri(), null, null)); 204 205 // Now give away full permission and check that we can update and delete only 206 // the own voicemail. 207 setUpForOwnPermission(); 208 mResolver.update(withSourcePackageParam(ownVoicemail), 209 getTestVoicemailValues(), null, null); 210 mResolver.delete(withSourcePackageParam(ownVoicemail), null, null); 211 212 // However, attempting to update or delete another-package's voicemail should fail. 213 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 214 @Override 215 public void run() { 216 mResolver.update(anotherVoicemail, null, null, null); 217 } 218 }); 219 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 220 @Override 221 public void run() { 222 mResolver.delete(anotherVoicemail, null, null); 223 } 224 }); 225 } 226 withSourcePackageParam(Uri uri)227 private Uri withSourcePackageParam(Uri uri) { 228 return uri.buildUpon() 229 .appendQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE, mActor.packageName) 230 .build(); 231 } 232 testUriPermissions()233 public void testUriPermissions() { 234 setUpForFullPermission(); 235 final Uri uri1 = insertVoicemail(); 236 final Uri uri2 = insertVoicemail(); 237 // Give away all permissions before querying. Access to both uris should be denied. 238 setUpForNoPermission(); 239 checkHasNoAccessToUri(uri1); 240 checkHasNoAccessToUri(uri2); 241 242 // Just grant permission to uri1. uri1 should pass but uri2 should still fail. 243 mActor.addUriPermissions(uri1); 244 checkHasReadOnlyAccessToUri(uri1); 245 checkHasNoAccessToUri(uri2); 246 247 // Cleanup. 248 mActor.removeUriPermissions(uri1); 249 } 250 checkHasNoAccessToUri(final Uri uri)251 private void checkHasNoAccessToUri(final Uri uri) { 252 checkHasNoReadAccessToUri(uri); 253 checkHasNoWriteAccessToUri(uri); 254 } 255 checkHasReadOnlyAccessToUri(final Uri uri)256 private void checkHasReadOnlyAccessToUri(final Uri uri) { 257 checkHasReadAccessToUri(uri); 258 checkHasNoWriteAccessToUri(uri); 259 } 260 checkHasReadAccessToUri(final Uri uri)261 private void checkHasReadAccessToUri(final Uri uri) { 262 Cursor cursor = null; 263 try { 264 cursor = mResolver.query(uri, null, null ,null, null); 265 assertEquals(1, cursor.getCount()); 266 try { 267 ParcelFileDescriptor fd = mResolver.openFileDescriptor(uri, "r"); 268 assertNotNull(fd); 269 fd.close(); 270 } catch (FileNotFoundException e) { 271 fail(e.getMessage()); 272 } catch (IOException e) { 273 fail(e.getMessage()); 274 } 275 } finally { 276 MoreCloseables.closeQuietly(cursor); 277 } 278 } 279 checkHasNoReadAccessToUri(final Uri uri)280 private void checkHasNoReadAccessToUri(final Uri uri) { 281 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 282 @Override 283 public void run() { 284 mResolver.query(uri, null, null ,null, null); 285 } 286 }); 287 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 288 @Override 289 public void run() { 290 try { 291 mResolver.openFileDescriptor(uri, "r"); 292 } catch (FileNotFoundException e) { 293 fail(e.getMessage()); 294 } 295 } 296 }); 297 } 298 checkHasNoWriteAccessToUri(final Uri uri)299 private void checkHasNoWriteAccessToUri(final Uri uri) { 300 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 301 @Override 302 public void run() { 303 mResolver.update(uri, getTestVoicemailValues(), null, null); 304 } 305 }); 306 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 307 @Override 308 public void run() { 309 mResolver.delete(uri, null, null); 310 } 311 }); 312 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 313 @Override 314 public void run() { 315 try { 316 mResolver.openFileDescriptor(uri, "w"); 317 } catch (FileNotFoundException e) { 318 fail(e.getMessage()); 319 } 320 } 321 }); 322 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 323 @Override 324 public void run() { 325 try { 326 mResolver.openFileDescriptor(uri, "rw"); 327 } catch (FileNotFoundException e) { 328 fail(e.getMessage()); 329 } 330 } 331 }); 332 } 333 334 // Test to ensure that all operations fail when no voicemail permission is granted. testNoPermissions()335 public void testNoPermissions() { 336 setUpForNoPermission(); 337 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 338 @Override 339 public void run() { 340 mResolver.insert(voicemailUri(), getTestVoicemailValues()); 341 } 342 }); 343 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 344 @Override 345 public void run() { 346 mResolver.update(voicemailUri(), getTestVoicemailValues(), null, null); 347 } 348 }); 349 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 350 @Override 351 public void run() { 352 mResolver.query(voicemailUri(), null, null, null, null); 353 } 354 }); 355 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 356 @Override 357 public void run() { 358 mResolver.delete(voicemailUri(), null, null); 359 } 360 }); 361 } 362 363 // Test to check that none of the call_log provider specific fields are 364 // insertable through voicemail provider. testCannotAccessCallLogSpecificFields_Insert()365 public void testCannotAccessCallLogSpecificFields_Insert() { 366 for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) { 367 final ContentValues values = getTestVoicemailValues(); 368 values.put(callLogColumn, "foo"); 369 EvenMoreAsserts.assertThrows("Column: " + callLogColumn, 370 IllegalArgumentException.class, new Runnable() { 371 @Override 372 public void run() { 373 mResolver.insert(voicemailUri(), values); 374 } 375 }); 376 } 377 } 378 379 // Test to check that none of the call_log provider specific fields are 380 // exposed through voicemail provider query. testCannotAccessCallLogSpecificFields_Query()381 public void testCannotAccessCallLogSpecificFields_Query() { 382 // Query. 383 Cursor cursor = mResolver.query(voicemailUri(), null, null, null, null); 384 List<String> columnNames = Arrays.asList(cursor.getColumnNames()); 385 assertEquals(NUM_VOICEMAIL_FIELDS, columnNames.size()); 386 // None of the call_log provider specific columns should be present. 387 for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) { 388 assertFalse("Unexpected column: '" + callLogColumn + "' returned.", 389 columnNames.contains(callLogColumn)); 390 } 391 } 392 393 // Test to check that none of the call_log provider specific fields are 394 // updatable through voicemail provider. testCannotAccessCallLogSpecificFields_Update()395 public void testCannotAccessCallLogSpecificFields_Update() { 396 for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) { 397 final Uri insertedUri = insertVoicemail(); 398 final ContentValues values = getTestVoicemailValues(); 399 values.put(callLogColumn, "foo"); 400 EvenMoreAsserts.assertThrows("Column: " + callLogColumn, 401 IllegalArgumentException.class, new Runnable() { 402 @Override 403 public void run() { 404 mResolver.update(insertedUri, values, null, null); 405 } 406 }); 407 } 408 } 409 410 // Tests for voicemail status table. 411 testStatusInsert()412 public void testStatusInsert() throws Exception { 413 ContentValues values = getTestStatusValues(); 414 Uri uri = mResolver.insert(statusUri(), values); 415 assertStoredValues(uri, values); 416 assertSelection(uri, values, Status._ID, ContentUris.parseId(uri)); 417 } 418 419 // Test to ensure that duplicate entries for the same package still end up as the same record. testStatusInsertDuplicate()420 public void testStatusInsertDuplicate() throws Exception { 421 setUpForFullPermission(); 422 ContentValues values = getTestStatusValues(); 423 assertNotNull(mResolver.insert(statusUri(), values)); 424 assertEquals(1, getCount(statusUri(), null, null)); 425 426 // Insertion request for the same package should fail with no change in count. 427 values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION); 428 assertNull(mResolver.insert(statusUri(), values)); 429 assertEquals(1, getCount(statusUri(), null, null)); 430 431 // Now insert entry for another source package, and it should end up as a separate record. 432 values.put(Status.SOURCE_PACKAGE, "another.package"); 433 assertNotNull(mResolver.insert(statusUri(), values)); 434 assertEquals(2, getCount(statusUri(), null, null)); 435 } 436 testStatusUpdate()437 public void testStatusUpdate() throws Exception { 438 Uri uri = insertTestStatusEntry(); 439 ContentValues values = getTestStatusValues(); 440 values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION); 441 values.put(Status.NOTIFICATION_CHANNEL_STATE, 442 Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING); 443 int count = mResolver.update(uri, values, null, null); 444 assertEquals(1, count); 445 assertStoredValues(uri, values); 446 } 447 testStatusDelete()448 public void testStatusDelete() { 449 Uri uri = insertTestStatusEntry(); 450 int count = mResolver.delete(statusUri(), Status._ID + "=" 451 + ContentUris.parseId(uri), null); 452 assertEquals(1, count); 453 assertEquals(0, getCount(uri, null, null)); 454 } 455 testStatusGetType()456 public void testStatusGetType() throws Exception { 457 // Item URI. 458 Uri uri = insertTestStatusEntry(); 459 assertEquals(Status.ITEM_TYPE, mResolver.getType(uri)); 460 461 // base URIs. 462 assertEquals(Status.DIR_TYPE, mResolver.getType(Status.CONTENT_URI)); 463 assertEquals(Status.DIR_TYPE, mResolver.getType(Status.buildSourceUri("foo"))); 464 } 465 466 // Basic permission checks for the status table. testStatusPermissions()467 public void testStatusPermissions() throws Exception { 468 final ContentValues values = getTestStatusValues(); 469 // Inserting for another package should fail with any of the URIs. 470 values.put(Status.SOURCE_PACKAGE, "another.package"); 471 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 472 @Override 473 public void run() { 474 mResolver.insert(Status.CONTENT_URI, values); 475 } 476 }); 477 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 478 @Override 479 public void run() { 480 mResolver.insert(Status.buildSourceUri(mActor.packageName), values); 481 } 482 }); 483 484 // But insertion with own package should succeed with the right uri. 485 values.put(Status.SOURCE_PACKAGE, mActor.packageName); 486 final Uri uri = mResolver.insert(Status.buildSourceUri(mActor.packageName), values); 487 assertNotNull(uri); 488 489 // Updating source_package should not work as well. 490 values.put(Status.SOURCE_PACKAGE, "another.package"); 491 EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { 492 @Override 493 public void run() { 494 mResolver.update(uri, values, null, null); 495 } 496 }); 497 } 498 499 // File operation is not supported by /status URI. testStatusFileOperation()500 public void testStatusFileOperation() throws Exception { 501 final Uri uri = insertTestStatusEntry(); 502 EvenMoreAsserts.assertThrows(UnsupportedOperationException.class, new Runnable() { 503 @Override 504 public void run() { 505 try { 506 mResolver.openOutputStream(uri); 507 } catch (FileNotFoundException e) { 508 fail("Unexpected exception " + e); 509 } 510 } 511 }); 512 513 EvenMoreAsserts.assertThrows(UnsupportedOperationException.class, new Runnable() { 514 @Override 515 public void run() { 516 try { 517 mResolver.openInputStream(uri); 518 } catch (FileNotFoundException e) { 519 fail("Unexpected exception " + e); 520 } 521 } 522 }); 523 } 524 525 /** 526 * Inserts a voicemail record with no source package set. The content provider 527 * will detect source package. 528 */ insertVoicemail()529 private Uri insertVoicemail() { 530 return mResolver.insert(voicemailUri(), getTestVoicemailValues()); 531 } 532 533 /** Inserts a voicemail record for the specified source package. */ insertVoicemailForSourcePackage(String sourcePackage)534 private Uri insertVoicemailForSourcePackage(String sourcePackage) { 535 ContentValues values = getTestVoicemailValues(); 536 values.put(Voicemails.SOURCE_PACKAGE, sourcePackage); 537 return mResolver.insert(voicemailUri(), values); 538 } 539 getTestVoicemailValues()540 private ContentValues getTestVoicemailValues() { 541 ContentValues values = new ContentValues(); 542 values.put(Voicemails.NUMBER, "1-800-4664-411"); 543 values.put(Voicemails.DATE, 1000); 544 values.put(Voicemails.DURATION, 30); 545 values.put(Voicemails.IS_READ, 0); 546 values.put(Voicemails.HAS_CONTENT, 0); 547 values.put(Voicemails.SOURCE_DATA, "1234"); 548 values.put(Voicemails.STATE, Voicemails.STATE_INBOX); 549 return values; 550 } 551 insertTestStatusEntry()552 private Uri insertTestStatusEntry() { 553 return mResolver.insert(statusUri(), getTestStatusValues()); 554 } 555 getTestStatusValues()556 private ContentValues getTestStatusValues() { 557 ContentValues values = new ContentValues(); 558 values.put(Status.SOURCE_PACKAGE, mActor.packageName); 559 values.put(Status.VOICEMAIL_ACCESS_URI, "tel:901"); 560 values.put(Status.SETTINGS_URI, "com.example.voicemail.source.SettingsActivity"); 561 values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK); 562 values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK); 563 values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK); 564 return values; 565 } 566 } 567