1 package org.robolectric.shadows; 2 3 import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION; 4 import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS; 5 import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER; 6 import static android.os.Build.VERSION_CODES.N; 7 import static android.os.Build.VERSION_CODES.O; 8 import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 9 import static com.google.common.truth.Truth.assertThat; 10 import static java.nio.charset.StandardCharsets.UTF_8; 11 import static org.junit.Assert.assertThrows; 12 import static org.junit.Assert.fail; 13 import static org.mockito.ArgumentMatchers.same; 14 import static org.mockito.Mockito.doReturn; 15 import static org.mockito.Mockito.mock; 16 import static org.mockito.Mockito.verify; 17 import static org.robolectric.Shadows.shadowOf; 18 import static org.robolectric.annotation.Config.NONE; 19 20 import android.accounts.Account; 21 import android.annotation.SuppressLint; 22 import android.app.Application; 23 import android.content.ContentProvider; 24 import android.content.ContentProviderOperation; 25 import android.content.ContentProviderResult; 26 import android.content.ContentResolver; 27 import android.content.ContentUris; 28 import android.content.ContentValues; 29 import android.content.Intent; 30 import android.content.OperationApplicationException; 31 import android.content.PeriodicSync; 32 import android.content.SyncAdapterType; 33 import android.content.SyncInfo; 34 import android.content.UriPermission; 35 import android.content.pm.ProviderInfo; 36 import android.content.res.AssetFileDescriptor; 37 import android.database.ContentObserver; 38 import android.database.Cursor; 39 import android.database.MatrixCursor; 40 import android.net.Uri; 41 import android.os.Bundle; 42 import android.os.CancellationSignal; 43 import android.os.Handler; 44 import android.os.ParcelFileDescriptor; 45 import android.os.RemoteException; 46 import androidx.test.core.app.ApplicationProvider; 47 import androidx.test.ext.junit.runners.AndroidJUnit4; 48 import com.google.common.collect.Iterables; 49 import java.io.ByteArrayInputStream; 50 import java.io.File; 51 import java.io.FileDescriptor; 52 import java.io.FileNotFoundException; 53 import java.io.FileOutputStream; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.io.OutputStream; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.List; 60 import java.util.concurrent.atomic.AtomicBoolean; 61 import java.util.concurrent.atomic.AtomicInteger; 62 import org.junit.Before; 63 import org.junit.Rule; 64 import org.junit.Test; 65 import org.junit.rules.TemporaryFolder; 66 import org.junit.runner.RunWith; 67 import org.mockito.ArgumentCaptor; 68 import org.robolectric.R; 69 import org.robolectric.Robolectric; 70 import org.robolectric.RuntimeEnvironment; 71 import org.robolectric.annotation.Config; 72 import org.robolectric.fakes.BaseCursor; 73 import org.robolectric.util.NamedStream; 74 75 @RunWith(AndroidJUnit4.class) 76 public class ShadowContentResolverTest { 77 @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 78 79 private static final String AUTHORITY = "org.robolectric"; 80 81 private ContentResolver contentResolver; 82 private ShadowContentResolver shadowContentResolver; 83 private Uri uri21; 84 private Uri uri22; 85 private Account a, b; 86 87 @Before setUp()88 public void setUp() { 89 contentResolver = ApplicationProvider.getApplicationContext().getContentResolver(); 90 shadowContentResolver = shadowOf(contentResolver); 91 uri21 = Uri.parse(EXTERNAL_CONTENT_URI.toString() + "/21"); 92 uri22 = Uri.parse(EXTERNAL_CONTENT_URI.toString() + "/22"); 93 94 a = new Account("a", "type"); 95 b = new Account("b", "type"); 96 } 97 98 @Test insert_shouldReturnIncreasingUris()99 public void insert_shouldReturnIncreasingUris() { 100 shadowContentResolver.setNextDatabaseIdForInserts(20); 101 102 assertThat(contentResolver.insert(EXTERNAL_CONTENT_URI, new ContentValues())).isEqualTo(uri21); 103 assertThat(contentResolver.insert(EXTERNAL_CONTENT_URI, new ContentValues())).isEqualTo(uri22); 104 } 105 106 @Test getType_shouldDefaultToNull()107 public void getType_shouldDefaultToNull() { 108 assertThat(contentResolver.getType(uri21)).isNull(); 109 } 110 111 @Test getType_shouldReturnProviderValue()112 public void getType_shouldReturnProviderValue() { 113 ShadowContentResolver.registerProviderInternal( 114 AUTHORITY, 115 new ContentProvider() { 116 @Override 117 public boolean onCreate() { 118 return false; 119 } 120 121 @Override 122 public Cursor query( 123 Uri uri, 124 String[] projection, 125 String selection, 126 String[] selectionArgs, 127 String sortOrder) { 128 return new BaseCursor(); 129 } 130 131 @Override 132 public Uri insert(Uri uri, ContentValues values) { 133 return null; 134 } 135 136 @Override 137 public int delete(Uri uri, String selection, String[] selectionArgs) { 138 return -1; 139 } 140 141 @Override 142 public int update( 143 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 144 return -1; 145 } 146 147 @Override 148 public String getType(Uri uri) { 149 return "mytype"; 150 } 151 }); 152 final Uri uri = Uri.parse("content://" + AUTHORITY + "/some/path"); 153 assertThat(contentResolver.getType(uri)).isEqualTo("mytype"); 154 } 155 156 @Test insert_shouldTrackInsertStatements()157 public void insert_shouldTrackInsertStatements() { 158 ContentValues contentValues = new ContentValues(); 159 contentValues.put("foo", "bar"); 160 contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues); 161 assertThat(shadowContentResolver.getInsertStatements().size()).isEqualTo(1); 162 assertThat(shadowContentResolver.getInsertStatements().get(0).getUri()) 163 .isEqualTo(EXTERNAL_CONTENT_URI); 164 assertThat( 165 shadowContentResolver 166 .getInsertStatements() 167 .get(0) 168 .getContentValues() 169 .getAsString("foo")) 170 .isEqualTo("bar"); 171 172 contentValues = new ContentValues(); 173 contentValues.put("hello", "world"); 174 contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues); 175 assertThat(shadowContentResolver.getInsertStatements().size()).isEqualTo(2); 176 assertThat( 177 shadowContentResolver 178 .getInsertStatements() 179 .get(1) 180 .getContentValues() 181 .getAsString("hello")) 182 .isEqualTo("world"); 183 } 184 185 @Test insert_shouldTrackUpdateStatements()186 public void insert_shouldTrackUpdateStatements() { 187 ContentValues contentValues = new ContentValues(); 188 contentValues.put("foo", "bar"); 189 contentResolver.update( 190 EXTERNAL_CONTENT_URI, contentValues, "robolectric", new String[] {"awesome"}); 191 assertThat(shadowContentResolver.getUpdateStatements().size()).isEqualTo(1); 192 assertThat(shadowContentResolver.getUpdateStatements().get(0).getUri()) 193 .isEqualTo(EXTERNAL_CONTENT_URI); 194 assertThat( 195 shadowContentResolver 196 .getUpdateStatements() 197 .get(0) 198 .getContentValues() 199 .getAsString("foo")) 200 .isEqualTo("bar"); 201 assertThat(shadowContentResolver.getUpdateStatements().get(0).getWhere()) 202 .isEqualTo("robolectric"); 203 assertThat(shadowContentResolver.getUpdateStatements().get(0).getSelectionArgs()) 204 .isEqualTo(new String[] {"awesome"}); 205 206 contentValues = new ContentValues(); 207 contentValues.put("hello", "world"); 208 contentResolver.update(EXTERNAL_CONTENT_URI, contentValues, null, null); 209 assertThat(shadowContentResolver.getUpdateStatements().size()).isEqualTo(2); 210 assertThat(shadowContentResolver.getUpdateStatements().get(1).getUri()) 211 .isEqualTo(EXTERNAL_CONTENT_URI); 212 assertThat( 213 shadowContentResolver 214 .getUpdateStatements() 215 .get(1) 216 .getContentValues() 217 .getAsString("hello")) 218 .isEqualTo("world"); 219 assertThat(shadowContentResolver.getUpdateStatements().get(1).getWhere()).isNull(); 220 assertThat(shadowContentResolver.getUpdateStatements().get(1).getSelectionArgs()).isNull(); 221 } 222 223 @Test insert_supportsNullContentValues()224 public void insert_supportsNullContentValues() { 225 contentResolver.insert(EXTERNAL_CONTENT_URI, null); 226 assertThat(shadowContentResolver.getInsertStatements().get(0).getContentValues()).isNull(); 227 } 228 229 @Test update_supportsNullContentValues()230 public void update_supportsNullContentValues() { 231 contentResolver.update(EXTERNAL_CONTENT_URI, null, null, null); 232 assertThat(shadowContentResolver.getUpdateStatements().get(0).getContentValues()).isNull(); 233 } 234 235 @Test delete_shouldTrackDeletedUris()236 public void delete_shouldTrackDeletedUris() { 237 assertThat(shadowContentResolver.getDeletedUris().size()).isEqualTo(0); 238 239 assertThat(contentResolver.delete(uri21, null, null)).isEqualTo(1); 240 assertThat(shadowContentResolver.getDeletedUris()).contains(uri21); 241 assertThat(shadowContentResolver.getDeletedUris().size()).isEqualTo(1); 242 243 assertThat(contentResolver.delete(uri22, null, null)).isEqualTo(1); 244 assertThat(shadowContentResolver.getDeletedUris()).contains(uri22); 245 assertThat(shadowContentResolver.getDeletedUris().size()).isEqualTo(2); 246 } 247 248 @Test delete_shouldTrackDeletedStatements()249 public void delete_shouldTrackDeletedStatements() { 250 assertThat(shadowContentResolver.getDeleteStatements().size()).isEqualTo(0); 251 252 assertThat(contentResolver.delete(uri21, "id", new String[] {"5"})).isEqualTo(1); 253 assertThat(shadowContentResolver.getDeleteStatements().size()).isEqualTo(1); 254 assertThat(shadowContentResolver.getDeleteStatements().get(0).getUri()).isEqualTo(uri21); 255 assertThat(shadowContentResolver.getDeleteStatements().get(0).getContentProvider()).isNull(); 256 assertThat(shadowContentResolver.getDeleteStatements().get(0).getWhere()).isEqualTo("id"); 257 assertThat(shadowContentResolver.getDeleteStatements().get(0).getSelectionArgs()[0]) 258 .isEqualTo("5"); 259 260 assertThat(contentResolver.delete(uri21, "foo", new String[] {"bar"})).isEqualTo(1); 261 assertThat(shadowContentResolver.getDeleteStatements().size()).isEqualTo(2); 262 assertThat(shadowContentResolver.getDeleteStatements().get(1).getUri()).isEqualTo(uri21); 263 assertThat(shadowContentResolver.getDeleteStatements().get(1).getWhere()).isEqualTo("foo"); 264 assertThat(shadowContentResolver.getDeleteStatements().get(1).getSelectionArgs()[0]) 265 .isEqualTo("bar"); 266 } 267 268 @Test whenCursorHasBeenSet_query_shouldReturnTheCursor()269 public void whenCursorHasBeenSet_query_shouldReturnTheCursor() { 270 assertThat(shadowContentResolver.query(null, null, null, null, null)).isNull(); 271 BaseCursor cursor = new BaseCursor(); 272 shadowContentResolver.setCursor(cursor); 273 assertThat((BaseCursor) shadowContentResolver.query(null, null, null, null, null)) 274 .isSameInstanceAs(cursor); 275 } 276 277 @Test whenCursorHasBeenSet_queryWithCancellationSignal_shouldReturnTheCursor()278 public void whenCursorHasBeenSet_queryWithCancellationSignal_shouldReturnTheCursor() { 279 assertThat(shadowContentResolver.query(null, null, null, null, null, new CancellationSignal())) 280 .isNull(); 281 BaseCursor cursor = new BaseCursor(); 282 shadowContentResolver.setCursor(cursor); 283 assertThat( 284 (BaseCursor) 285 shadowContentResolver.query(null, null, null, null, null, new CancellationSignal())) 286 .isSameInstanceAs(cursor); 287 } 288 289 @Test query_shouldReturnSpecificCursorsForSpecificUris()290 public void query_shouldReturnSpecificCursorsForSpecificUris() { 291 assertThat(shadowContentResolver.query(uri21, null, null, null, null)).isNull(); 292 assertThat(shadowContentResolver.query(uri22, null, null, null, null)).isNull(); 293 294 BaseCursor cursor21 = new BaseCursor(); 295 BaseCursor cursor22 = new BaseCursor(); 296 shadowContentResolver.setCursor(uri21, cursor21); 297 shadowContentResolver.setCursor(uri22, cursor22); 298 299 assertThat((BaseCursor) shadowContentResolver.query(uri21, null, null, null, null)) 300 .isSameInstanceAs(cursor21); 301 assertThat((BaseCursor) shadowContentResolver.query(uri22, null, null, null, null)) 302 .isSameInstanceAs(cursor22); 303 } 304 305 @Test query_shouldKnowWhatItsParamsWere()306 public void query_shouldKnowWhatItsParamsWere() { 307 String[] projection = {}; 308 String selection = "select"; 309 String[] selectionArgs = {}; 310 String sortOrder = "order"; 311 312 QueryParamTrackingCursor testCursor = new QueryParamTrackingCursor(); 313 314 shadowContentResolver.setCursor(testCursor); 315 Cursor cursor = 316 shadowContentResolver.query(uri21, projection, selection, selectionArgs, sortOrder); 317 assertThat((QueryParamTrackingCursor) cursor).isEqualTo(testCursor); 318 assertThat(testCursor.uri).isEqualTo(uri21); 319 assertThat(testCursor.projection).isEqualTo(projection); 320 assertThat(testCursor.selection).isEqualTo(selection); 321 assertThat(testCursor.selectionArgs).isEqualTo(selectionArgs); 322 assertThat(testCursor.sortOrder).isEqualTo(sortOrder); 323 } 324 325 @Test 326 @Config(minSdk = O) query_shouldKnowWhatIsInBundle()327 public void query_shouldKnowWhatIsInBundle() { 328 String[] projection = {}; 329 String selection = "select"; 330 String[] selectionArgs = {}; 331 String sortOrder = "order"; 332 Bundle queryArgs = new Bundle(); 333 queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection); 334 queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs); 335 queryArgs.putString(QUERY_ARG_SQL_SORT_ORDER, sortOrder); 336 337 QueryParamTrackingCursor testCursor = new QueryParamTrackingCursor(); 338 shadowContentResolver.setCursor(testCursor); 339 Cursor cursor = shadowContentResolver.query(uri21, projection, queryArgs, null); 340 assertThat((QueryParamTrackingCursor) cursor).isEqualTo(testCursor); 341 assertThat(testCursor.uri).isEqualTo(uri21); 342 assertThat(testCursor.projection).isEqualTo(projection); 343 assertThat(testCursor.selection).isEqualTo(selection); 344 assertThat(testCursor.selectionArgs).isEqualTo(selectionArgs); 345 assertThat(testCursor.sortOrder).isEqualTo(sortOrder); 346 } 347 348 @Test acquireUnstableProvider_shouldDefaultToNull()349 public void acquireUnstableProvider_shouldDefaultToNull() { 350 assertThat(contentResolver.acquireUnstableProvider(uri21)).isNull(); 351 } 352 353 @Test acquireUnstableProvider_shouldReturnWithUri()354 public void acquireUnstableProvider_shouldReturnWithUri() { 355 ContentProvider cp = mock(ContentProvider.class); 356 ShadowContentResolver.registerProviderInternal(AUTHORITY, cp); 357 final Uri uri = Uri.parse("content://" + AUTHORITY); 358 assertThat(contentResolver.acquireUnstableProvider(uri)) 359 .isSameInstanceAs(cp.getIContentProvider()); 360 } 361 362 @Test acquireUnstableProvider_shouldReturnWithString()363 public void acquireUnstableProvider_shouldReturnWithString() { 364 ContentProvider cp = mock(ContentProvider.class); 365 ShadowContentResolver.registerProviderInternal(AUTHORITY, cp); 366 assertThat(contentResolver.acquireUnstableProvider(AUTHORITY)) 367 .isSameInstanceAs(cp.getIContentProvider()); 368 } 369 370 @Test call_shouldCallProvider()371 public void call_shouldCallProvider() { 372 final String METHOD = "method"; 373 final String ARG = "arg"; 374 final Bundle EXTRAS = new Bundle(); 375 final Uri uri = Uri.parse("content://" + AUTHORITY); 376 377 ContentProvider provider = mock(ContentProvider.class); 378 doReturn(null).when(provider).call(METHOD, ARG, EXTRAS); 379 ShadowContentResolver.registerProviderInternal(AUTHORITY, provider); 380 381 contentResolver.call(uri, METHOD, ARG, EXTRAS); 382 verify(provider).call(METHOD, ARG, EXTRAS); 383 } 384 385 @Test registerProvider_shouldAttachProviderInfo()386 public void registerProvider_shouldAttachProviderInfo() { 387 ContentProvider mock = mock(ContentProvider.class); 388 389 ProviderInfo providerInfo0 = new ProviderInfo(); 390 providerInfo0.authority = "the-authority"; // todo: support multiple authorities 391 providerInfo0.grantUriPermissions = true; 392 mock.attachInfo(ApplicationProvider.getApplicationContext(), providerInfo0); 393 mock.onCreate(); 394 395 ArgumentCaptor<ProviderInfo> captor = ArgumentCaptor.forClass(ProviderInfo.class); 396 verify(mock) 397 .attachInfo( 398 same((Application) ApplicationProvider.getApplicationContext()), captor.capture()); 399 ProviderInfo providerInfo = captor.getValue(); 400 401 assertThat(providerInfo.authority).isEqualTo("the-authority"); 402 assertThat(providerInfo.grantUriPermissions).isEqualTo(true); 403 } 404 405 @Test(expected = UnsupportedOperationException.class) openInputStream_shouldReturnAnInputStreamThatExceptionsOnRead()406 public void openInputStream_shouldReturnAnInputStreamThatExceptionsOnRead() throws Exception { 407 InputStream inputStream = contentResolver.openInputStream(uri21); 408 inputStream.read(); 409 } 410 411 @Test openInputStream_returnsPreRegisteredStream()412 public void openInputStream_returnsPreRegisteredStream() throws Exception { 413 shadowContentResolver.registerInputStream( 414 uri21, new ByteArrayInputStream("ourStream".getBytes(UTF_8))); 415 InputStream inputStream = contentResolver.openInputStream(uri21); 416 byte[] data = new byte[9]; 417 inputStream.read(data); 418 assertThat(new String(data, UTF_8)).isEqualTo("ourStream"); 419 } 420 421 @Test openInputStream_returnsNewStreamEachTimeFromRegisteredSupplier()422 public void openInputStream_returnsNewStreamEachTimeFromRegisteredSupplier() throws Exception { 423 shadowContentResolver.registerInputStreamSupplier( 424 uri21, () -> new ByteArrayInputStream("ourStream".getBytes(UTF_8))); 425 InputStream inputStream1 = contentResolver.openInputStream(uri21); 426 byte[] data1 = new byte[9]; 427 inputStream1.read(data1); 428 inputStream1.close(); 429 InputStream inputStream2 = contentResolver.openInputStream(uri21); 430 byte[] data2 = new byte[9]; 431 inputStream2.read(data2); 432 inputStream2.close(); 433 assertThat(new String(data1, UTF_8)).isEqualTo("ourStream"); 434 assertThat(new String(data2, UTF_8)).isEqualTo("ourStream"); 435 } 436 437 @Test openInputStream_returnsResourceUriStream()438 public void openInputStream_returnsResourceUriStream() throws Exception { 439 InputStream inputStream = 440 contentResolver.openInputStream( 441 new Uri.Builder() 442 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 443 .authority(ApplicationProvider.getApplicationContext().getPackageName()) 444 .appendPath(String.valueOf(R.drawable.an_image)) 445 .build()); 446 assertThat(inputStream).isNotNull(); 447 inputStream.read(); 448 } 449 450 @SuppressLint("NewApi") 451 @Test openInputStream_returnsFileUriStream()452 public void openInputStream_returnsFileUriStream() throws Exception { 453 File file = temporaryFolder.newFile(); 454 try (FileOutputStream out = new FileOutputStream(file)) { 455 out.write("foo".getBytes(UTF_8)); 456 } 457 458 InputStream inputStream = contentResolver.openInputStream(Uri.fromFile(file)); 459 460 assertThat(inputStream).isNotNull(); 461 assertThat(new String(inputStream.readAllBytes(), UTF_8)).isEqualTo("foo"); 462 } 463 464 @Test openInputStream_returnsProviderInputStream()465 public void openInputStream_returnsProviderInputStream() throws Exception { 466 ProviderInfo info = new ProviderInfo(); 467 info.authority = AUTHORITY; 468 ContentProvider myContentProvider = new MyContentProvider(); 469 myContentProvider.attachInfo(ApplicationProvider.getApplicationContext(), info); 470 ShadowContentResolver.registerProviderInternal(AUTHORITY, myContentProvider); 471 472 Uri uri = Uri.parse("content://" + AUTHORITY + "/some/path"); 473 InputStream actualInputStream = contentResolver.openInputStream(uri); 474 // Registered provider does not return named stream 475 assertThat(actualInputStream).isNotInstanceOf(NamedStream.class); 476 477 Uri otherUri = Uri.parse("content://otherAuthority/some/path"); 478 InputStream secondInputStream = contentResolver.openInputStream(otherUri); 479 // No registered provider results in named stream 480 assertThat(secondInputStream).isInstanceOf(NamedStream.class); 481 482 shadowContentResolver.registerInputStreamSupplier( 483 uri, () -> new ByteArrayInputStream("ourStream".getBytes(UTF_8))); 484 InputStream registeredInputStream = contentResolver.openInputStream(uri); 485 byte[] byteArray = new byte[registeredInputStream.available()]; 486 registeredInputStream.read(byteArray); 487 // Explicitly registered stream takes precedence 488 assertThat(byteArray).isEqualTo("ourStream".getBytes(UTF_8)); 489 } 490 491 @Test openOutputStream_withNoRealOrRegisteredProvider_doesNotThrow()492 public void openOutputStream_withNoRealOrRegisteredProvider_doesNotThrow() throws Exception { 493 Uri uri = Uri.parse("content://invalidauthority/test/1"); 494 assertThat(contentResolver.openOutputStream(uri)).isNotNull(); 495 } 496 497 @Test openOutputStream_withRealContentProvider_canReadBytesWrittenToOutputStream()498 public void openOutputStream_withRealContentProvider_canReadBytesWrittenToOutputStream() 499 throws IOException, RemoteException { 500 Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY); 501 Uri uri = Uri.parse("content://" + AUTHORITY + "/test/1"); 502 503 // Write content through given outputstream 504 try (OutputStream outputStream = contentResolver.openOutputStream(uri)) { 505 outputStream.write("foo".getBytes(UTF_8)); 506 } 507 508 // Verify written content can be read back 509 InputStream inputStream = contentResolver.openInputStream(uri); 510 assertThat(new String(inputStream.readAllBytes(), UTF_8)).isEqualTo("foo"); 511 } 512 513 @Test openOutputStream_shouldReturnRegisteredStream()514 public void openOutputStream_shouldReturnRegisteredStream() throws Exception { 515 final Uri uri = Uri.parse("content://registeredProvider/path"); 516 517 AtomicInteger callCount = new AtomicInteger(); 518 OutputStream outputStream = 519 new OutputStream() { 520 521 @Override 522 public void write(int arg0) throws IOException { 523 callCount.incrementAndGet(); 524 } 525 526 @Override 527 public String toString() { 528 return "outputstream for " + uri; 529 } 530 }; 531 532 shadowOf(contentResolver).registerOutputStream(uri, outputStream); 533 534 assertThat(callCount.get()).isEqualTo(0); 535 contentResolver.openOutputStream(uri).write(5); 536 assertThat(callCount.get()).isEqualTo(1); 537 538 contentResolver.openOutputStream(uri21).write(5); 539 assertThat(callCount.get()).isEqualTo(1); 540 } 541 542 @Test openOutputStream_shouldReturnNewStreamFromRegisteredSupplier()543 public void openOutputStream_shouldReturnNewStreamFromRegisteredSupplier() throws Exception { 544 final Uri uri = Uri.parse("content://registeredProvider/path"); 545 546 AtomicInteger streamCreateCount = new AtomicInteger(); 547 shadowOf(contentResolver) 548 .registerOutputStreamSupplier( 549 uri, 550 () -> { 551 streamCreateCount.incrementAndGet(); 552 AtomicBoolean isClosed = new AtomicBoolean(); 553 isClosed.set(false); 554 OutputStream outputStream = 555 new OutputStream() { 556 @Override 557 public void close() { 558 isClosed.set(true); 559 } 560 561 @Override 562 public void write(int arg0) throws IOException { 563 if (isClosed.get()) { 564 throw new IOException(); 565 } 566 } 567 568 @Override 569 public String toString() { 570 return "outputstream for " + uri; 571 } 572 }; 573 return outputStream; 574 }); 575 576 assertThat(streamCreateCount.get()).isEqualTo(0); 577 OutputStream outputStream1 = contentResolver.openOutputStream(uri); 578 outputStream1.close(); 579 assertThat(streamCreateCount.get()).isEqualTo(1); 580 581 contentResolver.openOutputStream(uri).write(5); 582 assertThat(streamCreateCount.get()).isEqualTo(2); 583 } 584 585 @Test openOutputStream_withModeWithNoRealOrRegisteredProvider_throws()586 public void openOutputStream_withModeWithNoRealOrRegisteredProvider_throws() { 587 Uri uri = Uri.parse("content://invalidauthority/test/1"); 588 assertThrows(FileNotFoundException.class, () -> contentResolver.openOutputStream(uri, "wt")); 589 } 590 591 @Test openOutputStream_withModeWithRealContentProvider_canReadBytesWrittenToOutputStream()592 public void openOutputStream_withModeWithRealContentProvider_canReadBytesWrittenToOutputStream() 593 throws IOException, RemoteException { 594 Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY); 595 Uri uri = Uri.parse("content://" + AUTHORITY + "/test/1"); 596 597 // Write content through given outputstream 598 try (OutputStream outputStream = contentResolver.openOutputStream(uri, "wt")) { 599 outputStream.write("foo".getBytes(UTF_8)); 600 } 601 602 // Verify written content can be read back 603 InputStream inputStream = contentResolver.openInputStream(uri); 604 assertThat(new String(inputStream.readAllBytes(), UTF_8)).isEqualTo("foo"); 605 } 606 607 @Test openOutputStream_withModeShouldReturnRegisteredStream()608 public void openOutputStream_withModeShouldReturnRegisteredStream() throws Exception { 609 final Uri uri = Uri.parse("content://registeredProvider/path"); 610 611 AtomicInteger callCount = new AtomicInteger(); 612 OutputStream outputStream = 613 new OutputStream() { 614 615 @Override 616 public void write(int arg0) throws IOException { 617 callCount.incrementAndGet(); 618 } 619 620 @Override 621 public String toString() { 622 return "outputstream for " + uri; 623 } 624 }; 625 626 shadowOf(contentResolver).registerOutputStream(uri, outputStream); 627 628 assertThat(callCount.get()).isEqualTo(0); 629 contentResolver.openOutputStream(uri, "wt").write(5); 630 assertThat(callCount.get()).isEqualTo(1); 631 } 632 633 @Test openOutputStream_withModeShouldReturnNewStreamFromRegisteredSupplier()634 public void openOutputStream_withModeShouldReturnNewStreamFromRegisteredSupplier() 635 throws Exception { 636 final Uri uri = Uri.parse("content://registeredProvider/path"); 637 638 AtomicInteger streamCreateCount = new AtomicInteger(); 639 shadowOf(contentResolver) 640 .registerOutputStreamSupplier( 641 uri, 642 () -> { 643 streamCreateCount.incrementAndGet(); 644 AtomicBoolean isClosed = new AtomicBoolean(); 645 isClosed.set(false); 646 OutputStream outputStream = 647 new OutputStream() { 648 @Override 649 public void close() { 650 isClosed.set(true); 651 } 652 653 @Override 654 public void write(int arg0) throws IOException { 655 if (isClosed.get()) { 656 throw new IOException(); 657 } 658 } 659 660 @Override 661 public String toString() { 662 return "outputstream for " + uri; 663 } 664 }; 665 return outputStream; 666 }); 667 668 assertThat(streamCreateCount.get()).isEqualTo(0); 669 OutputStream outputStream1 = contentResolver.openOutputStream(uri, "wt"); 670 outputStream1.close(); 671 assertThat(streamCreateCount.get()).isEqualTo(1); 672 673 contentResolver.openOutputStream(uri, "wt").write(5); 674 assertThat(streamCreateCount.get()).isEqualTo(2); 675 } 676 677 @Test shouldTrackNotifiedUris()678 public void shouldTrackNotifiedUris() { 679 contentResolver.notifyChange(Uri.parse("foo"), null, true); 680 contentResolver.notifyChange(Uri.parse("bar"), null); 681 682 assertThat(shadowContentResolver.getNotifiedUris().size()).isEqualTo(2); 683 ShadowContentResolver.NotifiedUri uri = shadowContentResolver.getNotifiedUris().get(0); 684 685 assertThat(uri.uri.toString()).isEqualTo("foo"); 686 assertThat(uri.syncToNetwork).isTrue(); 687 assertThat(uri.observer).isNull(); 688 689 uri = shadowContentResolver.getNotifiedUris().get(1); 690 691 assertThat(uri.uri.toString()).isEqualTo("bar"); 692 assertThat(uri.syncToNetwork).isFalse(); 693 assertThat(uri.observer).isNull(); 694 } 695 696 @Test 697 @Config(minSdk = N) notifyChangeWithFlags_shouldTrackNotifiedUris()698 public void notifyChangeWithFlags_shouldTrackNotifiedUris() { 699 contentResolver.notifyChange(Uri.parse("foo"), null, ContentResolver.NOTIFY_SYNC_TO_NETWORK); 700 contentResolver.notifyChange(Uri.parse("bar"), null, ContentResolver.NOTIFY_UPDATE); 701 702 assertThat(shadowContentResolver.getNotifiedUris().size()).isEqualTo(2); 703 704 ShadowContentResolver.NotifiedUri uri = shadowContentResolver.getNotifiedUris().get(0); 705 706 assertThat(uri.uri.toString()).isEqualTo("foo"); 707 assertThat(uri.syncToNetwork).isTrue(); 708 assertThat(uri.observer).isNull(); 709 assertThat(uri.flags).isEqualTo(ContentResolver.NOTIFY_SYNC_TO_NETWORK); 710 711 uri = shadowContentResolver.getNotifiedUris().get(1); 712 713 assertThat(uri.uri.toString()).isEqualTo("bar"); 714 assertThat(uri.syncToNetwork).isFalse(); 715 assertThat(uri.observer).isNull(); 716 assertThat(uri.flags).isEqualTo(ContentResolver.NOTIFY_UPDATE); 717 } 718 719 @SuppressWarnings("serial") 720 @Test applyBatchForRegisteredProvider()721 public void applyBatchForRegisteredProvider() 722 throws RemoteException, OperationApplicationException { 723 final List<String> operations = new ArrayList<>(); 724 ShadowContentResolver.registerProviderInternal( 725 "registeredProvider", 726 new ContentProvider() { 727 @Override 728 public boolean onCreate() { 729 return true; 730 } 731 732 @Override 733 public Cursor query( 734 Uri uri, 735 String[] projection, 736 String selection, 737 String[] selectionArgs, 738 String sortOrder) { 739 operations.add("query"); 740 MatrixCursor cursor = new MatrixCursor(new String[] {"a"}); 741 cursor.addRow(new Object[] {"b"}); 742 return cursor; 743 } 744 745 @Override 746 public String getType(Uri uri) { 747 return null; 748 } 749 750 @Override 751 public Uri insert(Uri uri, ContentValues values) { 752 operations.add("insert"); 753 return ContentUris.withAppendedId(uri, 1); 754 } 755 756 @Override 757 public int delete(Uri uri, String selection, String[] selectionArgs) { 758 operations.add("delete"); 759 return 0; 760 } 761 762 @Override 763 public int update( 764 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 765 operations.add("update"); 766 return 0; 767 } 768 }); 769 770 final Uri uri = Uri.parse("content://registeredProvider/path"); 771 List<ContentProviderOperation> contentProviderOperations = 772 Arrays.asList( 773 ContentProviderOperation.newInsert(uri).withValue("a", "b").build(), 774 ContentProviderOperation.newUpdate(uri).withValue("a", "b").build(), 775 ContentProviderOperation.newDelete(uri).build(), 776 ContentProviderOperation.newAssertQuery(uri).withValue("a", "b").build()); 777 contentResolver.applyBatch("registeredProvider", new ArrayList<>(contentProviderOperations)); 778 779 assertThat(operations).containsExactly("insert", "update", "delete", "query"); 780 } 781 782 @Test applyBatchForUnregisteredProvider()783 public void applyBatchForUnregisteredProvider() 784 throws RemoteException, OperationApplicationException { 785 List<ContentProviderOperation> resultOperations = 786 shadowContentResolver.getContentProviderOperations(AUTHORITY); 787 assertThat(resultOperations).isNotNull(); 788 assertThat(resultOperations.size()).isEqualTo(0); 789 790 Uri uri = Uri.parse("content://org.robolectric"); 791 ArrayList<ContentProviderOperation> operations = new ArrayList<>(); 792 operations.add( 793 ContentProviderOperation.newInsert(uri) 794 .withValue("column1", "foo") 795 .withValue("column2", 5) 796 .build()); 797 operations.add( 798 ContentProviderOperation.newUpdate(uri) 799 .withSelection("id_column", new String[] {"99"}) 800 .withValue("column1", "bar") 801 .build()); 802 operations.add( 803 ContentProviderOperation.newDelete(uri) 804 .withSelection("id_column", new String[] {"11"}) 805 .build()); 806 ContentProviderResult[] result = contentResolver.applyBatch(AUTHORITY, operations); 807 808 resultOperations = shadowContentResolver.getContentProviderOperations(AUTHORITY); 809 assertThat(resultOperations).isEqualTo(operations); 810 assertThat(result).isNotNull(); 811 } 812 813 @Test shouldKeepTrackOfSyncRequests()814 public void shouldKeepTrackOfSyncRequests() { 815 ShadowContentResolver.Status status = ShadowContentResolver.getStatus(a, AUTHORITY, true); 816 assertThat(status).isNotNull(); 817 assertThat(status.syncRequests).isEqualTo(0); 818 ContentResolver.requestSync(a, AUTHORITY, new Bundle()); 819 assertThat(status.syncRequests).isEqualTo(1); 820 assertThat(status.syncExtras).isNotNull(); 821 } 822 823 @Test shouldKnowIfSyncIsActive()824 public void shouldKnowIfSyncIsActive() { 825 assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isFalse(); 826 ContentResolver.requestSync(a, AUTHORITY, new Bundle()); 827 assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isTrue(); 828 } 829 830 @Test shouldGetCurrentSyncs()831 public void shouldGetCurrentSyncs() { 832 ContentResolver.requestSync(a, AUTHORITY, new Bundle()); 833 ContentResolver.requestSync(b, AUTHORITY, new Bundle()); 834 835 List<SyncInfo> syncs = ContentResolver.getCurrentSyncs(); 836 assertThat(syncs.size()).isEqualTo(2); 837 838 SyncInfo syncA = Iterables.find(syncs, s -> s.account.equals(a)); 839 assertThat(syncA.account).isEqualTo(a); 840 assertThat(syncA.authority).isEqualTo(AUTHORITY); 841 842 SyncInfo syncB = Iterables.find(syncs, s -> s.account.equals(b)); 843 assertThat(syncB.account).isEqualTo(b); 844 assertThat(syncB.authority).isEqualTo(AUTHORITY); 845 846 ContentResolver.cancelSync(a, AUTHORITY); 847 List<SyncInfo> syncsAgain = ContentResolver.getCurrentSyncs(); 848 assertThat(syncsAgain.size()).isEqualTo(1); 849 850 SyncInfo firstAgain = syncsAgain.get(0); 851 assertThat(firstAgain.account).isEqualTo(b); 852 assertThat(firstAgain.authority).isEqualTo(AUTHORITY); 853 854 ContentResolver.cancelSync(b, AUTHORITY); 855 List<SyncInfo> s = ContentResolver.getCurrentSyncs(); 856 assertThat(s.size()).isEqualTo(0); 857 } 858 859 @Test shouldCancelSync()860 public void shouldCancelSync() { 861 ContentResolver.requestSync(a, AUTHORITY, new Bundle()); 862 ContentResolver.requestSync(b, AUTHORITY, new Bundle()); 863 assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isTrue(); 864 assertThat(ContentResolver.isSyncActive(b, AUTHORITY)).isTrue(); 865 866 ContentResolver.cancelSync(a, AUTHORITY); 867 assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isFalse(); 868 assertThat(ContentResolver.isSyncActive(b, AUTHORITY)).isTrue(); 869 } 870 871 @Test shouldSetIsSyncable()872 public void shouldSetIsSyncable() { 873 assertThat(ContentResolver.getIsSyncable(a, AUTHORITY)).isEqualTo(-1); 874 assertThat(ContentResolver.getIsSyncable(b, AUTHORITY)).isEqualTo(-1); 875 ContentResolver.setIsSyncable(a, AUTHORITY, 1); 876 ContentResolver.setIsSyncable(b, AUTHORITY, 2); 877 assertThat(ContentResolver.getIsSyncable(a, AUTHORITY)).isEqualTo(1); 878 assertThat(ContentResolver.getIsSyncable(b, AUTHORITY)).isEqualTo(2); 879 } 880 881 @Test shouldSetSyncAutomatically()882 public void shouldSetSyncAutomatically() { 883 assertThat(ContentResolver.getSyncAutomatically(a, AUTHORITY)).isFalse(); 884 ContentResolver.setSyncAutomatically(a, AUTHORITY, true); 885 assertThat(ContentResolver.getSyncAutomatically(a, AUTHORITY)).isTrue(); 886 } 887 888 @Test shouldAddPeriodicSync()889 public void shouldAddPeriodicSync() { 890 Bundle fooBar = new Bundle(); 891 fooBar.putString("foo", "bar"); 892 Bundle fooBaz = new Bundle(); 893 fooBaz.putString("foo", "baz"); 894 895 ContentResolver.addPeriodicSync(a, AUTHORITY, fooBar, 6000L); 896 ContentResolver.addPeriodicSync(a, AUTHORITY, fooBaz, 6000L); 897 ContentResolver.addPeriodicSync(b, AUTHORITY, fooBar, 6000L); 898 ContentResolver.addPeriodicSync(b, AUTHORITY, fooBaz, 6000L); 899 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)) 900 .containsExactly( 901 new PeriodicSync(a, AUTHORITY, fooBar, 6000L), 902 new PeriodicSync(a, AUTHORITY, fooBaz, 6000L)); 903 assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)) 904 .containsExactly( 905 new PeriodicSync(b, AUTHORITY, fooBar, 6000L), 906 new PeriodicSync(b, AUTHORITY, fooBaz, 6000L)); 907 908 // If same extras, but different time, simply update the time. 909 ContentResolver.addPeriodicSync(a, AUTHORITY, fooBar, 42L); 910 ContentResolver.addPeriodicSync(b, AUTHORITY, fooBaz, 42L); 911 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)) 912 .containsExactly( 913 new PeriodicSync(a, AUTHORITY, fooBar, 42L), 914 new PeriodicSync(a, AUTHORITY, fooBaz, 6000L)); 915 assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)) 916 .containsExactly( 917 new PeriodicSync(b, AUTHORITY, fooBar, 6000L), 918 new PeriodicSync(b, AUTHORITY, fooBaz, 42L)); 919 } 920 921 @Test shouldRemovePeriodSync()922 public void shouldRemovePeriodSync() { 923 Bundle fooBar = new Bundle(); 924 fooBar.putString("foo", "bar"); 925 Bundle fooBaz = new Bundle(); 926 fooBaz.putString("foo", "baz"); 927 Bundle foo42 = new Bundle(); 928 foo42.putInt("foo", 42); 929 assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)).isEmpty(); 930 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).isEmpty(); 931 932 ContentResolver.addPeriodicSync(a, AUTHORITY, fooBar, 6000L); 933 ContentResolver.addPeriodicSync(a, AUTHORITY, fooBaz, 6000L); 934 ContentResolver.addPeriodicSync(a, AUTHORITY, foo42, 6000L); 935 936 ContentResolver.addPeriodicSync(b, AUTHORITY, fooBar, 6000L); 937 ContentResolver.addPeriodicSync(b, AUTHORITY, fooBaz, 6000L); 938 ContentResolver.addPeriodicSync(b, AUTHORITY, foo42, 6000L); 939 940 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)) 941 .containsExactly( 942 new PeriodicSync(a, AUTHORITY, fooBar, 6000L), 943 new PeriodicSync(a, AUTHORITY, fooBaz, 6000L), 944 new PeriodicSync(a, AUTHORITY, foo42, 6000L)); 945 946 ContentResolver.removePeriodicSync(a, AUTHORITY, fooBar); 947 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)) 948 .containsExactly( 949 new PeriodicSync(a, AUTHORITY, fooBaz, 6000L), 950 new PeriodicSync(a, AUTHORITY, foo42, 6000L)); 951 952 ContentResolver.removePeriodicSync(a, AUTHORITY, fooBaz); 953 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)) 954 .containsExactly(new PeriodicSync(a, AUTHORITY, foo42, 6000L)); 955 956 ContentResolver.removePeriodicSync(a, AUTHORITY, foo42); 957 assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).isEmpty(); 958 assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)) 959 .containsExactly( 960 new PeriodicSync(b, AUTHORITY, fooBar, 6000L), 961 new PeriodicSync(b, AUTHORITY, fooBaz, 6000L), 962 new PeriodicSync(b, AUTHORITY, foo42, 6000L)); 963 } 964 965 @Test shouldGetPeriodSyncs()966 public void shouldGetPeriodSyncs() { 967 assertThat(ContentResolver.getPeriodicSyncs(a, AUTHORITY).size()).isEqualTo(0); 968 ContentResolver.addPeriodicSync(a, AUTHORITY, new Bundle(), 6000L); 969 970 List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(a, AUTHORITY); 971 assertThat(syncs.size()).isEqualTo(1); 972 973 PeriodicSync first = syncs.get(0); 974 assertThat(first.account).isEqualTo(a); 975 assertThat(first.authority).isEqualTo(AUTHORITY); 976 assertThat(first.period).isEqualTo(6000L); 977 assertThat(first.extras).isNotNull(); 978 } 979 980 @Test shouldValidateSyncExtras()981 public void shouldValidateSyncExtras() { 982 Bundle bundle = new Bundle(); 983 bundle.putString("foo", "strings"); 984 bundle.putLong("long", 10L); 985 bundle.putDouble("double", 10.0d); 986 bundle.putFloat("float", 10.0f); 987 bundle.putInt("int", 10); 988 bundle.putParcelable("account", a); 989 ContentResolver.validateSyncExtrasBundle(bundle); 990 } 991 992 @Test(expected = IllegalArgumentException.class) shouldValidateSyncExtrasAndThrow()993 public void shouldValidateSyncExtrasAndThrow() { 994 Bundle bundle = new Bundle(); 995 bundle.putParcelable("intent", new Intent()); 996 ContentResolver.validateSyncExtrasBundle(bundle); 997 } 998 999 @Test shouldSetMasterSyncAutomatically()1000 public void shouldSetMasterSyncAutomatically() { 1001 assertThat(ContentResolver.getMasterSyncAutomatically()).isFalse(); 1002 ContentResolver.setMasterSyncAutomatically(true); 1003 assertThat(ContentResolver.getMasterSyncAutomatically()).isTrue(); 1004 } 1005 1006 @Test shouldDelegateCallsToRegisteredProvider()1007 public void shouldDelegateCallsToRegisteredProvider() { 1008 ShadowContentResolver.registerProviderInternal( 1009 AUTHORITY, 1010 new ContentProvider() { 1011 @Override 1012 public boolean onCreate() { 1013 return false; 1014 } 1015 1016 @Override 1017 public Cursor query( 1018 Uri uri, 1019 String[] projection, 1020 String selection, 1021 String[] selectionArgs, 1022 String sortOrder) { 1023 return new BaseCursor(); 1024 } 1025 1026 @Override 1027 public Uri insert(Uri uri, ContentValues values) { 1028 return null; 1029 } 1030 1031 @Override 1032 public int delete(Uri uri, String selection, String[] selectionArgs) { 1033 return -1; 1034 } 1035 1036 @Override 1037 public int update( 1038 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1039 return -1; 1040 } 1041 1042 @Override 1043 public String getType(Uri uri) { 1044 return null; 1045 } 1046 }); 1047 final Uri uri = Uri.parse("content://" + AUTHORITY + "/some/path"); 1048 final Uri unrelated = Uri.parse("content://unrelated/some/path"); 1049 1050 assertThat(contentResolver.query(uri, null, null, null, null)).isNotNull(); 1051 assertThat(contentResolver.insert(uri, new ContentValues())).isNull(); 1052 1053 assertThat(contentResolver.delete(uri, null, null)).isEqualTo(-1); 1054 assertThat(contentResolver.update(uri, new ContentValues(), null, null)).isEqualTo(-1); 1055 1056 assertThat(contentResolver.query(unrelated, null, null, null, null)).isNull(); 1057 assertThat(contentResolver.insert(unrelated, new ContentValues())).isNotNull(); 1058 assertThat(contentResolver.delete(unrelated, null, null)).isEqualTo(1); 1059 assertThat(contentResolver.update(unrelated, new ContentValues(), null, null)).isEqualTo(1); 1060 } 1061 1062 @Test shouldThrowConfiguredExceptionWhenRegisteringContentObservers()1063 public void shouldThrowConfiguredExceptionWhenRegisteringContentObservers() { 1064 ShadowContentResolver scr = shadowOf(contentResolver); 1065 scr.setRegisterContentProviderException(EXTERNAL_CONTENT_URI, new SecurityException()); 1066 try { 1067 contentResolver.registerContentObserver( 1068 EXTERNAL_CONTENT_URI, true, new TestContentObserver(null)); 1069 fail(); 1070 } catch (SecurityException expected) { 1071 } 1072 } 1073 1074 @Test shouldClearConfiguredExceptionForRegisteringContentObservers()1075 public void shouldClearConfiguredExceptionForRegisteringContentObservers() { 1076 ShadowContentResolver scr = shadowOf(contentResolver); 1077 scr.setRegisterContentProviderException(EXTERNAL_CONTENT_URI, new SecurityException()); 1078 scr.clearRegisterContentProviderException(EXTERNAL_CONTENT_URI); 1079 // Should not throw the SecurityException. 1080 contentResolver.registerContentObserver( 1081 EXTERNAL_CONTENT_URI, true, new TestContentObserver(null)); 1082 } 1083 1084 @Test shouldRegisterContentObservers()1085 public void shouldRegisterContentObservers() { 1086 TestContentObserver co = new TestContentObserver(null); 1087 ShadowContentResolver scr = shadowOf(contentResolver); 1088 1089 assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).isEmpty(); 1090 1091 contentResolver.registerContentObserver(EXTERNAL_CONTENT_URI, true, co); 1092 1093 assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).containsExactly((ContentObserver) co); 1094 1095 assertThat(co.changed).isFalse(); 1096 contentResolver.notifyChange(EXTERNAL_CONTENT_URI, null); 1097 assertThat(co.changed).isTrue(); 1098 1099 contentResolver.unregisterContentObserver(co); 1100 assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).isEmpty(); 1101 } 1102 1103 @Test shouldUnregisterContentObservers()1104 public void shouldUnregisterContentObservers() { 1105 TestContentObserver co = new TestContentObserver(null); 1106 ShadowContentResolver scr = shadowOf(contentResolver); 1107 contentResolver.registerContentObserver(EXTERNAL_CONTENT_URI, true, co); 1108 assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).contains(co); 1109 1110 contentResolver.unregisterContentObserver(co); 1111 assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).isEmpty(); 1112 1113 assertThat(co.changed).isFalse(); 1114 contentResolver.notifyChange(EXTERNAL_CONTENT_URI, null); 1115 assertThat(co.changed).isFalse(); 1116 } 1117 1118 @Test shouldNotifyChildContentObservers()1119 public void shouldNotifyChildContentObservers() throws Exception { 1120 TestContentObserver co1 = new TestContentObserver(null); 1121 TestContentObserver co2 = new TestContentObserver(null); 1122 1123 Uri childUri = EXTERNAL_CONTENT_URI.buildUpon().appendPath("path").build(); 1124 1125 contentResolver.registerContentObserver(EXTERNAL_CONTENT_URI, true, co1); 1126 contentResolver.registerContentObserver(childUri, false, co2); 1127 1128 co1.changed = co2.changed = false; 1129 contentResolver.notifyChange(childUri, null); 1130 assertThat(co1.changed).isTrue(); 1131 assertThat(co2.changed).isTrue(); 1132 1133 co1.changed = co2.changed = false; 1134 contentResolver.notifyChange(EXTERNAL_CONTENT_URI, null); 1135 assertThat(co1.changed).isTrue(); 1136 assertThat(co2.changed).isFalse(); 1137 1138 co1.changed = co2.changed = false; 1139 contentResolver.notifyChange(childUri.buildUpon().appendPath("extra").build(), null); 1140 assertThat(co1.changed).isTrue(); 1141 assertThat(co2.changed).isFalse(); 1142 } 1143 1144 @Test getProvider_shouldCreateProviderFromManifest()1145 public void getProvider_shouldCreateProviderFromManifest() throws Exception { 1146 Uri uri = Uri.parse("content://org.robolectric.authority1/shadows"); 1147 ContentProvider provider = ShadowContentResolver.getProvider(uri); 1148 assertThat(provider).isNotNull(); 1149 assertThat(provider.getReadPermission()).isEqualTo("READ_PERMISSION"); 1150 assertThat(provider.getWritePermission()).isEqualTo("WRITE_PERMISSION"); 1151 assertThat(provider.getPathPermissions()).asList().hasSize(1); 1152 1153 // unfortunately, there is no direct way of testing if authority is set or not 1154 // however, it's checked in ContentProvider.Transport method calls (validateIncomingUri), so 1155 // it's the closest we can test against 1156 if (RuntimeEnvironment.getApiLevel() <= 28) { 1157 provider.getIContentProvider().getType(uri); // should not throw 1158 } else { 1159 // just call validateIncomingUri directly 1160 provider.validateIncomingUri(uri); 1161 } 1162 } 1163 1164 @Test 1165 @Config(manifest = NONE) 1166 @SuppressWarnings("RobolectricSystemContext") // preexisting when check was enabled getProvider_shouldNotReturnAnyProviderWhenManifestIsNull()1167 public void getProvider_shouldNotReturnAnyProviderWhenManifestIsNull() { 1168 Application application = new Application(); 1169 shadowOf(application).callAttach(RuntimeEnvironment.systemContext); 1170 assertThat(ShadowContentResolver.getProvider(Uri.parse("content://"))).isNull(); 1171 } 1172 1173 @Test openAssetFileDescriptor_shouldOpenDescriptor()1174 public void openAssetFileDescriptor_shouldOpenDescriptor() throws IOException { 1175 Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY); 1176 1177 try (AssetFileDescriptor afd = 1178 contentResolver.openAssetFileDescriptor( 1179 Uri.parse("content://" + AUTHORITY + "/whatever"), "r")) { 1180 FileDescriptor descriptor = afd.getFileDescriptor(); 1181 assertThat(descriptor).isNotNull(); 1182 } 1183 } 1184 1185 @Test openTypedAssetFileDescriptor_shouldOpenDescriptor()1186 public void openTypedAssetFileDescriptor_shouldOpenDescriptor() 1187 throws IOException, RemoteException { 1188 Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY); 1189 1190 try (AssetFileDescriptor afd = 1191 contentResolver.openTypedAssetFileDescriptor( 1192 Uri.parse("content://" + AUTHORITY + "/whatever"), "*/*", null)) { 1193 1194 FileDescriptor descriptor = afd.getFileDescriptor(); 1195 assertThat(descriptor).isNotNull(); 1196 } 1197 } 1198 1199 @Test takeAndReleasePersistableUriPermissions()1200 public void takeAndReleasePersistableUriPermissions() { 1201 List<UriPermission> permissions = contentResolver.getPersistedUriPermissions(); 1202 assertThat(permissions).isEmpty(); 1203 1204 // Take the read permission for the uri. 1205 Uri uri = Uri.parse("content://" + AUTHORITY + "/whatever"); 1206 contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 1207 assertThat(permissions).hasSize(1); 1208 assertThat(permissions.get(0).getUri()).isSameInstanceAs(uri); 1209 assertThat(permissions.get(0).isReadPermission()).isTrue(); 1210 assertThat(permissions.get(0).isWritePermission()).isFalse(); 1211 1212 // Take the write permission for the uri. 1213 contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 1214 assertThat(permissions).hasSize(1); 1215 assertThat(permissions.get(0).getUri()).isSameInstanceAs(uri); 1216 assertThat(permissions.get(0).isReadPermission()).isTrue(); 1217 assertThat(permissions.get(0).isWritePermission()).isTrue(); 1218 1219 // Release the read permission for the uri. 1220 contentResolver.releasePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); 1221 assertThat(permissions).hasSize(1); 1222 assertThat(permissions.get(0).getUri()).isSameInstanceAs(uri); 1223 assertThat(permissions.get(0).isReadPermission()).isFalse(); 1224 assertThat(permissions.get(0).isWritePermission()).isTrue(); 1225 1226 // Release the write permission for the uri. 1227 contentResolver.releasePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 1228 assertThat(permissions).isEmpty(); 1229 } 1230 1231 @Test getSyncAdapterTypes()1232 public void getSyncAdapterTypes() { 1233 SyncAdapterType[] syncAdapterTypes = 1234 new SyncAdapterType[] { 1235 new SyncAdapterType( 1236 "authority1", 1237 "accountType1", 1238 /* userVisible= */ false, 1239 /* supportsUploading= */ false), 1240 new SyncAdapterType( 1241 "authority2", 1242 "accountType2", 1243 /* userVisible= */ true, 1244 /* supportsUploading= */ false), 1245 new SyncAdapterType( 1246 "authority3", "accountType3", /* userVisible= */ true, /* supportsUploading= */ true) 1247 }; 1248 1249 ShadowContentResolver.setSyncAdapterTypes(syncAdapterTypes); 1250 assertThat(ContentResolver.getSyncAdapterTypes()).isEqualTo(syncAdapterTypes); 1251 } 1252 1253 private static class QueryParamTrackingCursor extends BaseCursor { 1254 public Uri uri; 1255 public String[] projection; 1256 public String selection; 1257 public String[] selectionArgs; 1258 public String sortOrder; 1259 1260 @Override setQuery( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)1261 public void setQuery( 1262 Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { 1263 this.uri = uri; 1264 this.projection = projection; 1265 this.selection = selection; 1266 this.selectionArgs = selectionArgs; 1267 this.sortOrder = sortOrder; 1268 } 1269 } 1270 1271 private static class TestContentObserver extends ContentObserver { TestContentObserver(Handler handler)1272 public TestContentObserver(Handler handler) { 1273 super(handler); 1274 } 1275 1276 public boolean changed = false; 1277 1278 @Override onChange(boolean selfChange)1279 public void onChange(boolean selfChange) { 1280 changed = true; 1281 } 1282 1283 @Override onChange(boolean selfChange, Uri uri)1284 public void onChange(boolean selfChange, Uri uri) { 1285 changed = true; 1286 } 1287 } 1288 1289 /** Provider that opens a temporary file. */ 1290 public static class MyContentProvider extends ContentProvider { 1291 @Override onCreate()1292 public boolean onCreate() { 1293 return true; 1294 } 1295 1296 @Override query(Uri uri, String[] strings, String s, String[] strings1, String s1)1297 public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { 1298 return null; 1299 } 1300 1301 @Override getType(Uri uri)1302 public String getType(Uri uri) { 1303 return null; 1304 } 1305 1306 @Override insert(Uri uri, ContentValues contentValues)1307 public Uri insert(Uri uri, ContentValues contentValues) { 1308 return null; 1309 } 1310 1311 @Override delete(Uri uri, String s, String[] strings)1312 public int delete(Uri uri, String s, String[] strings) { 1313 return 0; 1314 } 1315 1316 @Override update(Uri uri, ContentValues contentValues, String s, String[] strings)1317 public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { 1318 return 0; 1319 } 1320 1321 @Override openFile(Uri uri, String mode)1322 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 1323 final File file = 1324 new File(ApplicationProvider.getApplicationContext().getFilesDir(), "test_file"); 1325 try { 1326 file.createNewFile(); 1327 } catch (IOException e) { 1328 throw new RuntimeException("error creating new file", e); 1329 } 1330 return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); 1331 } 1332 } 1333 } 1334