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