package android.content.res;

import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_IN;
import static android.util.TypedValue.COMPLEX_UNIT_MM;
import static android.util.TypedValue.COMPLEX_UNIT_PT;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static android.util.TypedValue.COMPLEX_UNIT_SP;
import static android.util.TypedValue.TYPE_FIRST_COLOR_INT;
import static android.util.TypedValue.TYPE_INT_BOOLEAN;
import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
import static android.util.TypedValue.TYPE_INT_DEC;
import static android.util.TypedValue.TYPE_LAST_INT;
import static android.util.TypedValue.TYPE_REFERENCE;
import static android.util.TypedValue.TYPE_STRING;
import static android.util.TypedValue.applyDimension;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.robolectric.testapp.R.color.test_ARGB8;
import static org.robolectric.testapp.R.color.test_RGB8;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import com.google.common.collect.Range;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.testapp.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

/** Compatibility test for {@link Resources} */
@RunWith(AndroidJUnit4.class)
public class ResourcesTest {
  private Resources resources;
  private Context context;

  @Before
  public void setup() {
    context = ApplicationProvider.getApplicationContext();
    resources = context.getResources();
  }

  @Test
  public void getString() {
    assertThat(resources.getString(R.string.hello)).isEqualTo("Hello");
    assertThat(resources.getString(R.string.say_it_with_item)).isEqualTo("flowers");
  }

  @Test
  public void getString_withReference() {
    assertThat(resources.getString(R.string.greeting)).isEqualTo("Howdy");
  }

  @Test
  public void getString_withInterpolation() {
    assertThat(resources.getString(R.string.interpolate, "value")).isEqualTo("Here is a value!");
  }

  @Test
  public void getString_withHtml() {
    assertThat(resources.getString(R.string.some_html, "value")).isEqualTo("Hello, world");
  }

  @Test
  public void getString_withSurroundingQuotes() {
    assertThat(resources.getString(R.string.surrounding_quotes, "value")).isEqualTo("This'll work");
  }

  @Test
  public void getStringWithEscapedApostrophes() {
    assertThat(resources.getString(R.string.escaped_apostrophe)).isEqualTo("This'll also work");
  }

  @Test
  public void getStringWithEscapedQuotes() {
    assertThat(resources.getString(R.string.escaped_quotes)).isEqualTo("Click \"OK\"");
  }

  @Test
  public void getString_StringWithInlinedQuotesAreStripped() {
    assertThat(resources.getString(R.string.bad_example)).isEqualTo("This is a bad string.");
  }

  @Test
  public void getStringShouldStripNewLines() {
    assertThat(resources.getString(R.string.leading_and_trailing_new_lines)).isEqualTo("Some text");
  }

  @Test
  public void preserveEscapedNewlineAndTab() {
    assertThat(resources.getString(R.string.new_lines_and_tabs, 4)).isEqualTo("4\tmph\nfaster");
  }

  @Test
  public void getStringShouldConvertCodePoints() {
    assertThat(resources.getString(R.string.non_breaking_space))
        .isEqualTo("Closing" + " soon:\u00A05pm");
    assertThat(resources.getString(R.string.space)).isEqualTo("Closing soon: 5pm");
  }

  @Test
  public void getMultilineLayoutResource_shouldResolveLayoutReferencesWithLineBreaks() {
    // multiline_layout is a layout reference to activity_main layout.
    TypedValue multilineLayoutValue = new TypedValue();
    resources.getValue(R.layout.multiline_layout, multilineLayoutValue, true /* resolveRefs */);
    TypedValue mainActivityLayoutValue = new TypedValue();
    resources.getValue(R.layout.activity_main, mainActivityLayoutValue, false /* resolveRefs */);
    assertThat(multilineLayoutValue.string).isEqualTo(mainActivityLayoutValue.string);
  }

  @Test
  public void getText_withHtml() {
    assertThat(resources.getText(R.string.some_html, "value").toString()).isEqualTo("Hello, world");
    // TODO: Raw resources have lost the tags early, but the following call should return a
    // SpannedString
    // assertThat(resources.getText(R.string.some_html)).isInstanceOf(SpannedString.class);
  }

  @Test
  public void getText_plainString() {
    assertThat(resources.getText(R.string.hello, "value").toString()).isEqualTo("Hello");
    assertThat(resources.getText(R.string.hello)).isInstanceOf(String.class);
  }

  @Test
  public void getText_withLayoutId() {
    // This isn't _really_ supported by the platform (gives a lint warning that getText() expects a
    // String resource type
    // but the actual platform behaviour is to return a string that equals
    // "res/layout/layout_file.xml" so the current
    // Robolectric behaviour deviates from the platform as we append the full file path from the
    // current working directory.
    String textString = resources.getText(R.layout.different_screen_sizes, "value").toString();
    assertThat(textString).containsMatch("/different_screen_sizes.xml$");
    // If we run tests on devices with different config, the resource system will select different
    // layout directories.
    assertThat(textString).containsMatch("^res/layout");
  }

  @Test
  public void getStringArray() {
    assertThat(resources.getStringArray(R.array.items)).isEqualTo(new String[] {"foo", "bar"});
    assertThat(resources.getStringArray(R.array.greetings))
        .isEqualTo(new String[] {"hola", "Hello"});
  }

  @Test
  public void withIdReferenceEntry_obtainTypedArray() {
    TypedArray typedArray = resources.obtainTypedArray(R.array.typed_array_with_resource_id);
    assertThat(typedArray.length()).isEqualTo(2);

    assertThat(typedArray.getResourceId(0, 0)).isEqualTo(R.id.id_declared_in_item_tag);
    assertThat(typedArray.getResourceId(1, 0)).isEqualTo(R.id.id_declared_in_layout);
  }

