Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.KITKAT;
      4 import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
      5 import static java.nio.charset.StandardCharsets.UTF_8;
      6 import static org.assertj.core.api.Assertions.assertThat;
      7 import static org.mockito.Matchers.same;
      8 import static org.mockito.Mockito.doReturn;
      9 import static org.mockito.Mockito.mock;
     10 import static org.mockito.Mockito.verify;
     11 import static org.robolectric.Shadows.shadowOf;
     12 import static org.robolectric.annotation.Config.NONE;
     13 
     14 import android.accounts.Account;
     15 import android.app.Application;
     16 import android.content.ContentProvider;
     17 import android.content.ContentProviderOperation;
     18 import android.content.ContentProviderResult;
     19 import android.content.ContentResolver;
     20 import android.content.ContentUris;
     21 import android.content.ContentValues;
     22 import android.content.Intent;
     23 import android.content.OperationApplicationException;
     24 import android.content.PeriodicSync;
     25 import android.content.UriPermission;
     26 import android.content.pm.ProviderInfo;
     27 import android.content.res.AssetFileDescriptor;
     28 import android.database.ContentObserver;
     29 import android.database.Cursor;
     30 import android.database.MatrixCursor;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.os.CancellationSignal;
     34 import android.os.Handler;
     35 import android.os.ParcelFileDescriptor;
     36 import android.os.RemoteException;
     37 import java.io.ByteArrayInputStream;
     38 import java.io.File;
     39 import java.io.FileDescriptor;
     40 import java.io.FileNotFoundException;
     41 import java.io.IOException;
     42 import java.io.InputStream;
     43 import java.io.OutputStream;
     44 import java.util.ArrayList;
     45 import java.util.List;
     46 import org.junit.Before;
     47 import org.junit.Test;
     48 import org.junit.runner.RunWith;
     49 import org.mockito.ArgumentCaptor;
     50 import org.robolectric.Robolectric;
     51 import org.robolectric.RobolectricTestRunner;
     52 import org.robolectric.RuntimeEnvironment;
     53 import org.robolectric.annotation.Config;
     54 import org.robolectric.fakes.BaseCursor;
     55 
     56 @RunWith(RobolectricTestRunner.class)
     57 public class ShadowContentResolverTest {
     58   private static final String AUTHORITY = "org.robolectric";
     59 
     60   private ContentResolver contentResolver;
     61   private ShadowContentResolver shadowContentResolver;
     62   private Uri uri21;
     63   private Uri uri22;
     64   private Account a, b;
     65 
     66   @Before
     67   public void setUp() {
     68     contentResolver = RuntimeEnvironment.application.getContentResolver();
     69     shadowContentResolver = shadowOf(contentResolver);
     70     uri21 = Uri.parse(EXTERNAL_CONTENT_URI.toString() + "/21");
     71     uri22 = Uri.parse(EXTERNAL_CONTENT_URI.toString() + "/22");
     72 
     73     a = new Account("a", "type");
     74     b = new Account("b", "type");
     75   }
     76 
     77   @Test
     78   public void insert_shouldReturnIncreasingUris() {
     79     shadowContentResolver.setNextDatabaseIdForInserts(20);
     80 
     81     assertThat(contentResolver.insert(EXTERNAL_CONTENT_URI, new ContentValues())).isEqualTo(uri21);
     82     assertThat(contentResolver.insert(EXTERNAL_CONTENT_URI, new ContentValues())).isEqualTo(uri22);
     83   }
     84 
     85   @Test
     86   public void getType_shouldDefaultToNull() {
     87     assertThat(contentResolver.getType(uri21)).isNull();
     88   }
     89 
     90   @Test
     91   public void getType_shouldReturnProviderValue() {
     92     ShadowContentResolver.registerProviderInternal(AUTHORITY, new ContentProvider() {
     93       @Override public boolean onCreate() {
     94         return false;
     95       }
     96       @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     97         return new BaseCursor();
     98       }
     99       @Override public Uri insert(Uri uri, ContentValues values) {
    100         return null;
    101       }
    102       @Override public int delete(Uri uri, String selection, String[] selectionArgs) {
    103         return -1;
    104       }
    105       @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    106         return -1;
    107       }
    108       @Override public String getType(Uri uri) {
    109         return "mytype";
    110       }
    111     });
    112     final Uri uri = Uri.parse("content://"+AUTHORITY+"/some/path");
    113     assertThat(contentResolver.getType(uri)).isEqualTo("mytype");
    114   }
    115 
    116   @Test
    117   public void insert_shouldTrackInsertStatements() {
    118     ContentValues contentValues = new ContentValues();
    119     contentValues.put("foo", "bar");
    120     contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues);
    121     assertThat(shadowContentResolver.getInsertStatements().size()).isEqualTo(1);
    122     assertThat(shadowContentResolver.getInsertStatements().get(0).getUri()).isEqualTo(EXTERNAL_CONTENT_URI);
    123     assertThat(shadowContentResolver.getInsertStatements().get(0).getContentValues().getAsString("foo")).isEqualTo("bar");
    124 
    125     contentValues = new ContentValues();
    126     contentValues.put("hello", "world");
    127     contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues);
    128     assertThat(shadowContentResolver.getInsertStatements().size()).isEqualTo(2);
    129     assertThat(shadowContentResolver.getInsertStatements().get(1).getContentValues().getAsString("hello")).isEqualTo("world");
    130   }
    131 
    132   @Test
    133   public void insert_shouldTrackUpdateStatements() {
    134     ContentValues contentValues = new ContentValues();
    135     contentValues.put("foo", "bar");
    136     contentResolver.update(EXTERNAL_CONTENT_URI, contentValues, "robolectric", new String[] { "awesome" });
    137     assertThat(shadowContentResolver.getUpdateStatements().size()).isEqualTo(1);
    138     assertThat(shadowContentResolver.getUpdateStatements().get(0).getUri()).isEqualTo(EXTERNAL_CONTENT_URI);
    139     assertThat(shadowContentResolver.getUpdateStatements().get(0).getContentValues().getAsString("foo")).isEqualTo("bar");
    140     assertThat(shadowContentResolver.getUpdateStatements().get(0).getWhere()).isEqualTo("robolectric");
    141     assertThat(shadowContentResolver.getUpdateStatements().get(0).getSelectionArgs()).isEqualTo(new String[]{"awesome"});
    142 
    143     contentValues = new ContentValues();
    144     contentValues.put("hello", "world");
    145     contentResolver.update(EXTERNAL_CONTENT_URI, contentValues, null, null);
    146     assertThat(shadowContentResolver.getUpdateStatements().size()).isEqualTo(2);
    147     assertThat(shadowContentResolver.getUpdateStatements().get(1).getUri()).isEqualTo(EXTERNAL_CONTENT_URI);
    148     assertThat(shadowContentResolver.getUpdateStatements().get(1).getContentValues().getAsString("hello")).isEqualTo("world");
    149     assertThat(shadowContentResolver.getUpdateStatements().get(1).getWhere()).isNull();
    150     assertThat(shadowContentResolver.getUpdateStatements().get(1).getSelectionArgs()).isNull();
    151   }
    152 
    153   @Test
    154   public void insert_supportsNullContentValues() {
    155     contentResolver.insert(EXTERNAL_CONTENT_URI, null);
    156     assertThat(shadowContentResolver.getInsertStatements().get(0).getContentValues()).isNull();
    157   }
    158 
    159   @Test
    160   public void update_supportsNullContentValues() {
    161     contentResolver.update(EXTERNAL_CONTENT_URI, null, null, null);
    162     assertThat(shadowContentResolver.getUpdateStatements().get(0).getContentValues()).isNull();
    163   }
    164 
    165   @Test
    166   public void delete_shouldTrackDeletedUris() {
    167     assertThat(shadowContentResolver.getDeletedUris().size()).isEqualTo(0);
    168 
    169     assertThat(contentResolver.delete(uri21, null, null)).isEqualTo(1);
    170     assertThat(shadowContentResolver.getDeletedUris()).contains(uri21);
    171     assertThat(shadowContentResolver.getDeletedUris().size()).isEqualTo(1);
    172 
    173     assertThat(contentResolver.delete(uri22, null, null)).isEqualTo(1);
    174     assertThat(shadowContentResolver.getDeletedUris()).contains(uri22);
    175     assertThat(shadowContentResolver.getDeletedUris().size()).isEqualTo(2);
    176   }
    177 
    178   @Test
    179   public void delete_shouldTrackDeletedStatements() {
    180     assertThat(shadowContentResolver.getDeleteStatements().size()).isEqualTo(0);
    181 
    182     assertThat(contentResolver.delete(uri21, "id", new String[]{"5"})).isEqualTo(1);
    183     assertThat(shadowContentResolver.getDeleteStatements().size()).isEqualTo(1);
    184     assertThat(shadowContentResolver.getDeleteStatements().get(0).getUri()).isEqualTo(uri21);
    185     assertThat(shadowContentResolver.getDeleteStatements().get(0).getContentProvider()).isNull();
    186     assertThat(shadowContentResolver.getDeleteStatements().get(0).getWhere()).isEqualTo("id");
    187     assertThat(shadowContentResolver.getDeleteStatements().get(0).getSelectionArgs()[0]).isEqualTo("5");
    188 
    189     assertThat(contentResolver.delete(uri21, "foo", new String[]{"bar"})).isEqualTo(1);
    190     assertThat(shadowContentResolver.getDeleteStatements().size()).isEqualTo(2);
    191     assertThat(shadowContentResolver.getDeleteStatements().get(1).getUri()).isEqualTo(uri21);
    192     assertThat(shadowContentResolver.getDeleteStatements().get(1).getWhere()).isEqualTo("foo");
    193     assertThat(shadowContentResolver.getDeleteStatements().get(1).getSelectionArgs()[0]).isEqualTo("bar");
    194   }
    195 
    196   @Test
    197   public void whenCursorHasBeenSet_query_shouldReturnTheCursor() {
    198     assertThat(shadowContentResolver.query(null, null, null, null, null)).isNull();
    199     BaseCursor cursor = new BaseCursor();
    200     shadowContentResolver.setCursor(cursor);
    201     assertThat((BaseCursor) shadowContentResolver.query(null, null, null, null, null)).isSameAs(cursor);
    202   }
    203 
    204   @Test
    205   public void whenCursorHasBeenSet_queryWithCancellationSignal_shouldReturnTheCursor() {
    206     assertThat(shadowContentResolver.query(null, null, null, null, null, new CancellationSignal())).isNull();
    207     BaseCursor cursor = new BaseCursor();
    208     shadowContentResolver.setCursor(cursor);
    209     assertThat((BaseCursor) shadowContentResolver.query(null, null, null, null, null, new CancellationSignal())).isSameAs(cursor);
    210   }
    211 
    212   @Test
    213   public void query_shouldReturnSpecificCursorsForSpecificUris() {
    214     assertThat(shadowContentResolver.query(uri21, null, null, null, null)).isNull();
    215     assertThat(shadowContentResolver.query(uri22, null, null, null, null)).isNull();
    216 
    217     BaseCursor cursor21 = new BaseCursor();
    218     BaseCursor cursor22 = new BaseCursor();
    219     shadowContentResolver.setCursor(uri21, cursor21);
    220     shadowContentResolver.setCursor(uri22, cursor22);
    221 
    222     assertThat((BaseCursor) shadowContentResolver.query(uri21, null, null, null, null)).isSameAs(cursor21);
    223     assertThat((BaseCursor) shadowContentResolver.query(uri22, null, null, null, null)).isSameAs(cursor22);
    224   }
    225 
    226   @Test
    227   public void query_shouldKnowWhatItsParamsWere() {
    228     String[] projection = {};
    229     String selection = "select";
    230     String[] selectionArgs = {};
    231     String sortOrder = "order";
    232 
    233     QueryParamTrackingCursor testCursor = new QueryParamTrackingCursor();
    234 
    235     shadowContentResolver.setCursor(testCursor);
    236     Cursor cursor = shadowContentResolver.query(uri21, projection, selection, selectionArgs, sortOrder);
    237     assertThat((QueryParamTrackingCursor) cursor).isEqualTo(testCursor);
    238     assertThat(testCursor.uri).isEqualTo(uri21);
    239     assertThat(testCursor.projection).isEqualTo(projection);
    240     assertThat(testCursor.selection).isEqualTo(selection);
    241     assertThat(testCursor.selectionArgs).isEqualTo(selectionArgs);
    242     assertThat(testCursor.sortOrder).isEqualTo(sortOrder);
    243   }
    244 
    245   @Test
    246   public void acquireUnstableProvider_shouldDefaultToNull() {
    247     assertThat(contentResolver.acquireUnstableProvider(uri21)).isNull();
    248   }
    249 
    250   @Test
    251   public void acquireUnstableProvider_shouldReturnWithUri() {
    252     ContentProvider cp = mock(ContentProvider.class);
    253     ShadowContentResolver.registerProviderInternal(AUTHORITY, cp);
    254     final Uri uri = Uri.parse("content://" + AUTHORITY);
    255     assertThat(contentResolver.acquireUnstableProvider(uri)).isSameAs(cp.getIContentProvider());
    256   }
    257 
    258   @Test
    259   public void acquireUnstableProvider_shouldReturnWithString() {
    260     ContentProvider cp = mock(ContentProvider.class);
    261     ShadowContentResolver.registerProviderInternal(AUTHORITY, cp);
    262     assertThat(contentResolver.acquireUnstableProvider(AUTHORITY)).isSameAs(cp.getIContentProvider());
    263   }
    264 
    265   @Test
    266   public void call_shouldCallProvider() {
    267     final String METHOD = "method";
    268     final String ARG = "arg";
    269     final Bundle EXTRAS = new Bundle();
    270     final Uri uri = Uri.parse("content://" + AUTHORITY);
    271 
    272     ContentProvider provider = mock(ContentProvider.class);
    273     doReturn(null).when(provider).call(METHOD, ARG, EXTRAS);
    274     ShadowContentResolver.registerProviderInternal(AUTHORITY, provider);
    275 
    276     contentResolver.call(uri, METHOD, ARG, EXTRAS);
    277     verify(provider).call(METHOD, ARG, EXTRAS);
    278   }
    279 
    280   @Test
    281   public void registerProvider_shouldAttachProviderInfo() {
    282     ContentProvider mock = mock(ContentProvider.class);
    283 
    284     ProviderInfo providerInfo0 = new ProviderInfo();
    285     providerInfo0.authority = "the-authority"; // todo: support multiple authorities
    286     providerInfo0.grantUriPermissions = true;
    287     mock.attachInfo(RuntimeEnvironment.application, providerInfo0);
    288     mock.onCreate();
    289 
    290     ArgumentCaptor<ProviderInfo> captor = ArgumentCaptor.forClass(ProviderInfo.class);
    291     verify(mock).attachInfo(same(RuntimeEnvironment.application), captor.capture());
    292     ProviderInfo providerInfo = captor.getValue();
    293 
    294     assertThat(providerInfo.authority).isEqualTo("the-authority");
    295     assertThat(providerInfo.grantUriPermissions).isEqualTo(true);
    296   }
    297 
    298   @Test(expected = UnsupportedOperationException.class)
    299   public void openInputStream_shouldReturnAnInputStreamThatExceptionsOnRead() throws Exception {
    300     InputStream inputStream = contentResolver.openInputStream(uri21);
    301     inputStream.read();
    302   }
    303 
    304   @Test
    305   public void openInputStream_returnsPreRegisteredStream() throws Exception {
    306     shadowContentResolver.registerInputStream(uri21, new ByteArrayInputStream("ourStream".getBytes(UTF_8)));
    307     InputStream inputStream = contentResolver.openInputStream(uri21);
    308     byte[] data = new byte[9];
    309     inputStream.read(data);
    310     assertThat(new String(data, UTF_8)).isEqualTo("ourStream");
    311   }
    312 
    313   @Test
    314   public void openOutputStream_shouldReturnAnOutputStream() throws Exception {
    315     assertThat(contentResolver.openOutputStream(uri21)).isInstanceOf(OutputStream.class);
    316   }
    317 
    318   @Test
    319   public void shouldTrackNotifiedUris() {
    320     contentResolver.notifyChange(Uri.parse("foo"), null, true);
    321     contentResolver.notifyChange(Uri.parse("bar"), null);
    322 
    323     assertThat(shadowContentResolver.getNotifiedUris().size()).isEqualTo(2);
    324     ShadowContentResolver.NotifiedUri uri = shadowContentResolver.getNotifiedUris().get(0);
    325 
    326     assertThat(uri.uri.toString()).isEqualTo("foo");
    327     assertThat(uri.syncToNetwork).isTrue();
    328     assertThat(uri.observer).isNull();
    329 
    330     uri = shadowContentResolver.getNotifiedUris().get(1);
    331 
    332     assertThat(uri.uri.toString()).isEqualTo("bar");
    333     assertThat(uri.syncToNetwork).isFalse();
    334     assertThat(uri.observer).isNull();
    335   }
    336 
    337   @SuppressWarnings("serial")
    338   @Test
    339   public void applyBatchForRegisteredProvider() throws RemoteException, OperationApplicationException {
    340     final List<String> operations = new ArrayList<>();
    341     ShadowContentResolver.registerProviderInternal("registeredProvider", new ContentProvider() {
    342       @Override
    343       public boolean onCreate() {
    344         return true;
    345       }
    346 
    347       @Override
    348       public Cursor query(Uri uri, String[] projection, String selection,
    349           String[] selectionArgs, String sortOrder) {
    350         operations.add("query");
    351         MatrixCursor cursor = new MatrixCursor(new String[] {"a"});
    352         cursor.addRow(new Object[] {"b"});
    353         return cursor;
    354       }
    355 
    356       @Override
    357       public String getType(Uri uri) {
    358         return null;
    359       }
    360 
    361       @Override
    362       public Uri insert(Uri uri, ContentValues values) {
    363         operations.add("insert");
    364         return ContentUris.withAppendedId(uri, 1);
    365       }
    366 
    367       @Override
    368       public int delete(Uri uri, String selection, String[] selectionArgs) {
    369         operations.add("delete");
    370         return 0;
    371       }
    372 
    373       @Override
    374       public int update(Uri uri, ContentValues values, String selection,
    375           String[] selectionArgs) {
    376         operations.add("update");
    377         return 0;
    378       }
    379 
    380     });
    381 
    382     final Uri uri = Uri.parse("content://registeredProvider/path");
    383     contentResolver.applyBatch("registeredProvider", new ArrayList<ContentProviderOperation>() {
    384       {
    385         add(ContentProviderOperation.newInsert(uri).withValue("a", "b").build());
    386         add(ContentProviderOperation.newUpdate(uri).withValue("a", "b").build());
    387         add(ContentProviderOperation.newDelete(uri).build());
    388         add(ContentProviderOperation.newAssertQuery(uri).withValue("a", "b").build());
    389       }
    390     });
    391 
    392     assertThat(operations).containsExactly("insert", "update", "delete", "query");
    393   }
    394 
    395   @Test
    396   public void applyBatchForUnregisteredProvider() throws RemoteException, OperationApplicationException {
    397     List<ContentProviderOperation> resultOperations = shadowContentResolver.getContentProviderOperations(AUTHORITY);
    398     assertThat(resultOperations).isNotNull();
    399     assertThat(resultOperations.size()).isEqualTo(0);
    400 
    401     ContentProviderResult[] contentProviderResults = new ContentProviderResult[] {
    402         new ContentProviderResult(1),
    403         new ContentProviderResult(1),
    404     };
    405     shadowContentResolver.setContentProviderResult(contentProviderResults);
    406     Uri uri = Uri.parse("content://org.robolectric");
    407     ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    408     operations.add(ContentProviderOperation.newInsert(uri)
    409         .withValue("column1", "foo")
    410         .withValue("column2", 5)
    411         .build());
    412     operations.add(ContentProviderOperation.newUpdate(uri)
    413         .withSelection("id_column", new String[] { "99" })
    414         .withValue("column1", "bar")
    415         .build());
    416     operations.add(ContentProviderOperation.newDelete(uri)
    417         .withSelection("id_column", new String[] { "11" })
    418         .build());
    419     ContentProviderResult[] result = contentResolver.applyBatch(AUTHORITY, operations);
    420 
    421     resultOperations = shadowContentResolver.getContentProviderOperations(AUTHORITY);
    422     assertThat(resultOperations).isEqualTo(operations);
    423     assertThat(result).isEqualTo(contentProviderResults);
    424   }
    425 
    426   @Test
    427   public void shouldKeepTrackOfSyncRequests() {
    428     ShadowContentResolver.Status status = ShadowContentResolver.getStatus(a, AUTHORITY, true);
    429     assertThat(status).isNotNull();
    430     assertThat(status.syncRequests).isEqualTo(0);
    431     ContentResolver.requestSync(a, AUTHORITY, new Bundle());
    432     assertThat(status.syncRequests).isEqualTo(1);
    433     assertThat(status.syncExtras).isNotNull();
    434   }
    435 
    436   @Test
    437   public void shouldKnowIfSyncIsActive() {
    438     assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isFalse();
    439     ContentResolver.requestSync(a, AUTHORITY, new Bundle());
    440     assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isTrue();
    441   }
    442 
    443   @Test
    444   public void shouldCancelSync() {
    445     ContentResolver.requestSync(a, AUTHORITY, new Bundle());
    446     ContentResolver.requestSync(b, AUTHORITY, new Bundle());
    447     assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isTrue();
    448     assertThat(ContentResolver.isSyncActive(b, AUTHORITY)).isTrue();
    449 
    450     ContentResolver.cancelSync(a, AUTHORITY);
    451     assertThat(ContentResolver.isSyncActive(a, AUTHORITY)).isFalse();
    452     assertThat(ContentResolver.isSyncActive(b, AUTHORITY)).isTrue();
    453   }
    454 
    455   @Test
    456   public void shouldSetIsSyncable() {
    457     assertThat(ContentResolver.getIsSyncable(a, AUTHORITY)).isEqualTo(-1);
    458     assertThat(ContentResolver.getIsSyncable(b, AUTHORITY)).isEqualTo(-1);
    459     ContentResolver.setIsSyncable(a, AUTHORITY, 1);
    460     ContentResolver.setIsSyncable(b, AUTHORITY, 2);
    461     assertThat(ContentResolver.getIsSyncable(a, AUTHORITY)).isEqualTo(1);
    462     assertThat(ContentResolver.getIsSyncable(b, AUTHORITY)).isEqualTo(2);
    463   }
    464 
    465   @Test
    466   public void shouldSetSyncAutomatically() {
    467     assertThat(ContentResolver.getSyncAutomatically(a, AUTHORITY)).isFalse();
    468     ContentResolver.setSyncAutomatically(a, AUTHORITY, true);
    469     assertThat(ContentResolver.getSyncAutomatically(a, AUTHORITY)).isTrue();
    470   }
    471 
    472   @Test
    473   public void shouldAddPeriodicSync() {
    474     Bundle fooBar = new Bundle();
    475     fooBar.putString("foo", "bar");
    476     Bundle fooBaz = new Bundle();
    477     fooBaz.putString("foo", "baz");
    478 
    479     ContentResolver.addPeriodicSync(a, AUTHORITY, fooBar, 6000L);
    480     ContentResolver.addPeriodicSync(a, AUTHORITY, fooBaz, 6000L);
    481     ContentResolver.addPeriodicSync(b, AUTHORITY, fooBar, 6000L);
    482     ContentResolver.addPeriodicSync(b, AUTHORITY, fooBaz, 6000L);
    483     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).containsOnly(
    484         new PeriodicSync(a, AUTHORITY, fooBar, 6000L),
    485         new PeriodicSync(a, AUTHORITY, fooBaz, 6000L));
    486     assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)).containsOnly(
    487         new PeriodicSync(b, AUTHORITY, fooBar, 6000L),
    488         new PeriodicSync(b, AUTHORITY, fooBaz, 6000L));
    489 
    490     // If same extras, but different time, simply update the time.
    491     ContentResolver.addPeriodicSync(a, AUTHORITY, fooBar, 42L);
    492     ContentResolver.addPeriodicSync(b, AUTHORITY, fooBaz, 42L);
    493     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).containsOnly(
    494         new PeriodicSync(a, AUTHORITY, fooBar, 42L),
    495         new PeriodicSync(a, AUTHORITY, fooBaz, 6000L));
    496     assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)).containsOnly(
    497         new PeriodicSync(b, AUTHORITY, fooBar, 6000L),
    498         new PeriodicSync(b, AUTHORITY, fooBaz, 42L));
    499   }
    500 
    501   @Test
    502   public void shouldRemovePeriodSync() {
    503     Bundle fooBar = new Bundle();
    504     fooBar.putString("foo", "bar");
    505     Bundle fooBaz = new Bundle();
    506     fooBaz.putString("foo", "baz");
    507     Bundle foo42 = new Bundle();
    508     foo42.putInt("foo", 42);
    509     assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)).isEmpty();
    510     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).isEmpty();
    511 
    512     ContentResolver.addPeriodicSync(a, AUTHORITY, fooBar, 6000L);
    513     ContentResolver.addPeriodicSync(a, AUTHORITY, fooBaz, 6000L);
    514     ContentResolver.addPeriodicSync(a, AUTHORITY, foo42, 6000L);
    515 
    516     ContentResolver.addPeriodicSync(b, AUTHORITY, fooBar, 6000L);
    517     ContentResolver.addPeriodicSync(b, AUTHORITY, fooBaz, 6000L);
    518     ContentResolver.addPeriodicSync(b, AUTHORITY, foo42, 6000L);
    519 
    520     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).containsOnly(
    521         new PeriodicSync(a, AUTHORITY, fooBar, 6000L),
    522         new PeriodicSync(a, AUTHORITY, fooBaz, 6000L),
    523         new PeriodicSync(a, AUTHORITY, foo42, 6000L));
    524 
    525     ContentResolver.removePeriodicSync(a, AUTHORITY, fooBar);
    526     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).containsOnly(
    527         new PeriodicSync(a, AUTHORITY, fooBaz, 6000L),
    528         new PeriodicSync(a, AUTHORITY, foo42, 6000L));
    529 
    530     ContentResolver.removePeriodicSync(a, AUTHORITY, fooBaz);
    531     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).containsOnly(
    532         new PeriodicSync(a, AUTHORITY, foo42, 6000L));
    533 
    534     ContentResolver.removePeriodicSync(a, AUTHORITY, foo42);
    535     assertThat(ShadowContentResolver.getPeriodicSyncs(a, AUTHORITY)).isEmpty();
    536     assertThat(ShadowContentResolver.getPeriodicSyncs(b, AUTHORITY)).containsOnly(
    537         new PeriodicSync(b, AUTHORITY, fooBar, 6000L),
    538         new PeriodicSync(b, AUTHORITY, fooBaz, 6000L),
    539         new PeriodicSync(b, AUTHORITY, foo42, 6000L));
    540   }
    541 
    542   @Test
    543   public void shouldGetPeriodSyncs() {
    544     assertThat(ContentResolver.getPeriodicSyncs(a, AUTHORITY).size()).isEqualTo(0);
    545     ContentResolver.addPeriodicSync(a, AUTHORITY, new Bundle(), 6000L);
    546 
    547     List<PeriodicSync> syncs = ContentResolver.getPeriodicSyncs(a, AUTHORITY);
    548     assertThat(syncs.size()).isEqualTo(1);
    549 
    550     PeriodicSync first = syncs.get(0);
    551     assertThat(first.account).isEqualTo(a);
    552     assertThat(first.authority).isEqualTo(AUTHORITY);
    553     assertThat(first.period).isEqualTo(6000L);
    554     assertThat(first.extras).isNotNull();
    555   }
    556 
    557   @Test
    558   public void shouldValidateSyncExtras() {
    559     Bundle bundle = new Bundle();
    560     bundle.putString("foo", "strings");
    561     bundle.putLong("long", 10L);
    562     bundle.putDouble("double", 10.0d);
    563     bundle.putFloat("float", 10.0f);
    564     bundle.putInt("int", 10);
    565     bundle.putParcelable("account", a);
    566     ContentResolver.validateSyncExtrasBundle(bundle);
    567   }
    568 
    569   @Test(expected = IllegalArgumentException.class)
    570   public void shouldValidateSyncExtrasAndThrow() {
    571     Bundle bundle = new Bundle();
    572     bundle.putParcelable("intent", new Intent());
    573     ContentResolver.validateSyncExtrasBundle(bundle);
    574   }
    575 
    576   @Test
    577   public void shouldSetMasterSyncAutomatically() {
    578     assertThat(ContentResolver.getMasterSyncAutomatically()).isFalse();
    579     ContentResolver.setMasterSyncAutomatically(true);
    580     assertThat(ContentResolver.getMasterSyncAutomatically()).isTrue();
    581   }
    582 
    583   @Test
    584   public void shouldDelegateCallsToRegisteredProvider() {
    585     ShadowContentResolver.registerProviderInternal(AUTHORITY, new ContentProvider() {
    586       @Override
    587       public boolean onCreate() {
    588         return false;
    589       }
    590 
    591       @Override
    592       public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    593         return new BaseCursor();
    594       }
    595 
    596       @Override
    597       public Uri insert(Uri uri, ContentValues values) {
    598         return null;
    599       }
    600 
    601       @Override
    602       public int delete(Uri uri, String selection, String[] selectionArgs) {
    603         return -1;
    604       }
    605 
    606       @Override
    607       public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    608         return -1;
    609       }
    610 
    611       @Override
    612       public String getType(Uri uri) {
    613         return null;
    614       }
    615     });
    616     final Uri uri = Uri.parse("content://"+AUTHORITY+"/some/path");
    617     final Uri unrelated = Uri.parse("content://unrelated/some/path");
    618 
    619     assertThat(contentResolver.query(uri, null, null, null, null)).isNotNull();
    620     assertThat(contentResolver.insert(uri, new ContentValues())).isNull();
    621 
    622     assertThat(contentResolver.delete(uri, null, null)).isEqualTo(-1);
    623     assertThat(contentResolver.update(uri, new ContentValues(), null, null)).isEqualTo(-1);
    624 
    625     assertThat(contentResolver.query(unrelated, null, null, null, null)).isNull();
    626     assertThat(contentResolver.insert(unrelated, new ContentValues())).isNotNull();
    627     assertThat(contentResolver.delete(unrelated, null, null)).isEqualTo(1);
    628     assertThat(contentResolver.update(unrelated, new ContentValues(), null, null)).isEqualTo(1);
    629   }
    630 
    631   @Test
    632   public void shouldRegisterContentObservers() {
    633     TestContentObserver co = new TestContentObserver(null);
    634     ShadowContentResolver scr = shadowOf(contentResolver);
    635 
    636     assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).isEmpty();
    637 
    638     contentResolver.registerContentObserver(EXTERNAL_CONTENT_URI, true, co);
    639 
    640     assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).containsExactly((ContentObserver) co);
    641 
    642     assertThat(co.changed).isFalse();
    643     contentResolver.notifyChange(EXTERNAL_CONTENT_URI, null);
    644     assertThat(co.changed).isTrue();
    645 
    646     scr.clearContentObservers();
    647     assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).isEmpty();
    648   }
    649 
    650   @Test
    651   public void shouldUnregisterContentObservers() {
    652     TestContentObserver co = new TestContentObserver(null);
    653     ShadowContentResolver scr = shadowOf(contentResolver);
    654     contentResolver.registerContentObserver(EXTERNAL_CONTENT_URI, true, co);
    655     assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI))
    656         .containsExactlyInAnyOrder((ContentObserver) co);
    657 
    658     contentResolver.unregisterContentObserver(co);
    659     assertThat(scr.getContentObservers(EXTERNAL_CONTENT_URI)).isEmpty();
    660 
    661     assertThat(co.changed).isFalse();
    662     contentResolver.notifyChange(EXTERNAL_CONTENT_URI, null);
    663     assertThat(co.changed).isFalse();
    664   }
    665 
    666   @Test
    667   public void shouldNotifyChildContentObservers() throws Exception {
    668     TestContentObserver co1 = new TestContentObserver(null);
    669     TestContentObserver co2 = new TestContentObserver(null);
    670 
    671     Uri childUri = EXTERNAL_CONTENT_URI.buildUpon().appendPath("path").build();
    672 
    673     contentResolver.registerContentObserver(EXTERNAL_CONTENT_URI, true, co1);
    674     contentResolver.registerContentObserver(childUri, false, co2);
    675 
    676     co1.changed = co2.changed = false;
    677     contentResolver.notifyChange(childUri, null);
    678     assertThat(co1.changed).isTrue();
    679     assertThat(co2.changed).isTrue();
    680 
    681     co1.changed = co2.changed = false;
    682     contentResolver.notifyChange(EXTERNAL_CONTENT_URI, null);
    683     assertThat(co1.changed).isTrue();
    684     assertThat(co2.changed).isFalse();
    685 
    686     co1.changed = co2.changed = false;
    687     contentResolver.notifyChange(childUri.buildUpon().appendPath("extra").build(), null);
    688     assertThat(co1.changed).isTrue();
    689     assertThat(co2.changed).isFalse();
    690   }
    691 
    692   @Test
    693   public void getProvider_shouldCreateProviderFromManifest() throws Exception {
    694     Uri uri = Uri.parse("content://org.robolectric.my_content_provider_authority/shadows");
    695     ContentProvider provider = ShadowContentResolver.getProvider(uri);
    696     assertThat(provider).isNotNull();
    697     assertThat(provider.getReadPermission()).isEqualTo("READ_PERMISSION");
    698     assertThat(provider.getWritePermission()).isEqualTo("WRITE_PERMISSION");
    699     assertThat(provider.getPathPermissions()).hasSize(1);
    700 
    701     // unfortunately, there is no direct way of testing if authority is set or not
    702     // however, it's checked in ContentProvider.Transport method calls (validateIncomingUri), so
    703     // it's the closest we can test against
    704     provider.getIContentProvider().getType(uri); // should not throw
    705   }
    706 
    707   @Test
    708   @Config(manifest = NONE)
    709   public void getProvider_shouldNotReturnAnyProviderWhenManifestIsNull() {
    710     Application application = new Application();
    711     shadowOf(application).callAttach(RuntimeEnvironment.systemContext);
    712     assertThat(ShadowContentResolver.getProvider(Uri.parse("content://"))).isNull();
    713   }
    714 
    715 
    716 
    717   @Test
    718   public void openTypedAssetFileDescriptor_shouldOpenDescriptor() throws IOException, RemoteException {
    719     Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY);
    720 
    721     AssetFileDescriptor afd = contentResolver.openTypedAssetFileDescriptor(Uri.parse("content://" + AUTHORITY + "/whatever"), "*/*", null);
    722 
    723     FileDescriptor descriptor = afd.getFileDescriptor();
    724     assertThat(descriptor).isNotNull();
    725   }
    726 
    727   @Test
    728   @Config(minSdk = KITKAT)
    729   public void takeAndReleasePersistableUriPermissions() {
    730     List<UriPermission> permissions = contentResolver.getPersistedUriPermissions();
    731     assertThat(permissions).isEmpty();
    732 
    733     // Take the read permission for the uri.
    734     Uri uri = Uri.parse("content://" + AUTHORITY + "/whatever");
    735     contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    736     assertThat(permissions).hasSize(1);
    737     assertThat(permissions.get(0).getUri()).isSameAs(uri);
    738     assertThat(permissions.get(0).isReadPermission()).isTrue();
    739     assertThat(permissions.get(0).isWritePermission()).isFalse();
    740 
    741     // Take the write permission for the uri.
    742     contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    743     assertThat(permissions).hasSize(1);
    744     assertThat(permissions.get(0).getUri()).isSameAs(uri);
    745     assertThat(permissions.get(0).isReadPermission()).isTrue();
    746     assertThat(permissions.get(0).isWritePermission()).isTrue();
    747 
    748     // Release the read permission for the uri.
    749     contentResolver.releasePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    750     assertThat(permissions).hasSize(1);
    751     assertThat(permissions.get(0).getUri()).isSameAs(uri);
    752     assertThat(permissions.get(0).isReadPermission()).isFalse();
    753     assertThat(permissions.get(0).isWritePermission()).isTrue();
    754 
    755     // Release the write permission for the uri.
    756     contentResolver.releasePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    757     assertThat(permissions).isEmpty();
    758   }
    759 
    760   private static class QueryParamTrackingCursor extends BaseCursor {
    761     public Uri uri;
    762     public String[] projection;
    763     public String selection;
    764     public String[] selectionArgs;
    765     public String sortOrder;
    766 
    767     @Override
    768     public void setQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    769       this.uri = uri;
    770       this.projection = projection;
    771       this.selection = selection;
    772       this.selectionArgs = selectionArgs;
    773       this.sortOrder = sortOrder;
    774     }
    775   }
    776 
    777   private static class TestContentObserver extends ContentObserver {
    778     public TestContentObserver(Handler handler) {
    779       super(handler);
    780     }
    781 
    782     public boolean changed = false;
    783 
    784     @Override
    785     public void onChange(boolean selfChange) {
    786       changed = true;
    787     }
    788 
    789     @Override
    790     public void onChange(boolean selfChange, Uri uri) {
    791       changed = true;
    792     }
    793   }
    794 
    795   public static class TestContentProvider extends ContentProvider {
    796     @Override
    797     public int delete(Uri arg0, String arg1, String[] arg2) {
    798       return 0;
    799     }
    800 
    801     @Override
    802     public String getType(Uri arg0) {
    803       return null;
    804     }
    805 
    806     @Override
    807     public Uri insert(Uri arg0, ContentValues arg1) {
    808       return null;
    809     }
    810 
    811     @Override
    812     public boolean onCreate() {
    813       return false;
    814     }
    815 
    816     @Override
    817     public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
    818       return null;
    819     }
    820 
    821     @Override
    822     public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
    823       return 0;
    824     }
    825   }
    826 
    827   /**
    828    * Provider that opens a temporary file.
    829    */
    830   public static class MyContentProvider extends ContentProvider {
    831     @Override
    832     public boolean onCreate() {
    833       return true;
    834     }
    835 
    836     @Override
    837     public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
    838       return null;
    839     }
    840 
    841     @Override
    842     public String getType(Uri uri) {
    843       return null;
    844     }
    845 
    846     @Override
    847     public Uri insert(Uri uri, ContentValues contentValues) {
    848       return null;
    849     }
    850 
    851     @Override
    852     public int delete(Uri uri, String s, String[] strings) {
    853       return 0;
    854     }
    855 
    856     @Override
    857     public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
    858       return 0;
    859     }
    860 
    861     @Override
    862     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    863       final File file = new File(RuntimeEnvironment.application.getFilesDir(), "test_file");
    864       try {
    865         file.createNewFile();
    866       } catch (IOException e) {
    867         throw new RuntimeException("error creating new file", e);
    868       }
    869       return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    870     }
    871   }
    872 
    873 }
    874