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