blob: 51e7cdfea57f67e6fed4e69c0aad82a802a18f81 [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.N_MR1;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.Range;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.R;
import org.robolectric.Robolectric;
import org.robolectric.android.XmlResourceParserImpl;
import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowResourcesTest {
private Resources resources;
@Before
public void setup() throws Exception {
resources = ApplicationProvider.getApplicationContext().getResources();
}
@Test
@Config(qualifiers = "fr")
public void testGetValuesResFromSpecificQualifiers() {
assertThat(resources.getString(R.string.hello)).isEqualTo("Bonjour");
}
/**
* 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
@Config(
sdk =
Build.VERSION_CODES
.LOLLIPOP) // android:color/secondary_text_material_dark was added in API 21
public void shouldGenerateIdsForResourcesThatAreMissingRValues() {
int identifier_missing_from_r_file =
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(identifier_missing_from_r_file).isNotEqualTo(0);
// We expect to be able to successfully android:color/secondary_text_material_dark to a
// ColorStateList.
assertThat(resources.getColorStateList(identifier_missing_from_r_file)).isNotNull();
}
@Test
@Config(qualifiers = "fr")
public void openRawResource_shouldLoadDrawableWithQualifiers() {
InputStream resourceStream = resources.openRawResource(R.drawable.an_image);
Bitmap bitmap = BitmapFactory.decodeStream(resourceStream);
assertThat(bitmap.getHeight()).isEqualTo(100);
assertThat(bitmap.getWidth()).isEqualTo(100);
}
@Test
public void openRawResourceFd_returnsNull_todo_FIX() throws Exception {
try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) {
if (useLegacy()) {
assertThat(afd).isNull();
} else {
assertThat(afd).isNotNull();
}
}
}
@Test
@Config
public void themeResolveAttribute_shouldSupportDereferenceResource() {
TypedValue out = new TypedValue();
Resources.Theme theme = resources.newTheme();
theme.applyStyle(R.style.MyBlackTheme, false);
theme.resolveAttribute(android.R.attr.windowBackground, out, true);
assertThat(out.type).isNotEqualTo(TypedValue.TYPE_REFERENCE);
assertThat(out.type)
.isIn(Range.closed(TypedValue.TYPE_FIRST_COLOR_INT, TypedValue.TYPE_LAST_COLOR_INT));
int value = resources.getColor(android.R.color.black);
assertThat(out.data).isEqualTo(value);
}
@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(TypedValue.TYPE_REFERENCE);
assertThat(out.data).isEqualTo(android.R.color.black);
}
@Test
public void obtainStyledAttributes_shouldCheckXmlFirst_fromAttributeSetBuilder() {
// 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 =
ApplicationProvider.getApplicationContext()
.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 getXml_shouldHavePackageContextForReferenceResolution() {
if (!useLegacy()) {
return;
}
XmlResourceParserImpl xmlResourceParser =
(XmlResourceParserImpl) resources.getXml(R.xml.preferences);
assertThat(xmlResourceParser.qualify("?ref")).isEqualTo("?org.robolectric:attr/ref");
xmlResourceParser = (XmlResourceParserImpl) resources.getXml(android.R.layout.list_content);
assertThat(xmlResourceParser.qualify("?ref")).isEqualTo("?android:attr/ref");
}
@Test
@Config(minSdk = N_MR1)
public void obtainAttributes() {
TypedArray typedArray =
resources.obtainAttributes(
Robolectric.buildAttributeSet()
.addAttribute(R.attr.styleReference, "@xml/shortcuts")
.build(),
new int[] {R.attr.styleReference});
assertThat(typedArray).isNotNull();
assertThat(typedArray.peekValue(0).resourceId).isEqualTo(R.xml.shortcuts);
}
@Test
public void obtainAttributes_shouldUseReferencedIdFromAttributeSet() {
// 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 obtainAttributes_shouldReturnValuesFromAttributeSet() {
AttributeSet attributes =
Robolectric.buildAttributeSet()
.addAttribute(android.R.attr.title, "A title!")
.addAttribute(android.R.attr.width, "12px")
.addAttribute(android.R.attr.height, "1in")
.build();
TypedArray typedArray =
resources.obtainAttributes(
attributes,
new int[] {android.R.attr.height, android.R.attr.width, android.R.attr.title});
assertThat(typedArray.getDimension(0, 0)).isEqualTo(160f);
assertThat(typedArray.getDimension(1, 0)).isEqualTo(12f);
assertThat(typedArray.getString(2)).isEqualTo("A title!");
typedArray.recycle();
}
@Test
public void obtainStyledAttributesShouldCheckXmlFirst_andFollowReferences() {
// 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, "@integer/test_integer1")
.addAttribute(android.R.attr.viewportHeight, "@integer/test_integer2")
.build();
TypedArray typedArray =
ApplicationProvider.getApplicationContext()
.getTheme()
.obtainStyledAttributes(
attributes,
new int[] {android.R.attr.viewportWidth, android.R.attr.viewportHeight},
0,
0);
assertThat(typedArray.getFloat(0, 0)).isEqualTo(2000);
assertThat(typedArray.getFloat(1, 0)).isEqualTo(9);
typedArray.recycle();
}
@Test
public void getAttributeSetSourceResId() {
XmlResourceParser xmlResourceParser = resources.getXml(R.xml.preferences);
int sourceRedId = ShadowResources.getAttributeSetSourceResId(xmlResourceParser);
assertThat(sourceRedId).isEqualTo(R.xml.preferences);
}
@Test
public void addConfigurationChangeListener_callsOnConfigurationChange() {
AtomicBoolean listenerWasCalled = new AtomicBoolean();
shadowOf(resources)
.addConfigurationChangeListener(
(oldConfig, newConfig, newMetrics) -> {
listenerWasCalled.set(true);
assertThat(newConfig.fontScale).isEqualTo(oldConfig.fontScale * 2);
});
Configuration newConfig = new Configuration(resources.getConfiguration());
newConfig.fontScale *= 2;
resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
assertThat(listenerWasCalled.get()).isTrue();
}
@Test
public void removeConfigurationChangeListener_doesNotCallOnConfigurationChange() {
AtomicBoolean listenerWasCalled = new AtomicBoolean();
ShadowResources.OnConfigurationChangeListener listener =
(oldConfig, newConfig, newMetrics) -> listenerWasCalled.set(true);
Configuration newConfig = new Configuration(resources.getConfiguration());
newConfig.fontScale *= 2;
shadowOf(resources).addConfigurationChangeListener(listener);
shadowOf(resources).removeConfigurationChangeListener(listener);
resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
assertThat(listenerWasCalled.get()).isFalse();
}
@Test
public void subclassWithNpeGetConfiguration_constructsCorrectly() {
// Simulate the behavior of ResourcesWrapper during construction which will throw an NPE if
// getConfiguration is called, on lower SDKs the Configuration constructor calls
// updateConfiguration(), the ShadowResources will attempt to call getConfiguration during this
// method call and shouldn't fail.
Resources resourcesSubclass =
new Resources(
resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration()) {
@Override
public Configuration getConfiguration() {
throw new NullPointerException();
}
};
assertThat(resourcesSubclass).isNotNull();
}
}