Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use real Android code for ContentProviderClient.release #6908

Merged
merged 1 commit into from Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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();
}
}