Skip to content

Commit

Permalink
Use real Android code for ContentProviderClient.release
Browse files Browse the repository at this point in the history
This fixes two issues:
1) Because 'release' was shadowed, the underlying CloseGuard for the
ContentProviderClient was never closed, leading to CloseGuard errors that could
not be suppressed.

2) The behavior of 'release' was not correct for SDK > 23, where calling it
multiple times does not result in an IllegalStateException.

Also, update ShadowContentProviderClientTest to use a real ContentProvider, and
remove the interaction test. Trying to release a ContentProviderClient for a
mock ContentProvider results in an exception.

PiperOrigin-RevId: 413795247
  • Loading branch information
hoisie committed Dec 6, 2021
1 parent bcb31db commit 3d3b1d0
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 92 deletions.
@@ -1,60 +1,65 @@
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.M;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;

import android.content.ContentProvider;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.ArrayList;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ContentProviderController;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.testing.TestContentProvider1;

@RunWith(AndroidJUnit4.class)
public class ShadowContentProviderClientTest {

private static final String AUTHORITY = "org.robolectric";
private final Uri URI = Uri.parse("content://" + AUTHORITY);
private final ContentValues VALUES = new ContentValues();
private static final String[] PROJECTION = null;
private static final String SELECTION = "1=?";
private static final String[] SELECTION_ARGS = {"1"};
private static final String SORT_ORDER = "DESC";
private static final String MIME_TYPE = "application/octet-stream";

@Mock ContentProvider provider;
private final ContentProviderController<TestContentProvider1> controller =
Robolectric.buildContentProvider(TestContentProvider1.class);

ContentProvider provider = controller.create().get();

ContentResolver contentResolver =
ApplicationProvider.getApplicationContext().getContentResolver();

ContentProviderClient client;

@Before
public void setUp() {
initMocks(this);
ShadowContentResolver.registerProviderInternal(AUTHORITY, provider);
}

@After
public void tearDown() {
if (client != null) {
if (RuntimeEnvironment.getApiLevel() > M) {
client.close();
} else {
client.release();
}
}
}

@Test
public void acquireContentProviderClient_isStable() {
ContentProviderClient client = contentResolver.acquireContentProviderClient(AUTHORITY);
client = contentResolver.acquireContentProviderClient(AUTHORITY);
assertThat(shadowOf(client).isStable()).isTrue();
}

@Test
public void acquireUnstableContentProviderClient_isUnstable() {
ContentProviderClient client = contentResolver.acquireUnstableContentProviderClient(AUTHORITY);
client = contentResolver.acquireUnstableContentProviderClient(AUTHORITY);
assertThat(shadowOf(client).isStable()).isFalse();
}

Expand All @@ -67,63 +72,11 @@ public void release_shouldRelease() {
assertThat(shadow.isReleased()).isTrue();
}

@Test(expected = IllegalStateException.class)
@Test
@Config(maxSdk = M)
public void release_shouldFailWhenCalledTwice() {
ContentProviderClient client = contentResolver.acquireContentProviderClient(AUTHORITY);
client.release();
client.release();
fail("client.release() was called twice and did not throw");
}

@Test
@Config(minSdk = JELLY_BEAN_MR2)
public void shouldDelegateToContentProvider() throws Exception {
ContentProviderClient client = contentResolver.acquireContentProviderClient(AUTHORITY);

client.query(URI, PROJECTION, SELECTION, SELECTION_ARGS, SORT_ORDER);
verify(provider).query(URI, PROJECTION, SELECTION, SELECTION_ARGS, SORT_ORDER);

CancellationSignal signal = new CancellationSignal();
client.query(URI, PROJECTION, SELECTION, SELECTION_ARGS, SORT_ORDER, signal);
verify(provider).query(URI, PROJECTION, SELECTION, SELECTION_ARGS, SORT_ORDER, signal);

client.insert(URI, VALUES);
verify(provider).insert(URI, VALUES);

client.update(URI, VALUES, SELECTION, SELECTION_ARGS);
verify(provider).update(URI, VALUES, SELECTION, SELECTION_ARGS);

client.delete(URI, SELECTION, SELECTION_ARGS);
verify(provider).delete(URI, SELECTION, SELECTION_ARGS);

client.getType(URI);
verify(provider).getType(URI);

client.openFile(URI, "rw");
verify(provider).openFile(URI, "rw");

client.openAssetFile(URI, "r");
verify(provider).openAssetFile(URI, "r");

final Bundle opts = new Bundle();
client.openTypedAssetFileDescriptor(URI, MIME_TYPE, opts);
verify(provider).openTypedAssetFile(URI, MIME_TYPE, opts);

client.getStreamTypes(URI, MIME_TYPE);
verify(provider).getStreamTypes(URI, MIME_TYPE);

final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
client.applyBatch(ops);
verify(provider).applyBatch(ops);

final ContentValues[] values = {VALUES};
client.bulkInsert(URI, values);
verify(provider).bulkInsert(URI, values);

final String method = "method";
final String arg = "arg";
final Bundle extras = new Bundle();
client.call(method, arg, extras);
verify(provider).call(method, arg, extras);
assertThrows(IllegalStateException.class, () -> client.release());
}
}
@@ -1,6 +1,7 @@
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.content.ContentProvider;
import android.content.ContentProviderClient;
Expand All @@ -11,22 +12,26 @@
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

@Implements(ContentProviderClient.class)
public class ShadowContentProviderClient {
@RealObject private ContentProviderClient realContentProviderClient;

private boolean released;
private ContentProvider provider;

@Implementation(minSdk = JELLY_BEAN_MR1)
Expand Down Expand Up @@ -108,31 +113,41 @@ protected ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation>
return provider.applyBatch(operations);
}

@Implementation
protected boolean release() {
synchronized (this) {
if (released) {
throw new IllegalStateException("Already released");
}
released = true;
}
return true;
}

@Implementation
protected ContentProvider getLocalContentProvider() {
return ContentProvider.coerceToLocalContentProvider(provider.getIContentProvider());
}

public boolean isStable() {
return ReflectionHelpers.getField(realContentProviderClient, "mStable");
return reflector(ContentProviderClientReflector.class, realContentProviderClient).getStable();
}

public boolean isReleased() {
return released;
ContentProviderClientReflector contentProviderClientReflector =
reflector(ContentProviderClientReflector.class, realContentProviderClient);
if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.M) {
return contentProviderClientReflector.getReleased();
} else {
return contentProviderClientReflector.getClosed().get();
}
}

void setContentProvider(ContentProvider provider) {
this.provider = provider;
}

@ForType(ContentProviderClient.class)
interface ContentProviderClientReflector {
@Direct
boolean release();

@Accessor("mStable")
boolean getStable();

@Accessor("mReleased")
boolean getReleased();

@Accessor("mClosed")
AtomicBoolean getClosed();
}
}

0 comments on commit 3d3b1d0

Please sign in to comment.