diff --git a/resources/src/main/java/org/robolectric/res/android/Asset.java b/resources/src/main/java/org/robolectric/res/android/Asset.java index 06fd5b83b77..bc88e3f5021 100644 --- a/resources/src/main/java/org/robolectric/res/android/Asset.java +++ b/resources/src/main/java/org/robolectric/res/android/Asset.java @@ -656,7 +656,7 @@ public boolean isNinePatch() { // : mStart(0), mLength(0), mOffset(0), mFp(null), mFileName(null), mMap(null), mBuf(null) { // Register the Asset with the global list here after it is fully constructed and its - // vtable pointer points to this concrete type. b/31113965 + // vtable pointer points to this concrete type. registerAsset(this); } @@ -668,7 +668,7 @@ protected void finalize() { close(); // Unregister the Asset from the global list here before it is destructed and while its vtable - // pointer still points to this concrete type. b/31113965 + // pointer still points to this concrete type. unregisterAsset(this); } @@ -1136,7 +1136,7 @@ public boolean isNinePatch() { mFd = -1; // Register the Asset with the global list here after it is fully constructed and its - // vtable pointer points to this concrete type. b/31113965 + // vtable pointer points to this concrete type. registerAsset(this); } @@ -1167,7 +1167,7 @@ protected void finalize() { close(); // Unregister the Asset from the global list here before it is destructed and while its vtable - // pointer still points to this concrete type. b/31113965 + // pointer still points to this concrete type. unregisterAsset(this); } diff --git a/resources/src/main/java/org/robolectric/res/android/Chunk.java b/resources/src/main/java/org/robolectric/res/android/Chunk.java index 823312ceaad..9318af9e74b 100644 --- a/resources/src/main/java/org/robolectric/res/android/Chunk.java +++ b/resources/src/main/java/org/robolectric/res/android/Chunk.java @@ -166,7 +166,6 @@ Chunk Next() { return new Chunk(this_chunk); } - // TODO(b/111401637) remove this and have full resource file verification // Returns false if there was an error. For legacy purposes. boolean VerifyNextChunkNonFatal() { if (len_ < ResChunk_header.SIZEOF) { diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java index 30229121257..21e5fb15394 100644 --- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java +++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java @@ -51,8 +51,8 @@ enum FileType { kFileTypeSocket, } - - // transliterated from https://cs.corp.google.com/android/frameworks/base/libs/androidfw/include/androidfw/AssetManager.h + // transliterated from + // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/androidfw/include/androidfw/AssetManager.h private static class asset_path { // asset_path() : path(""), type(kFileTypeRegular), idmap(""), // isSystemOverlay(false), isSystemAsset(false) {} diff --git a/resources/src/main/java/org/robolectric/res/android/FileMap.java b/resources/src/main/java/org/robolectric/res/android/FileMap.java index 09fb4e4132a..fa4fecfd523 100644 --- a/resources/src/main/java/org/robolectric/res/android/FileMap.java +++ b/resources/src/main/java/org/robolectric/res/android/FileMap.java @@ -239,7 +239,7 @@ static ImmutableMap guessDataOffsets(File zipFile, int length) { while (true) { // Instead of trusting numRecords, read until we find the // end-of-central-directory signature. numRecords may wrap - // around with >64K entries (b/5455504). + // around with >64K entries. int sig = readInt(buffer, offset); if (sig == ENDSIG || sig == ENDSIG64) { break; diff --git a/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java b/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java index 6a12b793048..02d570a3b9a 100644 --- a/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java @@ -57,7 +57,8 @@ public boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityExceptio return true; } - // TODO(b/80130000): implementation copied from espresso's UIControllerImpl. Refactor code into common location + // TODO: implementation copied from espresso's UIControllerImpl. Refactor code into common + // location @Override public boolean injectString(String str) throws InjectEventSecurityException { checkNotNull(str); @@ -72,8 +73,7 @@ public boolean injectString(String str) throws InjectEventSecurityException { boolean eventInjected = false; KeyCharacterMap keyCharacterMap = getKeyCharacterMap(); - // TODO(b/80130875): Investigate why not use (as suggested in javadoc of - // keyCharacterMap.getEvents): + // TODO: Investigate why not use (as suggested in javadoc of keyCharacterMap.getEvents): // http://developer.android.com/reference/android/view/KeyEvent.html#KeyEvent(long, // java.lang.String, int, int) KeyEvent[] events = keyCharacterMap.getEvents(str.toCharArray()); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java index 20931cc2be6..e8235b5f7ed 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBugreportManagerTest.java @@ -4,7 +4,6 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; @@ -69,18 +68,16 @@ public void startBugreport_noPermission() throws Exception { BugreportCallback callback = mock(BugreportCallback.class); shadowBugreportManager.setHasPermission(false); - // TODO(b/179958637) switch to assertThrows once ThrowingRunnable no longer causes a test - // instantiation failure. - try { - shadowBugreportManager.startBugreport( - createWriteFile("bugreport"), - createWriteFile("screenshot"), - new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL), - directExecutor(), - callback); - fail("Expected SecurityException"); - } catch (SecurityException expected) { - } + assertThrows( + SecurityException.class, + () -> { + shadowBugreportManager.startBugreport( + createWriteFile("bugreport"), + createWriteFile("screenshot"), + new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL), + directExecutor(), + callback); + }); shadowMainLooper().idle(); assertThat(shadowBugreportManager.isBugreportInProgress()).isFalse(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java index db6aaa572ae..43f0f0363dc 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java @@ -2,6 +2,8 @@ import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.S; import static com.google.common.truth.Truth.assertThat; import android.os.Build; @@ -56,6 +58,13 @@ public void setVersionIncremental() { assertThat(VERSION.INCREMENTAL).isEqualTo("robo_incremental"); } + @Test + @Config(minSdk = S) + public void setVersionMediaPerformanceClass() { + ShadowBuild.setVersionMediaPerformanceClass(R); + assertThat(VERSION.MEDIA_PERFORMANCE_CLASS).isEqualTo(R); + } + @Test @Config(minSdk = M) public void setVersionSecurityPatch() { 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/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java index 399827597da..89892fa863f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java @@ -204,7 +204,7 @@ public void postAndRemoveSyncBarrierToken() { } @Test - // TODO(b/74402484): enable once workaround is removed + // TODO(https://github.com/robolectric/robolectric/issues/6852): enable once workaround is removed @Ignore public void removeInvalidSyncBarrierToken() { try { diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/SandboxClassLoader.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/SandboxClassLoader.java index fa0f96c0b1e..c71a735c6db 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/SandboxClassLoader.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/SandboxClassLoader.java @@ -82,7 +82,7 @@ private static URL[] getClassPathUrls(ClassLoader classloader) { return parseJavaClassPath(); } - // TODO(b/65488446): Use a public API once one is available. + // TODO(https://github.com/google/guava/issues/2956): Use a public API once one is available. private static URL[] parseJavaClassPath() { ImmutableList.Builder urls = ImmutableList.builder(); for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) { diff --git a/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java b/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java index 1105f5d39f3..2bbaf4e6780 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java +++ b/shadows/framework/src/main/java/org/robolectric/android/controller/ActivityController.java @@ -7,7 +7,6 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.app.Activity; -import android.app.ActivityThread; import android.app.Application; import android.app.Instrumentation; import android.content.ComponentName; @@ -502,8 +501,9 @@ public ActivityController recreate() { } } - private static Instrumentation getInstrumentation() { - return ((ActivityThread) RuntimeEnvironment.getActivityThread()).getInstrumentation(); + // Get the Instrumentation object scoped to the Activity. + private Instrumentation getInstrumentation() { + return _component_.getInstrumentation(); } /** Accessor interface for android.app.Activity.NonConfigurationInstances's internals. */ diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java index a859b18ec75..ea00874b1e5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java @@ -3,6 +3,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.S; import static org.robolectric.util.reflector.Reflector.reflector; import android.annotation.TargetApi; @@ -110,6 +111,18 @@ public static void setVersionIncremental(String versionIncremental) { ReflectionHelpers.setStaticField(Build.VERSION.class, "INCREMENTAL", versionIncremental); } + /** + * Sets the value of the {@link Build.VERSION#MEDIA_PERFORMANCE_CLASS} field. Available in Android + * S+. + * + *

It will be reset for the next test. + */ + @TargetApi(S) + public static void setVersionMediaPerformanceClass(int performanceClass) { + ReflectionHelpers.setStaticField( + Build.VERSION.class, "MEDIA_PERFORMANCE_CLASS", performanceClass); + } + /** * Sets the value of the {@link Build.VERSION#RELEASE} field. * 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); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java index f60f1b4c9e2..9934fb4ef9b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java @@ -166,7 +166,8 @@ private static void dispatchMessage(Message msg) { @Implementation @HiddenApi protected void removeSyncBarrier(int token) { - // TODO(b/74402484): workaround scheduler corruption of message queue + // TODO(https://github.com/robolectric/robolectric/issues/6852): workaround scheduler corruption + // of message queue try { reflector(MessageQueueReflector.class, realQueue).removeSyncBarrier(token); } catch (IllegalStateException e) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java index c0e3e930403..5b11bb9d8a3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java @@ -89,7 +89,7 @@ public Parcelable.Creator readParcelableCreator(ClassLoader loader) { // classloader" behavior. ClassLoader parcelableClassLoader = (loader == null ? getClass().getClassLoader() : loader); // Avoid initializing the Parcelable class until we know it implements - // Parcelable and has the necessary CREATOR field. http://b/1171613. + // Parcelable and has the necessary CREATOR field. Class parcelableClass = Class.forName(name, false /* initialize */, parcelableClassLoader); if (!Parcelable.class.isAssignableFrom(parcelableClass)) { throw new BadParcelableException( diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteOpenHelper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteOpenHelper.java index 8621359d112..a3208e034ae 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteOpenHelper.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteOpenHelper.java @@ -15,6 +15,6 @@ public class ShadowSQLiteOpenHelper { @Implementation(minSdk = O_MR1) protected void setIdleConnectionTimeout(long idleConnectionTimeoutMs) { // Calling the real one currently results in a Robolectric deadlock. Just ignore it. - // See b/78464547 . + // See https://github.com/robolectric/robolectric/issues/6853. } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java index e628ecb25fe..d1b965256b0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java @@ -918,7 +918,7 @@ public void setSignalStrength(SignalStrength signalStrength) { /** * Cribbed from {@link android.telephony.PhoneNumberUtils#isEmergencyNumberInternal}. * - *

TODO(b/122324733) need better implementation + *

TODO: need better implementation */ @Implementation(minSdk = Build.VERSION_CODES.Q) protected boolean isEmergencyNumber(String number) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimePickerDialog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimePickerDialog.java index 4a4c874d255..0c06e8927c6 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimePickerDialog.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTimePickerDialog.java @@ -3,13 +3,9 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.app.TimePickerDialog; -import android.content.Context; import android.widget.TimePicker; -import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; @@ -18,24 +14,6 @@ public class ShadowTimePickerDialog extends ShadowAlertDialog { @RealObject protected TimePickerDialog realTimePickerDialog; - @Implementation - protected void __constructor__( - Context context, - int theme, - TimePickerDialog.OnTimeSetListener callBack, - int hourOfDay, - int minute, - boolean is24HourView) { - - Shadow.invokeConstructor(TimePickerDialog.class, realTimePickerDialog, - ClassParameter.from(Context.class, context), - ClassParameter.from(int.class, theme), - ClassParameter.from(TimePickerDialog.OnTimeSetListener.class, callBack), - ClassParameter.from(int.class, hourOfDay), - ClassParameter.from(int.class, minute), - ClassParameter.from(boolean.class, is24HourView)); - } - public int getHourOfDay() { return reflector(TimePickerDialogProvider.class, realTimePickerDialog) .getTimePicker() diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java index 450782f8c35..e327bcec8a5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java @@ -372,4 +372,7 @@ void setVoiceInteractor( @Accessor("mConfigChangeFlags") void setConfigChangeFlags(int value); + + @Accessor("mInstrumentation") + Instrumentation getInstrumentation(); }