  @Test
  public void obtainTypedArray() {
    final TypedArray valuesTypedArray = resources.obtainTypedArray(R.array.typed_array_values);
    assertThat(valuesTypedArray.getString(0)).isEqualTo("abcdefg");
    assertThat(valuesTypedArray.getInt(1, 0)).isEqualTo(3875);
    assertThat(valuesTypedArray.getInteger(1, 0)).isEqualTo(3875);
    assertThat(valuesTypedArray.getFloat(2, 0.0f)).isEqualTo(2.0f);
    assertThat(valuesTypedArray.getColor(3, Color.BLACK)).isEqualTo(Color.MAGENTA);
    assertThat(valuesTypedArray.getColor(4, Color.BLACK)).isEqualTo(Color.parseColor("#00ffff"));
    assertThat(valuesTypedArray.getDimension(5, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_PX, 8, resources.getDisplayMetrics()));
    assertThat(valuesTypedArray.getDimension(6, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 12, resources.getDisplayMetrics()));
    assertThat(valuesTypedArray.getDimension(7, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 6, resources.getDisplayMetrics()));
    assertThat(valuesTypedArray.getDimension(8, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_MM, 3, resources.getDisplayMetrics()));
    assertThat(valuesTypedArray.getDimension(9, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_IN, 4, resources.getDisplayMetrics()));
    assertThat(valuesTypedArray.getDimension(10, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_SP, 36, resources.getDisplayMetrics()));
    assertThat(valuesTypedArray.getDimension(11, 0.0f))
        .isEqualTo(applyDimension(COMPLEX_UNIT_PT, 18, resources.getDisplayMetrics()));

    final TypedArray refsTypedArray = resources.obtainTypedArray(R.array.typed_array_references);
    assertThat(refsTypedArray.getString(0)).isEqualTo("apple");
    assertThat(refsTypedArray.getString(1)).isEqualTo("banana");
    assertThat(refsTypedArray.getInt(2, 0)).isEqualTo(5);
    assertThat(refsTypedArray.getBoolean(3, false)).isTrue();

    assertThat(refsTypedArray.getResourceId(8, 0)).isEqualTo(R.array.string_array_values);
    assertThat(refsTypedArray.getTextArray(8))
        .asList()
        .containsAtLeast(
            "abcdefg",
            "3875",
            "2.0",
            "#ffff00ff",
            "#00ffff",
            "8px",
            "12dp",
            "6dip",
            "3mm",
            "4in",
            "36sp",
            "18pt");

    assertThat(refsTypedArray.getResourceId(9, 0)).isEqualTo(R.style.Theme_Robolectric);
  }

  @Test
  public void getInt() {
    assertThat(resources.getInteger(R.integer.meaning_of_life)).isEqualTo(42);
    assertThat(resources.getInteger(R.integer.test_integer1)).isEqualTo(2000);
    assertThat(resources.getInteger(R.integer.test_integer2)).isEqualTo(9);
    assertThat(resources.getInteger(R.integer.test_large_hex)).isEqualTo(-65536);
    assertThat(resources.getInteger(R.integer.test_value_with_zero)).isEqualTo(7210);
    assertThat(resources.getInteger(R.integer.meaning_of_life_as_item)).isEqualTo(42);
  }

  @Test
  public void getInt_withReference() {
    assertThat(resources.getInteger(R.integer.reference_to_meaning_of_life)).isEqualTo(42);
  }

  @Test
  public void getIntArray() {
    assertThat(resources.getIntArray(R.array.empty_int_array)).isEqualTo(new int[] {});
    assertThat(resources.getIntArray(R.array.zero_to_four_int_array))
        .isEqualTo(new int[] {0, 1, 2, 3, 4});
    assertThat(resources.getIntArray(R.array.with_references_int_array))
        .isEqualTo(new int[] {0, 2000, 1});
    assertThat(resources.getIntArray(R.array.referenced_colors_int_array))
        .isEqualTo(new int[] {0x1, 0xFFFFFFFF, 0xFF000000, 0xFFF5F5F5, 0x802C76AD});
  }

  @Test
  public void getBoolean() {
    assertThat(resources.getBoolean(R.bool.false_bool_value)).isEqualTo(false);
    assertThat(resources.getBoolean(R.bool.true_as_item)).isEqualTo(true);
  }

  @Test
  public void getBoolean_withReference() {
    assertThat(resources.getBoolean(R.bool.reference_to_true)).isEqualTo(true);
  }

  @Test
  public void getDimension() {
    assertThat(resources.getDimension(R.dimen.test_dip_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 20, resources.getDisplayMetrics()));
    assertThat(resources.getDimension(R.dimen.test_dp_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_DIP, 8, resources.getDisplayMetrics()));
    assertThat(resources.getDimension(R.dimen.test_in_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_IN, 99, resources.getDisplayMetrics()));
    assertThat(resources.getDimension(R.dimen.test_mm_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_MM, 42, resources.getDisplayMetrics()));
    assertThat(resources.getDimension(R.dimen.test_px_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_PX, 15, resources.getDisplayMetrics()));
    assertThat(resources.getDimension(R.dimen.test_pt_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_PT, 12, resources.getDisplayMetrics()));
    assertThat(resources.getDimension(R.dimen.test_sp_dimen))
        .isEqualTo(applyDimension(COMPLEX_UNIT_SP, 5, resources.getDisplayMetrics()));
  }

