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