From 6beade5567e86265394364e7635ba4717b8ed21a Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 5 Nov 2021 07:51:23 -0700 Subject: [PATCH] Support overriding Icon loading executor from Icon#loadDrawableAsync. 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 --- .../robolectric/shadows/ShadowIconTest.java | 55 ++++++++++++++++++- .../org/robolectric/shadows/ShadowIcon.java | 48 ++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowIconTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowIconTest.java index d317ba92210..b1f6676eb1a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowIconTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowIconTest.java @@ -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; @@ -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); } @@ -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); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java index df8ced15c9f..72aa2fe321f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java @@ -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; @@ -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 @@ -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 { @@ -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); } }