  @Test
  public void getDimensionPixelSize() {
    assertThat(resources.getDimensionPixelSize(R.dimen.test_dip_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_DIP, 20)));
    assertThat(resources.getDimensionPixelSize(R.dimen.test_dp_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_DIP, 8)));
    assertThat(resources.getDimensionPixelSize(R.dimen.test_in_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_IN, 99)));
    assertThat(resources.getDimensionPixelSize(R.dimen.test_mm_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_MM, 42)));
    assertThat(resources.getDimensionPixelSize(R.dimen.test_px_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_PX, 15)));
    assertThat(resources.getDimensionPixelSize(R.dimen.test_pt_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_PT, 12)));
    assertThat(resources.getDimensionPixelSize(R.dimen.test_sp_dimen))
        .isIn(onePixelOf(convertDimension(COMPLEX_UNIT_SP, 5)));
  }

  private static Range<Integer> onePixelOf(int i) {
    return Range.closed(i - 1, i + 1);
  }

  @Test
  public void getDimensionPixelOffset() {
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_dip_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_DIP, 20));
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_dp_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_DIP, 8));
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_in_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_IN, 99));
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_mm_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_MM, 42));
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_px_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_PX, 15));
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_pt_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_PT, 12));
    assertThat(resources.getDimensionPixelOffset(R.dimen.test_sp_dimen))
        .isEqualTo(convertDimension(COMPLEX_UNIT_SP, 5));
  }

  private int convertDimension(int unit, float value) {
    return (int) applyDimension(unit, value, resources.getDisplayMetrics());
  }

  @Test
  public void getDimension_withReference() {
    assertThat(resources.getBoolean(R.bool.reference_to_true)).isEqualTo(true);
  }

  @Test
  public void getStringArray_shouldThrowExceptionIfNotFound() {
    assertThrows(Resources.NotFoundException.class, () -> resources.getStringArray(-1));
  }

  @Test
  public void getIntegerArray_shouldThrowExceptionIfNotFound() {
    assertThrows(Resources.NotFoundException.class, () -> resources.getIntArray(-1));
  }

  @Test
  public void getQuantityString() {
    assertThat(resources.getQuantityString(R.plurals.beer, 1)).isEqualTo("a beer");
    assertThat(resources.getQuantityString(R.plurals.beer, 2)).isEqualTo("some beers");
    assertThat(resources.getQuantityString(R.plurals.beer, 3)).isEqualTo("some beers");
  }

  @Test
  public void getQuantityText() {
    // Feature not supported in legacy (raw) resource mode.
    assumeFalse(isRobolectricLegacyMode());

    assertThat(resources.getQuantityText(R.plurals.beer, 1)).isEqualTo("a beer");
    assertThat(resources.getQuantityText(R.plurals.beer, 2)).isEqualTo("some beers");
    assertThat(resources.getQuantityText(R.plurals.beer, 3)).isEqualTo("some beers");
  }

  @Test
  public void getFraction() {
    final int myself = 300;
    final int myParent = 600;
    assertThat(resources.getFraction(R.fraction.half, myself, myParent)).isEqualTo(150f);
    assertThat(resources.getFraction(R.fraction.half_of_parent, myself, myParent)).isEqualTo(300f);

    assertThat(resources.getFraction(R.fraction.quarter_as_item, myself, myParent)).isEqualTo(75f);
    assertThat(resources.getFraction(R.fraction.quarter_of_parent_as_item, myself, myParent))
        .isEqualTo(150f);

    assertThat(resources.getFraction(R.fraction.fifth_as_reference, myself, myParent))
        .isWithin(0.01f)
        .of(60f);
    assertThat(resources.getFraction(R.fraction.fifth_of_parent_as_reference, myself, myParent))
        .isWithin(0.01f)
        .of(120f);
  }

  @Test
  public void testConfiguration() {
    Configuration configuration = resources.getConfiguration();
    assertThat(configuration).isNotNull();
    assertThat(configuration.locale).isNotNull();
  }

  @Test
  public void testConfigurationReturnsTheSameInstance() {
    assertThat(resources.getConfiguration()).isSameInstanceAs(resources.getConfiguration());
  }

  @Test
  public void testNewTheme() {
    assertThat(resources.newTheme()).isNotNull();
  }

  @Test
  public void testGetDrawableNullRClass() {
    assertThrows(
        Resources.NotFoundException.class,
        () -> assertThat(resources.getDrawable(-12345)).isInstanceOf(BitmapDrawable.class));
  }

  @Test
  public void testGetAnimationDrawable() {
    assertThat(resources.getDrawable(R.anim.animation_list)).isInstanceOf(AnimationDrawable.class);
  }

  @Test
  public void testGetColorDrawable() {
    Drawable drawable = resources.getDrawable(R.color.color_with_alpha);
    assertThat(drawable).isInstanceOf(ColorDrawable.class);
    assertThat(((ColorDrawable) drawable).getColor()).isEqualTo(0x802C76AD);
  }

  @Test
  public void getColor() {
    assertThat(resources.getColor(R.color.color_with_alpha)).isEqualTo(0x802C76AD);
  }

  @Test
  public void getColor_withReference() {
    assertThat(resources.getColor(R.color.background)).isEqualTo(0xfff5f5f5);
  }

  @Test
  public void testGetColor_Missing() {
    assertThrows(Resources.NotFoundException.class, () -> resources.getColor(11234));
  }

  @Test
  public void testGetColorStateList() {
    assertThat(resources.getColorStateList(R.color.color_state_list))
        .isInstanceOf(ColorStateList.class);
  }

  @Test
  public void testGetBitmapDrawable() {
    assertThat(resources.getDrawable(R.drawable.an_image)).isInstanceOf(BitmapDrawable.class);
  }

  @Test
  public void testGetNinePatchDrawable() {
    assertThat(resources.getDrawable(R.drawable.nine_patch_drawable))
        .isInstanceOf(NinePatchDrawable.class);
  }

  @Test
  public void testGetBitmapDrawableForUnknownId() {
    assertThrows(
        Resources.NotFoundException.class,
        () ->
            assertThat(resources.getDrawable(Integer.MAX_VALUE))
                .isInstanceOf(BitmapDrawable.class));
  }

  @Test
  public void testGetNinePatchDrawableIntrinsicWidth() {
    float density = resources.getDisplayMetrics().density;
    NinePatchDrawable ninePatchDrawable =
        (NinePatchDrawable) resources.getDrawable(R.drawable.nine_patch_drawable);
    // Use Math.round to convert calculated float width to int,
    // see NinePatchDrawable#scaleFromDensity.
    assertThat(ninePatchDrawable.getIntrinsicWidth()).isEqualTo(Math.round(98.0f * density));
  }

  @Test
  public void testGetIdentifier() {

    final String resourceType = "string";
    final String packageName = context.getPackageName();

    final String resourceName = "hello";
    final int resId1 = resources.getIdentifier(resourceName, resourceType, packageName);
    assertThat(resId1).isEqualTo(R.string.hello);

    final String typedResourceName = resourceType + "/" + resourceName;
    final int resId2 = resources.getIdentifier(typedResourceName, resourceType, packageName);
    assertThat(resId2).isEqualTo(R.string.hello);

    final String fqn = packageName + ":" + typedResourceName;
    final int resId3 = resources.getIdentifier(fqn, resourceType, packageName);
    assertThat(resId3).isEqualTo(R.string.hello);
  }

  @Test
  public void getIdentifier() {
    String string = resources.getString(R.string.hello);
    assertThat(string).isEqualTo("Hello");

    int id = resources.getIdentifier("hello", "string", context.getPackageName());
    assertThat(id).isEqualTo(R.string.hello);

    String hello = resources.getString(id);
    assertThat(hello).isEqualTo("Hello");
  }

  @Test
  public void getIdentifier_nonExistantResource() {
    int id = resources.getIdentifier("just_alot_of_crap", "string", context.getPackageName());
    assertThat(id).isEqualTo(0);
  }

  @Test
  public void getIdentifier_material() {
    int id = Resources.getSystem().getIdentifier("btn_check_material_anim", "drawable", "android");
    assertThat(id).isGreaterThan(0);
  }

  /**
   * Public framework symbols are defined here:
   * https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/public.xml
   * Private framework symbols are defined here:
   * https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/symbols.xml
   *
   * <p>These generate android.R and com.android.internal.R respectively, when Framework Java code
   * does not need to reference a framework resource it will not have an R value generated.
   * Robolectric is then missing an identifier for this resource so we must generate a placeholder
   * ourselves.
   */
  @Test
  public void shouldGenerateIdsForResourcesThatAreMissingRValues() {
    int identifierMissingFromRFile =
        resources.getIdentifier("secondary_text_material_dark", "color", "android");

    // We expect Robolectric to generate a placeholder identifier where one was not generated in the
    // android R files.
    assertThat(identifierMissingFromRFile).isNotEqualTo(0);

    // We expect to be able to successfully android:color/secondary_text_material_dark to a
    // ColorStateList.
    assertThat(resources.getColorStateList(identifierMissingFromRFile)).isNotNull();
  }

  @Test
  public void getSystemShouldReturnSystemResources() {
    assertThat(Resources.getSystem()).isInstanceOf(Resources.class);
  }

  @Test
  public void multipleCallsToGetSystemShouldReturnSameInstance() {
    assertThat(Resources.getSystem()).isEqualTo(Resources.getSystem());
  }

  @Test
  public void applicationResourcesShouldHaveBothSystemAndLocalValues() {
    assertThat(context.getResources().getString(android.R.string.copy)).isEqualTo("Copy");
    assertThat(context.getResources().getString(R.string.copy)).isEqualTo("Local Copy");
  }

  @Test
  public void systemResourcesShouldReturnCorrectSystemId() {
    assertThat(Resources.getSystem().getIdentifier("copy", "string", "android"))
        .isEqualTo(android.R.string.copy);
  }

  @Test
  public void systemResourcesShouldReturnZeroForLocalId() {
    assertThat(Resources.getSystem().getIdentifier("copy", "string", context.getPackageName()))
        .isEqualTo(0);
  }

  @Test
  public void testGetXml() throws Exception {
    XmlResourceParser parser = resources.getXml(R.xml.preferences);
    assertThat(parser).isNotNull();
    assertThat(findRootTag(parser)).isEqualTo("PreferenceScreen");

    parser = resources.getXml(R.layout.custom_layout);
    assertThat(parser).isNotNull();
    assertThat(findRootTag(parser)).isEqualTo("org.robolectric.android.CustomView");

    parser = resources.getXml(R.menu.test);
    assertThat(parser).isNotNull();
    assertThat(findRootTag(parser)).isEqualTo("menu");

    parser = resources.getXml(R.drawable.rainbow);
    assertThat(parser).isNotNull();
    assertThat(findRootTag(parser)).isEqualTo("layer-list");

    parser = resources.getXml(R.anim.test_anim_1);
    assertThat(parser).isNotNull();
    assertThat(findRootTag(parser)).isEqualTo("set");

    parser = resources.getXml(R.color.color_state_list);
    assertThat(parser).isNotNull();
    assertThat(findRootTag(parser)).isEqualTo("selector");
  }

  @Test
  public void testGetXml_nonexistentResource() {
    assertThrows(Resources.NotFoundException.class, () -> resources.getXml(0));
  }

  @Test
  public void testGetXml_nonxmlfile() {
    assertThrows(Resources.NotFoundException.class, () -> resources.getXml(R.drawable.an_image));
  }

  @Test
  public void testGetXml_notNPEAfterClose() {
    XmlResourceParser parser = resources.getXml(R.xml.preferences);
    parser.close();
    // the following methods should not NPE if the XmlResourceParser has been closed.
    assertThat(parser.getName()).isNull();
    assertThat(parser.getNamespace()).isEmpty();
    assertThat(parser.getText()).isNull();
  }

  @Test
  public void openRawResource_shouldLoadRawResources() {
    InputStream resourceStream = resources.openRawResource(R.raw.raw_resource);
    assertThat(resourceStream).isNotNull();
    // assertThat(TestUtil.readString(resourceStream)).isEqualTo("raw txt file contents");
  }

  @Test
  public void openRawResource_shouldLoadDrawables() {
    InputStream resourceStream = resources.openRawResource(R.drawable.an_image);
    Bitmap bitmap = BitmapFactory.decodeStream(resourceStream);
    assertThat(bitmap.getHeight()).isEqualTo(53);
    assertThat(bitmap.getWidth()).isEqualTo(64);
  }

  @Test
  public void openRawResource_withNonFile_throwsNotFoundException() {
    try {
      resources.openRawResource(R.string.hello);
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }

    try {
      resources.openRawResource(R.string.hello, new TypedValue());
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }

    try {
      resources.openRawResource(-1234, new TypedValue());
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }
  }

  @Test
  public void openRawResourceFd_withNonCompressedFile_returnsNotNull() throws IOException {
    // This test will run on non-legacy resource mode in Robolectric environment.
    // To test behavior on legacy mode environment, please see ShadowResourceTest.
    try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) {
      assertThat(afd).isNotNull();
    }
  }

  @Test
  public void openRawResourceFd_withNonFile_throwsNotFoundException() {
    try {
      resources.openRawResourceFd(R.string.hello);
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }

    try {
      resources.openRawResourceFd(-1234);
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }
  }

  @Test
  public void getXml_withNonFile_throwsNotFoundException() {
    try {
      resources.getXml(R.string.hello);
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }

    try {
      resources.getXml(-1234);
      fail("should throw");
    } catch (Resources.NotFoundException e) {
      // cool
    }
  }

  @Test
  public void themeResolveAttribute_shouldSupportNotDereferencingResource() {
    TypedValue out = new TypedValue();

    Resources.Theme theme = resources.newTheme();
    theme.applyStyle(R.style.MyBlackTheme, false);

    theme.resolveAttribute(android.R.attr.windowBackground, out, false);
    assertThat(out.type).isEqualTo(TYPE_REFERENCE);
    assertThat(out.data).isEqualTo(android.R.color.black);
  }

  @Test
  public void obtainAttributes_shouldReturnValuesFromResources() throws Exception {
    XmlPullParser parser = resources.getXml(R.xml.xml_attrs);
    parser.next();
    parser.next();
    AttributeSet attributes = Xml.asAttributeSet(parser);

    TypedArray typedArray =
        resources.obtainAttributes(
            attributes, new int[] {android.R.attr.title, android.R.attr.scrollbarFadeDuration});

    assertThat(typedArray.getString(0)).isEqualTo("Android Title");
    assertThat(typedArray.getInt(1, 0)).isEqualTo(1111);
    typedArray.recycle();
  }

  // @Test
  // public void obtainAttributes_shouldUseReferencedIdFromAttributeSet() throws Exception {
  //   // android:id/mask was introduced in API 21, but it's still possible for apps built against
  // API 21 to refer to it
  //   // in older runtimes because referenced resource ids are compiled (by aapt) into the binary
  // XML format.
  //   AttributeSet attributeSet = Robolectric.buildAttributeSet()
  //       .addAttribute(android.R.attr.id, "@android:id/mask").build();
  //   TypedArray typedArray = resources.obtainAttributes(attributeSet, new
  // int[]{android.R.attr.id});
  //   assertThat(typedArray.getResourceId(0, -9)).isEqualTo(android.R.id.mask);
  // }

  @Test
  public void obtainStyledAttributesShouldDereferenceValues() {
    Resources.Theme theme = resources.newTheme();
    theme.applyStyle(R.style.MyBlackTheme, false);
    TypedArray arr = theme.obtainStyledAttributes(new int[] {android.R.attr.windowBackground});
    TypedValue value = new TypedValue();
    arr.getValue(0, value);
    arr.recycle();

    assertThat(value.type).isAtLeast(TYPE_FIRST_COLOR_INT);
    assertThat(value.type).isAtMost(TYPE_LAST_INT);
  }

  // @Test
  // public void obtainStyledAttributes_shouldCheckXmlFirst_fromAttributeSetBuilder() throws
  // Exception {
  //
  //   // This simulates a ResourceProvider built from a 21+ SDK as viewportHeight / viewportWidth
  // were introduced in API 21
  //   // but the public ID values they are assigned clash with private com.android.internal.R
  // values on older SDKs. This
  //   // test ensures that even on older SDKs, on calls to obtainStyledAttributes() Robolectric
  // will first check for matching
  //   // resource ID values in the AttributeSet before checking the theme.
  //
  //   AttributeSet attributes = Robolectric.buildAttributeSet()
  //       .addAttribute(android.R.attr.viewportWidth, "12.0")
  //       .addAttribute(android.R.attr.viewportHeight, "24.0")
  //       .build();
  //
  //   TypedArray typedArray = context.getTheme().obtainStyledAttributes(attributes, new int[] {
  //       android.R.attr.viewportWidth,
  //       android.R.attr.viewportHeight
  //   }, 0, 0);
  //   assertThat(typedArray.getFloat(0, 0)).isEqualTo(12.0f);
  //   assertThat(typedArray.getFloat(1, 0)).isEqualTo(24.0f);
  //   typedArray.recycle();
  // }

  @Test
  public void obtainStyledAttributes_shouldCheckXmlFirst_fromXmlLoadedFromResources()
      throws Exception {

    // This simulates a ResourceProvider built from a 21+ SDK as viewportHeight / viewportWidth were
    // introduced in API 21
    // but the public ID values they are assigned clash with private com.android.internal.R values
    // on older SDKs. This
    // test ensures that even on older SDKs, on calls to obtainStyledAttributes() Robolectric will
    // first check for matching
    // resource ID values in the AttributeSet before checking the theme.

    XmlResourceParser xml = context.getResources().getXml(R.drawable.vector);
    xml.next();
    xml.next();
    AttributeSet attributeSet = Xml.asAttributeSet(xml);

    TypedArray typedArray =
        context
            .getTheme()
            .obtainStyledAttributes(
                attributeSet,
                new int[] {android.R.attr.viewportWidth, android.R.attr.viewportHeight},
                0,
                0);
    assertThat(typedArray.getFloat(0, 0)).isEqualTo(12.0f);
    assertThat(typedArray.getFloat(1, 0)).isEqualTo(24.0f);
    typedArray.recycle();
  }

  @Test
  public void whenAttrIsDefinedInRuntimeSdk_getResourceName_findsResource() {
    assertThat(context.getResources().getResourceName(android.R.attr.viewportHeight))
        .isEqualTo("android:attr/viewportHeight");
  }

  @Test
  public void subClassInitializedOK() {
    SubClassResources subClassResources = new SubClassResources(resources);
    assertThat(subClassResources.openRawResource(R.raw.raw_resource)).isNotNull();
  }

  @Test
  public void applyStyleForced() {
    final Resources.Theme theme = resources.newTheme();

    theme.applyStyle(R.style.MyBlackTheme, true);
    TypedArray arr =
        theme.obtainStyledAttributes(
            new int[] {android.R.attr.windowBackground, android.R.attr.textColorHint});

    final TypedValue blackBackgroundColor = new TypedValue();
    arr.getValue(0, blackBackgroundColor);
    assertThat(blackBackgroundColor.resourceId).isEqualTo(android.R.color.black);
    arr.recycle();

    theme.applyStyle(R.style.MyBlueTheme, true);
    arr =
        theme.obtainStyledAttributes(
            new int[] {
              android.R.attr.windowBackground,
              android.R.attr.textColor,
              android.R.attr.textColorHint
            });

    final TypedValue blueBackgroundColor = new TypedValue();
    arr.getValue(0, blueBackgroundColor);
    assertThat(blueBackgroundColor.resourceId).isEqualTo(R.color.blue);

    final TypedValue blueTextColor = new TypedValue();
    arr.getValue(1, blueTextColor);
    assertThat(blueTextColor.resourceId).isEqualTo(R.color.white);

    final TypedValue blueTextColorHint = new TypedValue();
    arr.getValue(2, blueTextColorHint);
    assertThat(blueTextColorHint.resourceId).isEqualTo(android.R.color.darker_gray);

    arr.recycle();
  }

  @Test
  public void applyStyleNotForced() {
    final Resources.Theme theme = resources.newTheme();

    // Apply black theme
    theme.applyStyle(R.style.MyBlackTheme, true);
    TypedArray arr =
        theme.obtainStyledAttributes(
            new int[] {android.R.attr.windowBackground, android.R.attr.textColorHint});

    final TypedValue blackBackgroundColor = new TypedValue();
    arr.getValue(0, blackBackgroundColor);
    assertThat(blackBackgroundColor.resourceId).isEqualTo(android.R.color.black);

    final TypedValue blackTextColorHint = new TypedValue();
    arr.getValue(1, blackTextColorHint);
    assertThat(blackTextColorHint.resourceId).isEqualTo(android.R.color.darker_gray);

    arr.recycle();

    // Apply blue theme
    theme.applyStyle(R.style.MyBlueTheme, false);
    arr =
        theme.obtainStyledAttributes(
            new int[] {
              android.R.attr.windowBackground,
              android.R.attr.textColor,
              android.R.attr.textColorHint
            });

    final TypedValue blueBackgroundColor = new TypedValue();
    arr.getValue(0, blueBackgroundColor);
    assertThat(blueBackgroundColor.resourceId).isEqualTo(android.R.color.black);

    final TypedValue blueTextColor = new TypedValue();
    arr.getValue(1, blueTextColor);
    assertThat(blueTextColor.resourceId).isEqualTo(R.color.white);

    final TypedValue blueTextColorHint = new TypedValue();
    arr.getValue(2, blueTextColorHint);
    assertThat(blueTextColorHint.resourceId).isEqualTo(android.R.color.darker_gray);

    arr.recycle();
  }

  @Test
  public void getValueShouldClearTypedArrayBetweenCalls() {
    TypedValue outValue = new TypedValue();

    resources.getValue(R.string.hello, outValue, true);
    assertThat(outValue.type).isEqualTo(TYPE_STRING);
    assertThat(outValue.string).isEqualTo(resources.getString(R.string.hello));
    // outValue.data is an index into the String block which we don't know for raw xml resources.
    assertThat(outValue.assetCookie).isNotEqualTo(0);

    resources.getValue(R.color.blue, outValue, true);
    assertThat(outValue.type).isEqualTo(TYPE_INT_COLOR_RGB8);
    assertThat(outValue.data).isEqualTo(0xFF0000FF);
    assertThat(outValue.string).isNull();
    // outValue.assetCookie is not supported with raw XML

    resources.getValue(R.integer.loneliest_number, outValue, true);
    assertThat(outValue.type).isEqualTo(TYPE_INT_DEC);
    assertThat(outValue.data).isEqualTo(1);
    assertThat(outValue.string).isNull();

    resources.getValue(R.bool.true_bool_value, outValue, true);
    assertThat(outValue.type).isEqualTo(TYPE_INT_BOOLEAN);
    assertThat(outValue.data).isNotEqualTo(0); // true == traditionally 0xffffffff, -1 in Java but
    // tests should be checking for non-zero
    assertThat(outValue.string).isNull();
  }

  @Test
  public void getXml() throws Exception {
    XmlResourceParser xmlResourceParser = resources.getXml(R.xml.preferences);
    assertThat(xmlResourceParser).isNotNull();
    assertThat(xmlResourceParser.next()).isEqualTo(XmlResourceParser.START_DOCUMENT);
    assertThat(xmlResourceParser.next()).isEqualTo(XmlResourceParser.START_TAG);
    assertThat(xmlResourceParser.getName()).isEqualTo("PreferenceScreen");
  }

  @Test
  public void getXml_shouldParseEmojiCorrectly() throws IOException, XmlPullParserException {
    XmlResourceParser xmlResourceParser = resources.getXml(R.xml.has_emoji);
    xmlResourceParser.next();
    xmlResourceParser.next();
    assertThat(xmlResourceParser.getName()).isEqualTo("EmojiRoot");
    AttributeSet attributeSet = Xml.asAttributeSet(xmlResourceParser);
    assertThat(attributeSet.getAttributeValue(null, "label1")).isEqualTo("no emoji");
    String pureEmoji = "\uD83D\uDE00";
    assertThat(attributeSet.getAttributeValue(null, "label2")).isEqualTo(pureEmoji);
    assertThat(attributeSet.getAttributeValue(null, "label3")).isEqualTo(pureEmoji);
    String mixEmojiAndText = "\uD83D\uDE00internal1\uD83D\uDE00internal2\uD83D\uDE00";
    assertThat(attributeSet.getAttributeValue(null, "label4")).isEqualTo(mixEmojiAndText);
    assertThat(attributeSet.getAttributeValue(null, "label5")).isEqualTo(mixEmojiAndText);
    assertThat(attributeSet.getAttributeValue(null, "label6"))
        .isEqualTo("don't worry be \uD83D\uDE00");
  }

  @Test
  public void whenMissingXml_throwNotFoundException() {
    try {
      resources.getXml(0x3038);
      fail();
    } catch (Resources.NotFoundException e) {
      assertThat(e.getMessage()).contains("Resource ID #0x3038");
    }
  }

  @Test
  public void stringWithSpaces() {
    // this differs from actual Android behavior, which collapses whitespace as "Up to 25 USD"
    assertThat(resources.getString(R.string.string_with_spaces, "25", "USD"))
        .isEqualTo("Up to 25 USD");
  }

  @Test
  public void internalWhiteSpaceShouldBeCollapsed() {
    assertThat(resources.getString(R.string.internal_whitespace_blocks))
        .isEqualTo("Whitespace in" + " the middle");
    assertThat(resources.getString(R.string.internal_newlines)).isEqualTo("Some Newlines");
  }

  @Test
  public void fontTagWithAttributesShouldBeRead() {
    assertThat(resources.getString(R.string.font_tag_with_attribute))
        .isEqualTo("This string has a font tag");
  }

  @Test
  public void linkTagWithAttributesShouldBeRead() {
    assertThat(resources.getString(R.string.link_tag_with_attribute))
        .isEqualTo("This string has a link tag");
  }

  @Test
  public void getResourceTypeName_mipmap() {
    assertThat(resources.getResourceTypeName(R.mipmap.mipmap_reference)).isEqualTo("mipmap");
    assertThat(resources.getResourceTypeName(R.mipmap.robolectric)).isEqualTo("mipmap");
  }

  @Test
  public void getDrawable_mipmapReferencesResolve() {
    Drawable reference = resources.getDrawable(R.mipmap.mipmap_reference);
    Drawable original = resources.getDrawable(R.mipmap.robolectric);

    assertThat(reference.getMinimumHeight()).isEqualTo(original.getMinimumHeight());
    assertThat(reference.getMinimumWidth()).isEqualTo(original.getMinimumWidth());
  }

  @Test
  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
  @Config(minSdk = Build.VERSION_CODES.O)
  public void getDrawable_mipmapReferencesResolveXml() {
    Drawable reference = resources.getDrawable(R.mipmap.robolectric_xml);
    Drawable original = resources.getDrawable(R.mipmap.mipmap_reference_xml);

    assertThat(reference.getMinimumHeight()).isEqualTo(original.getMinimumHeight());
    assertThat(reference.getMinimumWidth()).isEqualTo(original.getMinimumWidth());
  }

  @Test
  public void forUntouchedThemes_copyTheme_shouldCopyNothing() {
    Resources.Theme theme1 = resources.newTheme();
    Resources.Theme theme2 = resources.newTheme();
    theme2.setTo(theme1);
  }

  @Test
  public void getResourceIdentifier_shouldReturnValueFromRClass() {
    assertThat(resources.getIdentifier("id_declared_in_item_tag", "id", context.getPackageName()))
        .isEqualTo(R.id.id_declared_in_item_tag);
    assertThat(
            resources.getIdentifier("id/id_declared_in_item_tag", null, context.getPackageName()))
        .isEqualTo(R.id.id_declared_in_item_tag);
    assertThat(
            resources.getIdentifier(
                context.getPackageName() + ":id_declared_in_item_tag", "id", null))
        .isEqualTo(R.id.id_declared_in_item_tag);
    assertThat(
            resources.getIdentifier(
                context.getPackageName() + ":id/id_declared_in_item_tag", "other", "other"))
        .isEqualTo(R.id.id_declared_in_item_tag);
  }

  @Test
  public void whenPackageIsUnknown_getResourceIdentifier_shouldReturnZero() {
    assertThat(resources.getIdentifier("whatever", "id", "some.unknown.package")).isEqualTo(0);
    assertThat(resources.getIdentifier("id/whatever", null, "some.unknown.package")).isEqualTo(0);
    assertThat(resources.getIdentifier("some.unknown.package:whatever", "id", null)).isEqualTo(0);
    assertThat(resources.getIdentifier("some.unknown.package:id/whatever", "other", "other"))
        .isEqualTo(0);

    assertThat(resources.getIdentifier("whatever", "drawable", "some.unknown.package"))
        .isEqualTo(0);
    assertThat(resources.getIdentifier("drawable/whatever", null, "some.unknown.package"))
        .isEqualTo(0);
    assertThat(resources.getIdentifier("some.unknown.package:whatever", "drawable", null))
        .isEqualTo(0);
    assertThat(resources.getIdentifier("some.unknown.package:id/whatever", "other", "other"))
        .isEqualTo(0);
  }

  @Test
  @Ignore(
      "currently ids are always automatically assigned a value; to fix this we'd need to check "
          + "layouts for +@id/___, which is expensive")
  public void whenCalledForIdWithNameNotInRClassOrXml_getResourceIdentifier_shouldReturnZero() {
    assertThat(
            resources.getIdentifier(
                "org.robolectric:id/idThatDoesntExistAnywhere", "other", "other"))
        .isEqualTo(0);
  }

  @Test
  public void
      whenIdIsAbsentInXmlButPresentInRClass_getResourceIdentifier_shouldReturnIdFromRClass_probablyBecauseItWasDeclaredInALayout() {
    assertThat(resources.getIdentifier("id_declared_in_layout", "id", context.getPackageName()))
        .isEqualTo(R.id.id_declared_in_layout);
  }

  @Test
  public void whenResourceIsAbsentInXml_getResourceIdentifier_shouldReturn0() {
    assertThat(resources.getIdentifier("fictitiousDrawable", "drawable", context.getPackageName()))
        .isEqualTo(0);
  }

  @Test
  public void whenResourceIsAbsentInXml_getResourceIdentifier_shouldReturnId() {
    assertThat(resources.getIdentifier("an_image", "drawable", context.getPackageName()))
        .isEqualTo(R.drawable.an_image);
  }

  @Test
  public void whenResourceIsXml_getResourceIdentifier_shouldReturnId() {
    assertThat(resources.getIdentifier("preferences", "xml", context.getPackageName()))
        .isEqualTo(R.xml.preferences);
  }

  @Test
  public void whenResourceIsRaw_getResourceIdentifier_shouldReturnId() {
    assertThat(resources.getIdentifier("raw_resource", "raw", context.getPackageName()))
        .isEqualTo(R.raw.raw_resource);
  }

  @Test
  public void getResourceValue_colorARGB8() {
    TypedValue outValue = new TypedValue();
    resources.getValue(test_ARGB8, outValue, false);
    assertThat(outValue.type).isEqualTo(TYPE_INT_COLOR_ARGB8);
    assertThat(Color.blue(outValue.data)).isEqualTo(2);
  }

  @Test
  public void getResourceValue_colorRGB8() {
    TypedValue outValue = new TypedValue();
    resources.getValue(test_RGB8, outValue, false);
    assertThat(outValue.type).isEqualTo(TYPE_INT_COLOR_RGB8);
    assertThat(Color.blue(outValue.data)).isEqualTo(4);
  }

  @Test
  public void getResourceEntryName_forStyle() {
    assertThat(resources.getResourceEntryName(android.R.style.TextAppearance_Small))
        .isEqualTo("TextAppearance.Small");
  }

  @Test
  @SdkSuppress(minSdkVersion = O)
  @Config(minSdk = O)
  public void getFont() {
    // Feature not supported in legacy (raw) resource mode.
    assumeFalse(isRobolectricLegacyMode());

    Typeface typeface = resources.getFont(R.font.vt323_regular);
    assertThat(typeface).isNotNull();
  }

  @Test
  @SdkSuppress(minSdkVersion = O)
  @Config(minSdk = O)
  public void getFontFamily() {
    // Feature not supported in legacy (raw) resource mode.
    assumeFalse(isRobolectricLegacyMode());

    Typeface typeface = resources.getFont(R.font.vt323);
    assertThat(typeface).isNotNull();
  }

  @Test
  @SdkSuppress(minSdkVersion = O)
  @Config(minSdk = O)
  public void getFontFamily_downloadable() {
    // Feature not supported in legacy (raw) resource mode.
    assumeFalse(isRobolectricLegacyMode());

    Typeface typeface = resources.getFont(R.font.downloadable);
    assertThat(typeface).isNotNull();
  }

  @Test
  @SdkSuppress(minSdkVersion = Q)
  @Config(minSdk = Q)
  public void testFontBuilder() throws Exception {
    // Used to throw `java.io.IOException: Failed to read font contents`
    new Font.Builder(context.getResources(), R.font.vt323_regular).build();
  }

  @Test
  @SdkSuppress(minSdkVersion = Q)
  @Config(minSdk = Q)
  public void fontFamily_getFont() throws Exception {
    Font platformFont = new Font.Builder(resources, R.font.vt323_regular).build();
    FontFamily fontFamily = new FontFamily.Builder(platformFont).build();
    assertThat(fontFamily.getFont(0)).isNotNull();
  }

  @Test
  @SdkSuppress(minSdkVersion = Q)
  @Config(minSdk = Q)
  public void getAttributeSetSourceResId() {
    XmlResourceParser xmlResourceParser = resources.getXml(R.xml.preferences);

    int sourceResId = Resources.getAttributeSetSourceResId(xmlResourceParser);

    assertThat(sourceResId).isEqualTo(R.xml.preferences);
  }

  private static String findRootTag(XmlResourceParser parser) throws Exception {
    int event;
    do {
      event = parser.next();
    } while (event != XmlPullParser.START_TAG);
    return parser.getName();
  }

  private static class SubClassResources extends Resources {
    public SubClassResources(Resources res) {
      super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
    }
  }

  private static boolean isRobolectricLegacyMode() {
    try {
      Class<?> runtimeEnvironmentClass = Class.forName("org.robolectric.RuntimeEnvironment");
      Method useLegacyResourcesMethod =
          runtimeEnvironmentClass.getDeclaredMethod("useLegacyResources");
      return (boolean) useLegacyResourcesMethod.invoke(null);
    } catch (Exception e) {
      return false;
    }
  }
}
