Skip to content

Commit

Permalink
Support overriding Icon loading executor from Icon#loadDrawableAsync.
Browse files Browse the repository at this point in the history
Icon#loadDrawableAsync is loading icon on AsyncTask#THREAD_POOL_EXECUTOR, that test has no way to control the execution. This patch adds a dedicated executor overriding as ShadowIcon#overrideExecutor to let test control where the async icon loading will happen.

PiperOrigin-RevId: 407821420
  • Loading branch information
Googler authored and hoisie committed Nov 17, 2021
1 parent 4b2ecb9 commit 6beade5
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 3 deletions.
Expand Up @@ -2,11 +2,18 @@

import static android.os.Build.VERSION_CODES.M;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.robolectric.Shadows.shadowOf;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
Expand All @@ -16,16 +23,30 @@
@RunWith(AndroidJUnit4.class)
@Config(minSdk = M)
public class ShadowIconTest {

private static final int MSG_ICON_LOADED = 312;

public static final int TYPE_BITMAP = 1;
public static final int TYPE_RESOURCE = 2;
public static final int TYPE_DATA = 3;
public static final int TYPE_URI = 4;

@Nullable private Drawable loadedDrawable;

private final Context appContext = ApplicationProvider.getApplicationContext();
private final Handler mainHandler =
new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_ICON_LOADED) {
loadedDrawable = (Drawable) msg.obj;
}
}
};

@Test
public void testGetRes() {
Icon icon =
Icon.createWithResource(
ApplicationProvider.getApplicationContext(), android.R.drawable.ic_delete);
Icon icon = Icon.createWithResource(appContext, android.R.drawable.ic_delete);
assertThat(shadowOf(icon).getType()).isEqualTo(TYPE_RESOURCE);
assertThat(shadowOf(icon).getResId()).isEqualTo(android.R.drawable.ic_delete);
}
Expand Down Expand Up @@ -55,4 +76,32 @@ public void testGetUri() {
assertThat(shadowOf(icon).getType()).isEqualTo(TYPE_URI);
assertThat(shadowOf(icon).getUri()).isEqualTo(uri);
}

@Test
public void testLoadDrawableAsyncWithMessage() {
ShadowIcon.overrideExecutor(directExecutor());

Icon icon = Icon.createWithResource(appContext, android.R.drawable.ic_delete);

Message andThen = Message.obtain(mainHandler, MSG_ICON_LOADED);

icon.loadDrawableAsync(appContext, andThen);
ShadowLooper.idleMainLooper();

assertThat(shadowOf(loadedDrawable).getCreatedFromResId())
.isEqualTo(android.R.drawable.ic_delete);
}

@Test
public void testLoadDrawableAsyncWithListener() {
ShadowIcon.overrideExecutor(directExecutor());

Icon icon = Icon.createWithResource(appContext, android.R.drawable.ic_delete);

icon.loadDrawableAsync(appContext, drawable -> this.loadedDrawable = drawable, mainHandler);
ShadowLooper.idleMainLooper();

assertThat(shadowOf(loadedDrawable).getCreatedFromResId())
.isEqualTo(android.R.drawable.ic_delete);
}
}
Expand Up @@ -3,9 +3,16 @@
import static android.os.Build.VERSION_CODES.M;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.Icon.OnDrawableLoadedListener;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import androidx.annotation.Nullable;
import java.util.concurrent.Executor;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
Expand All @@ -17,6 +24,13 @@
@Implements(value = Icon.class, minSdk = M)
public class ShadowIcon {

@Nullable private static Executor executorOverride;

/** Set the executor where async drawable loading will run. */
public static void overrideExecutor(Executor executor) {
executorOverride = executor;
}

@RealObject private Icon realIcon;

@HiddenApi
Expand Down Expand Up @@ -61,6 +75,33 @@ public byte[] getDataBytes() {
return reflector(IconReflector.class, realIcon).getDataBytes();
}

@Implementation
protected void loadDrawableAsync(Context context, Message andThen) {
if (executorOverride != null) {
executorOverride.execute(
() -> {
andThen.obj = realIcon.loadDrawable(context);
andThen.sendToTarget();
});
} else {
reflector(IconReflector.class, realIcon).loadDrawableAsync(context, andThen);
}
}

@Implementation
protected void loadDrawableAsync(
Context context, final OnDrawableLoadedListener listener, Handler handler) {
if (executorOverride != null) {
executorOverride.execute(
() -> {
Drawable result = realIcon.loadDrawable(context);
handler.post(() -> listener.onDrawableLoaded(result));
});
} else {
reflector(IconReflector.class, realIcon).loadDrawableAsync(context, listener, handler);
}
}

@ForType(Icon.class)
interface IconReflector {

Expand All @@ -84,5 +125,12 @@ interface IconReflector {

@Direct
byte[] getDataBytes();

@Direct
void loadDrawableAsync(Context context, Message andThen);

@Direct
void loadDrawableAsync(
Context context, final OnDrawableLoadedListener listener, Handler handler);
}
}

0 comments on commit 6beade5

Please sign in to comment.